#[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();
if args.len() < 3 {
eprintln!("usage: rusty_h265 <in.bit> <out.yuv> [--headers-only]");
std::process::exit(2);
}
let data = match std::fs::read(&args[1]) {
Ok(d) => d,
Err(e) => {
eprintln!("read {}: {e}", args[1]);
std::process::exit(2);
}
};
let headers_only = args.iter().any(|a| a == "--headers-only");
let verify_sei = args.iter().any(|a| a == "--verify-sei");
let mut out: Box<dyn Write> = if args[2] == "-" {
Box::new(std::io::sink())
} else {
Box::new(std::io::BufWriter::new(std::fs::File::create(&args[2]).expect("create output")))
};
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);
}
dec.flush();
let (w, h, bd) = drain(&mut dec, &mut out);
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" });
println!(
"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 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) -> (usize, usize, u8) {
let mut geom = (0, 0, 0);
let mut buf = Vec::new();
while let Ok(frame) = dec.next_frame() {
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
}