use std::{fs, path::PathBuf};
fn fixture_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("fixtures")
.join("ts")
.join("h264_aac.ts")
}
fn load_fixture() -> Vec<u8> {
let path = fixture_path();
fs::read(&path).unwrap_or_else(|e| panic!("cannot read fixture {path:?}: {e}"))
}
#[test]
fn reconstruct_pes_video_pid_0100() {
let ts = load_fixture();
let aus = ts_fix::pes::reconstruct_access_units(&ts, &[0x0100, 0x0101]);
let video: Vec<_> = aus.iter().filter(|au| au.pid == 0x0100).collect();
let audio: Vec<_> = aus.iter().filter(|au| au.pid == 0x0101).collect();
assert_eq!(video.len(), 75, "expected 75 access units on PID 0x0100");
assert_eq!(audio.len(), 14, "expected 14 access units on PID 0x0101");
}
#[test]
fn all_video_aus_have_pts() {
let ts = load_fixture();
let aus = ts_fix::pes::reconstruct_access_units(&ts, &[0x0100]);
assert!(!aus.is_empty(), "expected at least one AU on PID 0x0100");
for au in &aus {
assert!(
au.pts.is_some(),
"PID 0x0100 AU missing PTS: pts={:?}",
au.pts
);
}
}
#[test]
fn all_audio_aus_have_pts() {
let ts = load_fixture();
let aus = ts_fix::pes::reconstruct_access_units(&ts, &[0x0101]);
assert!(!aus.is_empty(), "expected at least one AU on PID 0x0101");
for au in &aus {
assert!(
au.pts.is_some(),
"PID 0x0101 AU missing PTS: pts={:?}",
au.pts
);
}
}
#[test]
fn every_au_starts_with_pes_start_code() {
let ts = load_fixture();
let aus = ts_fix::pes::reconstruct_access_units(&ts, &[0x0100, 0x0101]);
assert!(!aus.is_empty(), "expected at least one access unit");
for au in &aus {
assert!(
au.data.len() >= 4,
"AU data too short ({} bytes) for PES start code",
au.data.len()
);
assert_eq!(
au.data[0..3],
[0x00, 0x00, 0x01],
"PID {:#06x} AU does not start with PES start code",
au.pid
);
assert!(au.data[3] != 0, "PID {:#06x} AU has zero stream_id", au.pid);
}
}
#[test]
fn video_dts_non_decreasing() {
let ts = load_fixture();
let aus = ts_fix::pes::reconstruct_access_units(&ts, &[0x0100]);
let mut prev_dts: Option<u64> = None;
for au in &aus {
if let Some(dts) = au.dts {
if let Some(prev) = prev_dts {
assert!(dts >= prev, "DTS regressed: {dts} < {prev}");
}
prev_dts = Some(dts);
}
}
}
#[test]
fn identity_passthrough_byte_identical() {
let input = load_fixture();
assert!(!input.is_empty(), "fixture is empty");
assert_eq!(
input.len() % 188,
0,
"fixture length {} is not a multiple of 188",
input.len()
);
let mut engine = ts_fix::TsFix::builder()
.build()
.expect("identity build should not fail");
let mut output: Vec<u8> = Vec::with_capacity(input.len());
for chunk in input.chunks(188) {
engine
.push(chunk, |pkt| {
output.extend_from_slice(pkt);
})
.expect("valid 188-byte packet from fixture");
}
engine.finish(|pkt| {
output.extend_from_slice(pkt);
});
assert_eq!(
output.len(),
input.len(),
"output length {} differs from input length {}",
output.len(),
input.len()
);
assert_eq!(output, input, "identity engine output differs from input");
}