#[cfg(feature = "bench-alloc")]
#[global_allocator]
static GLOBAL_ALLOC: rusty_alloc_api::RustyAlloc = rusty_alloc_api::RustyAlloc;
use std::io::Write;
fn main() {
let args: Vec<String> = std::env::args().collect();
let mut positional: Vec<&String> = Vec::new();
let mut skip_next = false;
for a in args.iter().skip(1) {
if skip_next {
skip_next = false;
continue;
}
if a == "--isa" {
skip_next = true; continue;
}
if a.starts_with("--") {
continue;
}
positional.push(a);
}
if positional.len() < 2 {
eprintln!("usage: rusty_h265 <in.bit> <out.yuv> [--headers-only] [--pipe] [--isa avx2|sse41|baseline]");
std::process::exit(2);
}
let (in_path, out_path) = (positional[0].clone(), positional[1].clone());
let data = match std::fs::read(&in_path) {
Ok(d) => d,
Err(e) => {
eprintln!("read {in_path}: {e}");
std::process::exit(2);
}
};
let headers_only = args.iter().any(|a| a == "--headers-only");
let pipe = args.iter().any(|a| a == "--pipe");
if let Some(i) = args.iter().position(|a| a == "--isa") {
match args.get(i + 1).map(String::as_str) {
Some("sse41") => rusty_h265::accel::force_isa(rusty_h265::accel::Isa::Sse41),
Some("baseline") | Some("sse2") => rusty_h265::accel::force_isa(rusty_h265::accel::Isa::Baseline),
Some("avx2") | None => {}
Some(other) => {
eprintln!("--isa: expected avx2, sse41 or baseline, got {other:?}");
std::process::exit(2);
}
}
}
let verify_sei = args.iter().any(|a| a == "--verify-sei");
let serialize = pipe || out_path != "-";
let mut out: Box<dyn Write> = if pipe {
Box::new(std::io::BufWriter::with_capacity(1 << 20, std::io::stdout()))
} else if out_path == "-" {
Box::new(std::io::sink())
} else {
Box::new(std::io::BufWriter::new(std::fs::File::create(&out_path).expect("create output")))
};
#[cfg(feature = "prof")]
let profiling = std::env::var_os("RH265_PROF").is_some();
#[cfg(feature = "prof")]
if profiling {
rusty_h265::prof::enable();
}
let t0 = std::time::Instant::now();
let mut dec = rusty_h265::Decoder::new();
dec.headers_only = headers_only;
dec.verify_sei = verify_sei;
let mut first_err: Option<String> = None;
for nal in rusty_h265::nal::split_annex_b(&data) {
if let Err(e) = dec.push_nal(nal, None) {
dec.stats.errors += 1;
first_err.get_or_insert_with(|| e.to_string());
}
drain(&mut dec, &mut out, serialize);
}
dec.flush();
let (w, h, bd) = drain(&mut dec, &mut out, serialize);
out.flush().expect("flush output");
let ms = t0.elapsed().as_millis();
let s = dec.stats;
let first_sei = dec.sei_results.first().map_or("none", |r| if r.1 { "ok" } else { "bad" });
let line = format!(
"frames={} errors={} decode_ms={} width={} height={} bit_depth={} pictures={} slices={} skipped_rasl={} generated_refs={} sei_checked={} sei_mismatch={} first_sei={} alloc={} isa={}",
FRAMES.with(|f| f.get()),
s.errors,
ms,
w,
h,
bd,
s.pictures,
s.slices,
s.skipped_rasl,
s.generated_refs,
s.sei_checked,
s.sei_mismatch,
first_sei,
if cfg!(feature = "bench-alloc") { "rusty" } else { "system" },
rusty_h265::accel::describe().rsplit(": ").next().unwrap_or("?"),
);
if pipe {
eprintln!("{line}");
} else {
println!("{line}");
}
#[cfg(feature = "prof")]
if profiling {
eprint!("{}", rusty_h265::prof::report(t0.elapsed().as_nanos() as u64));
}
if std::env::var_os("RH265_SAOBYTES").is_some() {
use std::sync::atomic::Ordering;
let (cp, pl, sp) = (
rusty_h265::filters::SAO_COPIED.load(Ordering::Relaxed),
rusty_h265::filters::SAO_PLANE.load(Ordering::Relaxed),
rusty_h265::filters::SAO_SPANS.load(Ordering::Relaxed),
);
eprintln!("SAO copy: {cp} of {pl} samples ({:.1}%) in {sp} spans, {:.0} samples/span", 100.0 * cp as f64 / pl as f64, cp as f64 / sp.max(1) as f64);
}
if std::env::var_os("RH265_POOL").is_some() {
use std::sync::atomic::Ordering;
let (h, m) = (rusty_h265::decoder::POOL_HIT.load(Ordering::Relaxed), rusty_h265::decoder::POOL_MISS.load(Ordering::Relaxed));
let (ok, sh, fu) = (
rusty_h265::decoder::RECL_OK.load(Ordering::Relaxed),
rusty_h265::decoder::RECL_SHARED.load(Ordering::Relaxed),
rusty_h265::decoder::RECL_FULL.load(Ordering::Relaxed),
);
eprintln!("picture pool: {h} reused, {m} fresh ({:.0}% hit); reclaim: {ok} ok, {sh} still shared, {fu} pool full", 100.0 * h as f64 / (h + m).max(1) as f64);
}
if let Some(e) = first_err {
eprintln!("first error: {e}");
}
if std::env::var_os("RH265_CENSUS").is_some() {
eprintln!("{}", rusty_h265::accel::describe());
if !rusty_h265::accel::census::ALWAYS {
eprintln!(
"census WARNING: built without `--features census`. Counters on per-bin, per-block and per-edge paths are compile-time gated and will read 0 here. Rebuild with `--features census` before believing any zero below."
);
}
for (name, v) in rusty_h265::accel::census::snapshot() {
eprintln!("census {name} = {v}");
}
}
if FRAMES.with(|f| f.get()) == 0 || s.errors > 0 {
std::process::exit(1);
}
}
thread_local! {
static FRAMES: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
fn drain(dec: &mut rusty_h265::Decoder, out: &mut impl Write, serialize: bool) -> (usize, usize, u8) {
let mut geom = (0, 0, 0);
let mut buf = Vec::new();
while let Ok(frame) = dec.next_frame() {
if serialize {
buf.clear();
frame.write_yuv(&mut buf);
out.write_all(&buf).expect("write output");
}
geom = (frame.width, frame.height, frame.bit_depth());
FRAMES.with(|f| f.set(f.get() + 1));
}
geom
}