mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-29 20:47:00 -06:00
- hand-built CAVLC and CABAC bitstreams (test-only CABAC encoder) with I_PCM and P_Skip macroblocks exercising long-term references, MMCO 1-6, frame_num gaps and reference list modification (idc 0 and 2); expected output is known exactly and cross-checked with ffmpeg when available - avcC configuration + length-prefixed samples with pts round trip - corrupted / truncated / garbage input never panics or deadlocks (single- and multi-threaded) - Decoder::stats() coverage counters and an ignored coverage report test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
4.0 KiB
Rust
121 lines
4.0 KiB
Rust
//! avcC / length-prefixed input and resilience against damaged streams.
|
|
|
|
mod common;
|
|
|
|
use filmcraft_bitstream::annexb_nals;
|
|
use filmcraft_h264::Decoder;
|
|
|
|
/// Build an avcC record and length-prefixed samples from an Annex-B stream.
|
|
fn to_avcc(data: &[u8]) -> (Vec<u8>, Vec<Vec<u8>>) {
|
|
let mut sps = Vec::new();
|
|
let mut pps = Vec::new();
|
|
let mut samples = Vec::new();
|
|
for au in common::split_access_units(data) {
|
|
let mut sample = Vec::new();
|
|
for nal in annexb_nals(au) {
|
|
match nal[0] & 0x1f {
|
|
7 => sps.push(nal.to_vec()),
|
|
8 => pps.push(nal.to_vec()),
|
|
_ => {
|
|
sample.extend((nal.len() as u32).to_be_bytes());
|
|
sample.extend_from_slice(nal);
|
|
}
|
|
}
|
|
}
|
|
if !sample.is_empty() {
|
|
samples.push(sample);
|
|
}
|
|
}
|
|
sps.dedup();
|
|
pps.dedup();
|
|
let s0 = &sps[0];
|
|
let mut avcc = vec![1, s0[1], s0[2], s0[3], 0xff, 0xe0 | sps.len() as u8];
|
|
for s in &sps {
|
|
avcc.extend((s.len() as u16).to_be_bytes());
|
|
avcc.extend_from_slice(s);
|
|
}
|
|
avcc.push(pps.len() as u8);
|
|
for p in &pps {
|
|
avcc.extend((p.len() as u16).to_be_bytes());
|
|
avcc.extend_from_slice(p);
|
|
}
|
|
(avcc, samples)
|
|
}
|
|
|
|
#[test]
|
|
fn avcc_input_matches_reference() {
|
|
for name in ["main_cabac_b", "baseline_qcif"] {
|
|
let f = common::fixture(name);
|
|
let Some((h264, yuv)) = common::ensure(f) else { return };
|
|
let (avcc, samples) = to_avcc(&std::fs::read(h264).unwrap());
|
|
let mut dec = Decoder::from_avcc(&avcc).unwrap();
|
|
assert_eq!(dec.nal_length_size(), Some(4));
|
|
let mut pics = Vec::new();
|
|
for (i, s) in samples.iter().enumerate() {
|
|
pics.extend(dec.decode(s, i as i64 * 1000).unwrap());
|
|
}
|
|
pics.extend(dec.flush());
|
|
let reference = std::fs::read(yuv).unwrap();
|
|
common::compare(&pics, &reference, f.width as usize, f.height as usize).unwrap();
|
|
// pts are passed through reordering
|
|
let mut pts: Vec<i64> = pics.iter().map(|p| p.pts).collect();
|
|
pts.sort();
|
|
assert_eq!(pts, (0..samples.len() as i64).map(|i| i * 1000).collect::<Vec<_>>());
|
|
let p0 = &pics[0];
|
|
assert!(p0.key);
|
|
assert_eq!((p0.y_stride, p0.uv_stride), (f.width as usize, f.width as usize / 2));
|
|
}
|
|
}
|
|
|
|
fn xorshift(seed: &mut u64) -> u64 {
|
|
*seed ^= *seed << 13;
|
|
*seed ^= *seed >> 7;
|
|
*seed ^= *seed << 17;
|
|
*seed
|
|
}
|
|
|
|
/// Damaged streams must never panic or hang; errors are fine.
|
|
#[test]
|
|
fn corrupted_streams_do_not_panic() {
|
|
for name in ["main_cabac_b", "baseline_cif_noise", "cavlc_b", "bpyramid"] {
|
|
let f = common::fixture(name);
|
|
let Some((h264, _)) = common::ensure(f) else { return };
|
|
let data = std::fs::read(h264).unwrap();
|
|
let mut seed = 0x1234_5678_9abc_def0u64 ^ name.len() as u64;
|
|
for round in 0..12 {
|
|
let mut d = data.clone();
|
|
// flip bytes after the parameter sets
|
|
for _ in 0..(1 + round * 3) {
|
|
let pos = 64 + (xorshift(&mut seed) as usize) % (d.len() - 64);
|
|
d[pos] ^= 1 << (xorshift(&mut seed) % 8);
|
|
}
|
|
if round % 4 == 3 {
|
|
d.truncate(d.len() * 2 / 3);
|
|
}
|
|
for threads in [1, 4] {
|
|
let mut dec = Decoder::with_threads(threads);
|
|
for au in common::split_access_units(&d) {
|
|
let _ = dec.decode(au, 0);
|
|
}
|
|
let _ = dec.flush();
|
|
let _ = dec.take_error();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn garbage_input_is_rejected_gracefully() {
|
|
let mut seed = 42u64;
|
|
for len in [0usize, 1, 5, 100, 5000] {
|
|
let mut g: Vec<u8> = (0..len).map(|_| xorshift(&mut seed) as u8).collect();
|
|
if len > 10 {
|
|
g[..4].copy_from_slice(&[0, 0, 0, 1]);
|
|
}
|
|
let mut dec = Decoder::new();
|
|
let _ = dec.decode(&g, 0);
|
|
let _ = dec.flush();
|
|
}
|
|
assert!(Decoder::from_avcc(&[1, 2, 3]).is_err());
|
|
}
|