use mpeg_ts::ts::{TsPacket, TS_PACKET_SIZE};
use std::fs;
fn fixture(name: &str) -> Vec<u8> {
let subdir = if name == "m6-single.ts" {
"ts"
} else {
"mpeg-ts"
};
fs::read(format!(
"{}/../fixtures/{}/{}",
env!("CARGO_MANIFEST_DIR"),
subdir,
name
))
.unwrap_or_else(|e| panic!("fixture {name} must be present: {e}"))
}
fn af_body(pkt: &[u8]) -> &[u8] {
let af_len = (pkt[4] as usize).min(TS_PACKET_SIZE - 5);
&pkt[5..5 + af_len]
}
fn assert_all_byte_identical(buf: &[u8]) -> (usize, usize) {
assert_eq!(
buf.len() % TS_PACKET_SIZE,
0,
"fixture must be a whole number of 188-byte packets"
);
let mut total = 0usize;
let mut stuffed = 0usize;
for (i, pkt) in buf.chunks_exact(TS_PACKET_SIZE).enumerate() {
assert_eq!(pkt[0], 0x47, "pkt {i}: lost sync");
if (pkt[3] & 0xC0) >> 6 != 0 {
continue;
}
let Some(Ok(af)) = TsPacket::parse(pkt)
.expect("packet parses")
.adaptation_field()
else {
continue;
};
total += 1;
if af.stuffing_len > 0 {
stuffed += 1;
}
let orig = af_body(pkt);
let mut out = vec![0u8; af.serialized_len()];
let n = af.serialize_into(&mut out).expect("AF serialize");
assert_eq!(
&out[..n],
orig,
"pkt {i}: adaptation field NOT byte-identical\n orig: {}\n out: {}",
orig.iter().map(|b| format!("{b:02X}")).collect::<String>(),
out[..n]
.iter()
.map(|b| format!("{b:02X}"))
.collect::<String>(),
);
}
(total, stuffed)
}
#[test]
fn transport_private_data_byte_identical() {
let buf = fixture("af-transport-private-data.ts");
let (total, _) = assert_all_byte_identical(&buf);
let mut tpd = 0usize;
let mut tpd_with_pcr = 0usize;
let mut tpd_no_pcr = 0usize;
for pkt in buf.chunks_exact(TS_PACKET_SIZE) {
let Some(Ok(af)) = TsPacket::parse(pkt).unwrap().adaptation_field() else {
continue;
};
if af.transport_private_data.is_some() {
tpd += 1;
if af.pcr.is_some() {
tpd_with_pcr += 1;
} else {
tpd_no_pcr += 1;
}
}
}
assert_eq!(total, 4, "fixture should hold 4 adaptation fields");
assert!(tpd >= 2, "expected >=2 transport_private_data, got {tpd}");
assert!(tpd_with_pcr >= 1, "expected a tpd+PCR packet");
assert!(tpd_no_pcr >= 1, "expected a tpd-only packet");
}
#[test]
fn pcr_and_stuffing_byte_identical() {
let buf = fixture("af-pcr-stuffing.ts");
let (total, stuffed) = assert_all_byte_identical(&buf);
assert!(total >= 2, "expected >=2 adaptation fields, got {total}");
assert_eq!(
stuffed, total,
"every packet in this fixture is a stuffed adaptation field"
);
let mut pcr_stuffed = 0usize;
let mut max_stuffing = 0usize;
for pkt in buf.chunks_exact(TS_PACKET_SIZE) {
let Some(Ok(af)) = TsPacket::parse(pkt).unwrap().adaptation_field() else {
continue;
};
max_stuffing = max_stuffing.max(af.stuffing_len);
if af.pcr.is_some() && af.stuffing_len > 0 {
pcr_stuffed += 1;
}
}
assert!(
pcr_stuffed >= 1,
"expected a PCR + stuffing adaptation field"
);
assert!(
max_stuffing >= 100,
"expected a large stuffing run (got max {max_stuffing}); proves stuffing is reproduced, not dropped"
);
}
#[test]
fn m6_single_all_adaptation_fields_byte_identical() {
let buf = fixture("m6-single.ts");
let (total, stuffed) = assert_all_byte_identical(&buf);
assert!(
total >= 50,
"m6-single.ts should contain many adaptation fields, got {total}"
);
assert!(
stuffed >= 1,
"m6-single.ts should contain at least one stuffed adaptation field, got {stuffed}"
);
}