use rusty_h264_decoder::Decoder;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::path::Path;
use std::process::Command;
fn main() {
let dir = std::env::args().nth(1).expect("usage: oracle <dir> (set H264DEC)");
let h264dec = std::env::var("H264DEC").unwrap_or_else(|_| {
"C:/Users/talmo/coding/openh264/builddir_rs/codec/console/dec/h264dec.exe".to_string()
});
let tmp = std::env::temp_dir();
let mut entries: Vec<_> = std::fs::read_dir(&dir)
.expect("read dir")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "264"))
.collect();
entries.sort();
std::panic::set_hook(Box::new(|_| {}));
let (mut matched, mut diff, mut rej, mut panic, mut reffail) = (0, 0, 0, 0, 0);
for path in &entries {
let name = path.file_name().unwrap().to_string_lossy().to_string();
let data = std::fs::read(path).expect("read file");
let ref_yuv = match reference_decode(&h264dec, path, &tmp) {
Some(y) if !y.is_empty() => y,
_ => {
reffail += 1;
println!(" REF-FAIL {name}");
continue;
}
};
let ours = catch_unwind(AssertUnwindSafe(|| our_decode_all(&data)));
match ours {
Err(_) => {
panic += 1;
println!(" OURS-PANIC {name}");
}
Ok(Err(e)) => {
rej += 1;
println!(" OURS-REJ {name} -> {e}");
}
Ok(Ok(our_yuv)) => {
if our_yuv == ref_yuv {
matched += 1;
println!(" MATCH {name} ({} bytes)", our_yuv.len());
} else {
diff += 1;
println!(" DIFF {name} {}", describe_diff(&our_yuv, &ref_yuv));
}
}
}
}
let _ = std::panic::take_hook();
println!(
"\n{} files: {matched} MATCH, {diff} DIFF, {rej} ours-rejected, {panic} ours-panicked, {reffail} ref-failed",
entries.len()
);
}
fn reference_decode(h264dec: &str, input: &Path, tmp: &Path) -> Option<Vec<u8>> {
let out = tmp.join(format!(
"oracle_{}.yuv",
input.file_stem().unwrap().to_string_lossy()
));
let _ = std::fs::remove_file(&out);
let status = Command::new(h264dec)
.arg(input)
.arg(&out)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.ok()?;
if !status.success() {
return None;
}
let bytes = std::fs::read(&out).ok()?;
let _ = std::fs::remove_file(&out);
Some(bytes)
}
fn our_decode_all(stream: &[u8]) -> Result<Vec<u8>, String> {
let frames = Decoder::new().decode_stream(stream).map_err(|e| e.to_string())?;
let mut out = Vec::new();
for f in &frames {
out.extend_from_slice(&f.y);
out.extend_from_slice(&f.u);
out.extend_from_slice(&f.v);
}
Ok(out)
}
fn describe_diff(ours: &[u8], reference: &[u8]) -> String {
if ours.len() != reference.len() {
return format!("size ours={} ref={}", ours.len(), reference.len());
}
let mut ndiff = 0usize;
let mut maxd = 0i32;
let mut first = None;
for (i, (a, b)) in ours.iter().zip(reference).enumerate() {
if a != b {
ndiff += 1;
maxd = maxd.max((*a as i32 - *b as i32).abs());
if first.is_none() {
first = Some(i);
}
}
}
format!(
"{ndiff}/{} bytes differ (max |Δ|={maxd}), first @ {}",
ours.len(),
first.unwrap_or(0)
)
}