#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![allow(dead_code, unused)]
#![allow(clippy::unnecessary_unwrap, clippy::zombie_processes)]
use rusty_h264_common::YuvFrame;
use rusty_h264_decoder::Decoder;
use rusty_h264_decoder::edc_stats_report;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn feed_hash(h: &mut DefaultHasher, fr: &YuvFrame) {
fr.y.hash(h);
fr.u.hash(h);
fr.v.hash(h);
fr.width.hash(h);
fr.height.hash(h);
}
fn main() {
rusty_h264_common::alloc_site_arm();
let args: Vec<String> = std::env::args().skip(1).collect();
let path = args.first().expect("usage: decode_bench <stream.264> [reps]");
let reps: usize = args.get(1).and_then(|v| v.parse().ok()).unwrap_or(5);
let mut max_frames: Option<usize> = None;
let mut out_path: Option<String> = None;
let mut fthreads: usize = 0;
let mut hash_out = false;
for a in &args {
if let Some(v) = a.strip_prefix("mt=") {
std::env::set_var("RS_H264_EDC_MT", v);
}
if let Some(v) = a.strip_prefix("edc=") {
std::env::set_var("RS_H264_EDC", v);
}
if let Some(v) = a.strip_prefix("bound=") {
std::env::set_var("RS_H264_EDC_BOUND", v);
}
if a == "double=1" {
std::env::set_var("RS_H264_DOUBLE_RECON", "1");
}
if let Some(v) = a.strip_prefix("nores=") {
std::env::set_var("RS_H264_NORES", v);
}
if let Some(v) = a.strip_prefix("fatslice=") {
std::env::set_var("RS_H264_FAT_SLICE", v);
}
if let Some(v) = a.strip_prefix("slicepool=") {
if v == "0" { std::env::set_var("RS_H264_NO_SLICE_POOL", "1"); }
else { std::env::remove_var("RS_H264_NO_SLICE_POOL"); }
}
if let Some(v) = a.strip_prefix("batch=") {
std::env::set_var("RS_H264_BATCH", v);
}
if let Some(v) = a.strip_prefix("fthreads=") {
fthreads = v.parse().expect("fthreads=N");
std::env::set_var("RS_H264_FRAME_THREADS", v);
}
if let Some(v) = a.strip_prefix("rowprog=") {
std::env::set_var("RS_H264_ROW_PROGRESS", v);
}
if let Some(v) = a.strip_prefix("rowpub=") {
std::env::set_var("RS_H264_ROW_PUB", v);
}
if a == "rowhook=eager" {
std::env::set_var("RS_H264_ROWHOOK_EAGER", "1");
}
if a == "dmemo=0" {
std::env::set_var("RS_H264_DIRECT_MEMO", "0");
}
if a == "qpel=compose" {
std::env::set_var("RS_H264_QPEL_COMPOSE", "1");
}
if let Some(v) = a.strip_prefix("maxf=") {
max_frames = Some(v.parse().expect("maxf=N"));
}
if let Some(v) = a.strip_prefix("out=") {
out_path = Some(v.to_string());
}
if a == "hash=1" {
hash_out = true;
}
}
println!("arm : {}", rusty_h264_common::arms::simd_arms());
for (k, v, class) in rusty_h264_common::arms::active_knobs() {
println!("KNOB ACTIVE: {k}={v} [{class}]");
}
let input = std::fs::read(path).expect("read stream");
let (mut best, mut worst) = (f64::MAX, 0f64);
let (mut frames, mut px) = (0usize, 0usize);
for rep in 0..reps {
let mut dec = Decoder::new();
let t = std::time::Instant::now();
let (mut f, mut p) = (0usize, 0usize);
let mut hasher = DefaultHasher::new();
if hash_out && rep == 0 {
let limit = max_frames.unwrap_or(usize::MAX);
if fthreads > 1 {
let _n = dec
.decode_stream_threaded_sink(&input, fthreads, |fr| {
if f < limit {
feed_hash(&mut hasher, &fr);
f += 1;
p += fr.width * fr.height;
}
})
.expect("frame-mt hash");
} else {
let frames = dec.decode_stream(&input).expect("decode_stream hash");
for fr in frames.into_iter().take(limit) {
feed_hash(&mut hasher, &fr);
f += 1;
p += fr.width * fr.height;
}
}
eprintln!("hash={:016x} frames={f}", hasher.finish());
} else if fthreads > 1 {
let limit = max_frames.unwrap_or(usize::MAX);
if let Some(ref op) = out_path {
if rep == 0 {
let mut out_buf: Vec<u8> = Vec::new();
let mut done = false;
let _n = dec
.decode_stream_threaded_sink(&input, fthreads, |fr| {
if done || f >= limit {
done = true;
return;
}
out_buf.extend_from_slice(&fr.y);
out_buf.extend_from_slice(&fr.u);
out_buf.extend_from_slice(&fr.v);
f += 1;
p += fr.width * fr.height;
})
.expect("decode_stream_threaded");
std::fs::write(op, &out_buf).expect("write out=");
}
} else {
let _n = dec
.decode_stream_threaded_sink(&input, fthreads, |fr| {
if f < limit {
f += 1;
p += fr.width * fr.height;
}
})
.expect("frame-mt decode");
}
} else if out_path.is_some() && rep == 0 {
let limit = max_frames.unwrap_or(usize::MAX);
let mut gop: Vec<(i32, YuvFrame)> = Vec::new();
let mut out_buf: Vec<u8> = Vec::new();
let mut done = false;
for au in rusty_h264_decoder::split_access_units(&input) {
if rusty_h264_decoder::au_is_idr(au) {
gop.sort_by_key(|pair| pair.0);
for (_, fr) in gop.drain(..) {
if f >= limit {
done = true;
break;
}
out_buf.extend_from_slice(&fr.y);
out_buf.extend_from_slice(&fr.u);
out_buf.extend_from_slice(&fr.v);
f += 1;
p += fr.width * fr.height;
}
if done {
break;
}
}
if let Some(frame) = dec.decode(au).expect("decode") {
gop.push((dec.last_poc(), frame));
}
}
if !done {
gop.sort_by_key(|pair| pair.0);
for (_, fr) in gop.drain(..) {
if f >= limit {
break;
}
out_buf.extend_from_slice(&fr.y);
out_buf.extend_from_slice(&fr.u);
out_buf.extend_from_slice(&fr.v);
f += 1;
p += fr.width * fr.height;
}
}
std::fs::write(out_path.as_ref().unwrap(), &out_buf).expect("write out=");
} else {
for au in rusty_h264_decoder::split_access_units(&input) {
if let Some(fr) = dec.decode(au).expect("decode") {
f += 1;
p += fr.width * fr.height;
if max_frames.is_some_and(|m| f >= m) {
break;
}
}
}
}
let e = t.elapsed().as_secs_f64();
best = best.min(e);
worst = worst.max(e);
frames = f;
px = p;
}
println!(
"{}: frames={frames} px={px} best={:.1}ms worst={:.1}ms spread={:.1}% -> {:.1} Mpx/s",
path,
best * 1e3,
worst * 1e3,
(worst - best) * 100.0 / best,
px as f64 / best / 1e6
);
#[cfg(feature = "census")]
twin_census_report();
edc_stats_report();
rusty_h264_common::alloc_stats_report(frames);
#[cfg(feature = "profile")]
{
rusty_h264_common::prof::reset();
rusty_h264_common::deblock::census::reset();
let mut dec = Decoder::new();
for au in rusty_h264_decoder::split_access_units(&input) {
let _ = dec.decode(au).expect("decode");
}
rusty_h264_common::prof::dump();
rusty_h264_common::deblock::census::dump();
let (d, b, t) = rusty_h264_decoder::bin_census::snapshot();
let r = rusty_h264_decoder::bin_census::renorms();
eprintln!(
"--- CABAC bin census: {d} decisions ({r} renorm = {:.1}%), {b} bypasses, {t} terminates (total {} bins) ---",
100.0 * r as f64 / d.max(1) as f64,
d + b + t
);
}
let snap = rusty_h264_common::prof::snapshot();
let total = snap[rusty_h264_common::prof::Stage::Total as usize].0.max(1e-9);
let first = rusty_h264_common::prof::Stage::Total as usize + 1;
if snap[first..].iter().all(|s| s.1 == 0) {
return; }
#[cfg(feature = "profile")]
{
let mc = rusty_h264_common::inter::mcstats::snapshot_cycles();
if !mc.is_empty() {
let tot: u64 = mc.iter().map(|r| r.3).sum();
eprintln!("--- MC census (size x phase), by CYCLES [total {tot} cyc] ---");
let mut rows = mc;
rows.sort_by_key(|r| std::cmp::Reverse(r.3));
for (size, phase, n, cyc) in rows.iter().take(24) {
eprintln!(
" {size:<10} {phase:<8} {n:>9} calls {:>5.1}% cycles {:>6.1} cyc/call",
100.0 * *cyc as f64 / tot.max(1) as f64,
*cyc as f64 / (*n).max(1) as f64
);
}
}
}
eprintln!("--- INFO / nested stages (not part of the residue sum) ---");
for i in first..snap.len() {
if snap[i].1 == 0 {
continue;
}
eprintln!(
" {:<20} {:>8.1} ms {:>5.1}% ({} calls)",
rusty_h264_common::prof::name(i),
snap[i].0,
100.0 * snap[i].0 / total,
snap[i].1
);
}
}
#[cfg(feature = "census")]
fn twin_census_report() {
eprintln!();
eprintln!("=== PER-TWIN CALL CENSUS (scalar vs kernel) ===");
eprintln!(
"{:<38} {:>12} {:>12} {:>12} {:>9} {:>9}",
"twin", "wide(AVX2)", "base(SSE2)", "SCALAR", "kernel%", "scalar%"
);
let (mut tk, mut ts) = (0u64, 0u64);
rusty_h264_common::census::each(&mut |name, wide, base, scalar| {
let kern = wide + base;
let tot = kern + scalar;
tk += kern;
ts += scalar;
if tot == 0 {
eprintln!("{name:<38} {:>12} {:>12} {:>12} {:>9} {:>9}", 0, 0, 0, "n/a", "n/a");
return;
}
eprintln!(
"{name:<38} {wide:>12} {base:>12} {scalar:>12} {:>8.2}% {:>8.2}%",
100.0 * kern as f64 / tot as f64,
100.0 * scalar as f64 / tot as f64
);
});
let tot = tk + ts;
if tot > 0 {
eprintln!(
"{:<38} {:>12} {:>12} {:>12} {:>8.2}% {:>8.2}%",
"ALL TWINS", "", tk, ts,
100.0 * tk as f64 / tot as f64,
100.0 * ts as f64 / tot as f64
);
}
}