use std::fs;
use broadcast_common::Parse;
use mpeg_ps::SystemHeader;
use mpeg_ps::program_stream;
fn fixture() -> Vec<u8> {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../fixtures/mpeg-ps/ffmpeg-mpeg2-ps.mpg"
);
fs::read(path).expect("fixture ffmpeg-mpeg2-ps.mpg must be present")
}
#[test]
fn real_fixture_walk() {
let data = fixture();
let (packs, trailing) = program_stream::parse_all_packs(&data).unwrap();
assert!(
packs.len() >= 7,
"expected at least 7 packs, got {}",
packs.len()
);
assert!(
trailing.is_empty(),
"unexpected trailing bytes: {}",
trailing.len()
);
let sh: &SystemHeader = packs[0]
.system_header
.as_ref()
.expect("first pack must have a system header");
assert!(sh.rate_bound > 0, "rate_bound should be positive");
assert!(sh.audio_bound > 0, "audio_bound should be positive");
assert!(sh.video_bound > 0, "video_bound should be positive");
assert!(
!sh.std_buffer_bounds.is_empty(),
"should have P-STD buffer bounds"
);
let mut offset = 0usize;
let mut scr_ticks_prev = 0u64;
for (i, pack) in packs.iter().enumerate() {
let ticks = pack.pack_header.scr.ticks();
assert!(
ticks >= scr_ticks_prev,
"pack {i}: SCR went backwards ({ticks} < {scr_ticks_prev})"
);
scr_ticks_prev = ticks;
assert!(
pack.pack_header.program_mux_rate > 0,
"pack {i}: mux_rate is zero"
);
assert_eq!(
pack.pack_header.program_mux_rate, sh.rate_bound,
"pack {i}: program_mux_rate must match system_header.rate_bound"
);
let orig_bytes = &data[offset..offset + pack.pack_header.serialized_len()];
let mut round = vec![0u8; pack.pack_header.serialized_len()];
pack.pack_header.serialize_into(&mut round).unwrap();
assert_eq!(
&round[..],
orig_bytes,
"pack {i}: pack_header round-trip mismatch"
);
offset += pack.pack_header.serialized_len()
+ pack
.system_header
.as_ref()
.map_or(0, |sh| sh.serialized_len())
+ pack
.pes_packets
.iter()
.map(|p| p.serialized_len())
.sum::<usize>();
}
{
let sh_offset = packs[0].pack_header.serialized_len();
let sh = packs[0].system_header.as_ref().unwrap();
let sh_len = sh.serialized_len();
let orig_sh = &data[sh_offset..sh_offset + sh_len];
let mut round = vec![0u8; sh_len];
sh.serialize_into(&mut round).unwrap();
assert_eq!(&round[..], orig_sh, "system_header round-trip mismatch");
}
{
let mut sh_offset = 0usize;
for p in &packs {
sh_offset += p.pack_header.serialized_len();
if p.system_header.is_some() {
break;
}
}
let sh_bytes = &data[sh_offset..];
let parsed = SystemHeader::parse(sh_bytes).unwrap();
assert_eq!(&parsed, packs[0].system_header.as_ref().unwrap());
}
}
use broadcast_common::Serialize;
use mpeg_ps::ProgramStreamMap;
#[test]
fn psm_unit_round_trip() {
use mpeg_ps::EsMapEntry;
let entries = vec![EsMapEntry {
stream_type: 0x02, elementary_stream_id: 0xE0,
stream_id_extension: None,
descriptors: &[0x0A, 0x04, b'H', b'E', b'L', b'L'],
}];
let psm = ProgramStreamMap {
current_next_indicator: true,
single_extension_stream_flag: false,
version: 3,
program_stream_info: &[],
elementary_stream_map: entries,
crc: 0,
};
let mut buf = vec![0u8; psm.serialized_len()];
psm.serialize_into(&mut buf).unwrap();
let parsed = ProgramStreamMap::parse(&buf).unwrap();
assert!(parsed.current_next_indicator);
assert_eq!(parsed.version, 3);
assert_eq!(parsed.elementary_stream_map.len(), 1);
assert_eq!(
parsed.elementary_stream_map[0].descriptors,
&[0x0A, 0x04, b'H', b'E', b'L', b'L'],
);
let mut out2 = vec![0u8; parsed.serialized_len()];
parsed.serialize_into(&mut out2).unwrap();
assert_eq!(&out2[..], &buf[..]);
}
fn pack_offsets(data: &[u8]) -> Vec<usize> {
let mut offsets = Vec::new();
let mut pos = 0usize;
while data.len() - pos >= 4 {
let (pack, consumed) = program_stream::parse_pack(&data[pos..]).unwrap();
if pack.is_none() {
break;
}
offsets.push(pos);
pos += consumed;
}
offsets
}
#[test]
fn scan_packs_resyncs_after_a_corrupt_pack_and_reports_it() {
let clean = fixture();
let offsets = pack_offsets(&clean);
assert!(offsets.len() >= 7);
let (clean_packs, _) = program_stream::parse_all_packs(&clean).unwrap();
let scan = program_stream::scan_packs(&clean);
assert_eq!(scan.packs.len(), clean_packs.len());
assert!(scan.skipped.is_empty());
let mut bad = clean.clone();
bad[offsets[2]] = 0xFF;
assert!(program_stream::parse_all_packs(&bad).is_err());
let scan = program_stream::scan_packs(&bad);
assert_eq!(scan.packs.len(), clean_packs.len() - 1);
assert_eq!(scan.skipped.len(), 1);
assert_eq!(scan.skipped[0].offset, offsets[2]);
assert_eq!(scan.skipped[0].len, offsets[3] - offsets[2]);
assert!(matches!(
scan.skipped[0].error,
mpeg_ps::Error::BadPackStartCode(0xFF00_01BA)
));
let hdr_len = clean_packs[4].pack_header.header_len();
let pes_at = offsets[4] + hdr_len;
assert_eq!(&clean[pes_at..pes_at + 3], &[0x00, 0x00, 0x01]);
let mut bad2 = clean.clone();
bad2[pes_at] = 0xFF;
let scan = program_stream::scan_packs(&bad2);
assert_eq!(scan.packs.len(), clean_packs.len());
assert!(scan.packs[4].pes_packets.is_empty());
assert_eq!(scan.skipped.len(), 1);
assert_eq!(scan.skipped[0].offset, pes_at);
assert_eq!(scan.skipped[0].len, offsets[5] - pes_at);
let scan = program_stream::scan_packs(&[0x55; 64]);
assert!(scan.packs.is_empty());
assert_eq!(scan.skipped.len(), 1);
assert_eq!(scan.skipped[0].len, 64);
assert!(scan.remaining.is_empty());
}