use super::codec::startcode::find_start_code;
const PACK_HEADER_ID: u8 = 0xBA;
const SYSTEM_HEADER_ID: u8 = 0xBB;
const PROGRAM_END_ID: u8 = 0xB9;
const PRIVATE_STREAM_1: u8 = 0xBD;
const MAX_PS_BUFFER: usize = 4 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct PsPacket {
pub stream_id: u8,
pub sub_stream_id: Option<u8>,
pub pts: Option<u64>,
pub dts: Option<u64>,
pub data: Vec<u8>,
}
pub const DVD_VIDEO_PID: u16 = 0xE0;
pub fn dvd_audio_pid(sub_stream_id: u8) -> Option<u16> {
match sub_stream_id {
0x80..=0x8F | 0xA0..=0xA7 => Some(0xBD00 | sub_stream_id as u16),
_ => None,
}
}
pub fn dvd_subtitle_pid(sub_stream_id: u8) -> Option<u16> {
match sub_stream_id {
0x20..=0x3F => Some(sub_stream_id as u16),
_ => None,
}
}
impl PsPacket {
pub fn dvd_pid(&self) -> Option<u16> {
match self.stream_id {
0xE0..=0xEF => Some(DVD_VIDEO_PID),
0xBD => {
let sub = self.sub_stream_id?;
dvd_audio_pid(sub).or_else(|| dvd_subtitle_pid(sub))
}
_ => None,
}
}
}
pub struct PsDemuxer {
buffer: Vec<u8>,
}
impl Default for PsDemuxer {
fn default() -> Self {
Self::new()
}
}
impl PsDemuxer {
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(64 * 1024),
}
}
pub fn feed(&mut self, data: &[u8]) -> Vec<PsPacket> {
self.buffer.extend_from_slice(data);
self.extract_packets(false)
}
pub fn flush(&mut self) -> Vec<PsPacket> {
let packets = self.extract_packets(true);
self.buffer.clear();
packets
}
fn extract_packets(&mut self, flushing: bool) -> Vec<PsPacket> {
let mut packets = Vec::with_capacity(4);
let mut pos = 0;
while let Some(sc) = find_start_code(&self.buffer, pos) {
if sc + 3 >= self.buffer.len() {
break;
}
let code = self.buffer[sc + 3];
match code {
PROGRAM_END_ID => {
pos = sc + 4;
}
PACK_HEADER_ID => {
if sc + 14 > self.buffer.len() {
break; }
let stuffing = (self.buffer[sc + 13] & 0x07) as usize;
let pack_len = 14 + stuffing;
if sc + pack_len > self.buffer.len() {
break;
}
pos = sc + pack_len;
}
SYSTEM_HEADER_ID => {
if sc + 6 > self.buffer.len() {
break;
}
let header_len =
((self.buffer[sc + 4] as usize) << 8) | self.buffer[sc + 5] as usize;
let total = 6 + header_len;
if sc + total > self.buffer.len() {
break;
}
pos = sc + total;
}
id if is_pes_stream_id(id) => {
if sc + 6 > self.buffer.len() {
break;
}
let pes_packet_len =
((self.buffer[sc + 4] as usize) << 8) | self.buffer[sc + 5] as usize;
let end = if pes_packet_len == 0 {
match find_ps_boundary(&self.buffer, sc + 4) {
Some(next) => next,
None if flushing => self.buffer.len(),
None => {
if self.buffer.len() - sc > MAX_PS_BUFFER {
self.buffer.len()
} else {
break; }
}
}
} else {
let e = sc + 6 + pes_packet_len;
if e > self.buffer.len() {
break; }
e
};
if let Some(pkt) = parse_pes_packet(&self.buffer[sc..end]) {
packets.push(pkt);
}
pos = end;
}
_ => {
pos = sc + 4;
}
}
}
if pos > 0 {
self.buffer.drain(..pos);
}
packets
}
}
fn find_ps_boundary(data: &[u8], from: usize) -> Option<usize> {
let mut pos = from;
while let Some(sc) = find_start_code(data, pos) {
if sc + 3 >= data.len() {
return None;
}
let id = data[sc + 3];
if id == PACK_HEADER_ID
|| id == SYSTEM_HEADER_ID
|| id == PROGRAM_END_ID
|| is_pes_stream_id(id)
{
return Some(sc);
}
pos = sc + 4;
}
None
}
fn is_pes_stream_id(id: u8) -> bool {
matches!(id, 0xBD..=0xEF)
}
fn parse_pes_packet(data: &[u8]) -> Option<PsPacket> {
if data.len() < 6 {
return None;
}
if data[0] != 0x00 || data[1] != 0x00 || data[2] != 0x01 {
return None;
}
let stream_id = data[3];
if stream_id == 0xBE {
return None;
}
if stream_id == 0xBF {
let payload = if data.len() > 6 { &data[6..] } else { &[] };
return Some(PsPacket {
stream_id,
sub_stream_id: None,
pts: None,
dts: None,
data: payload.to_vec(),
});
}
if data.len() < 9 {
return None;
}
let pts_dts_flags = (data[7] >> 6) & 0x03;
let header_data_len = data[8] as usize;
let header_end = 9 + header_data_len;
if header_end > data.len() {
return None;
}
let mut pts = None;
let mut dts = None;
if pts_dts_flags >= 2 && header_data_len >= 5 && data.len() >= 14 {
pts = Some(parse_pts(&data[9..14]));
}
if pts_dts_flags == 3 && header_data_len >= 10 && data.len() >= 19 {
dts = Some(parse_pts(&data[14..19]));
}
let payload = &data[header_end..];
let (sub_stream_id, es_data) = if stream_id == PRIVATE_STREAM_1 && !payload.is_empty() {
let sub_id = payload[0];
let skip = match sub_id {
0x80..=0x8F => 4, 0xA0..=0xA7 => 7, _ => 1,
};
let start = skip.min(payload.len());
(Some(sub_id), payload[start..].to_vec())
} else {
(None, payload.to_vec())
};
Some(PsPacket {
stream_id,
sub_stream_id,
pts,
dts,
data: es_data,
})
}
fn parse_pts(buf: &[u8]) -> u64 {
debug_assert!(buf.len() >= 5);
let b0 = buf[0] as u64;
let b1 = buf[1] as u64;
let b2 = buf[2] as u64;
let b3 = buf[3] as u64;
let b4 = buf[4] as u64;
((b0 >> 1) & 0x07) << 30 | b1 << 22 | (b2 >> 1) << 15 | b3 << 7 | b4 >> 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_pack_header() {
let mut demuxer = PsDemuxer::new();
let mut pack = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x01, 0x89, 0xC3, 0xF8, ];
pack.extend_from_slice(&[
0x00, 0x00, 0x01, 0xE0, 0x00, 0x08, 0x80, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, ]);
let packets = demuxer.feed(&pack);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].stream_id, 0xE0);
assert_eq!(packets[0].data, vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE]);
}
#[test]
fn pack_header_with_stuffing() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x01, 0x89, 0xC3,
0xFB, 0xFF, 0xFF, 0xFF, ];
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xC0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x11, 0x22, ]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].stream_id, 0xC0);
assert_eq!(packets[0].data, vec![0x11, 0x22]);
}
#[test]
fn pes_header_with_pts() {
let mut demuxer = PsDemuxer::new();
let pts_bytes = encode_pts(90000, 0x20);
let mut data = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0D, 0x80, 0x80, 0x05, ];
data.extend_from_slice(&pts_bytes);
data.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x00]);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].stream_id, 0xE0);
assert_eq!(packets[0].pts, Some(90000));
assert!(packets[0].dts.is_none());
assert_eq!(packets[0].data, vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00]);
}
#[test]
fn pes_header_with_pts_and_dts() {
let mut demuxer = PsDemuxer::new();
let pts_bytes = encode_pts(180000, 0x30); let dts_bytes = encode_pts(90000, 0x10);
let mut data = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x11, 0x80, 0xC0, 0x0A, ];
data.extend_from_slice(&pts_bytes);
data.extend_from_slice(&dts_bytes);
data.extend_from_slice(&[0xCA, 0xFE]);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].pts, Some(180000));
assert_eq!(packets[0].dts, Some(90000));
}
#[test]
fn private_stream_1_ac3_substream() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBD, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0xAA, 0xBB, 0xCC, 0xDD, ];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].stream_id, 0xBD);
assert_eq!(packets[0].sub_stream_id, Some(0x80));
assert_eq!(packets[0].data, vec![0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn private_stream_1_dts_substream() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBD, 0x00, 0x09, 0x80, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x11, 0x22,
];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].sub_stream_id, Some(0x88));
assert_eq!(packets[0].data, vec![0x11, 0x22]);
}
#[test]
fn private_stream_1_subtitle_substream() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBD, 0x00, 0x06, 0x80, 0x00, 0x00,
0x20, 0xFF, 0xFE,
];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].sub_stream_id, Some(0x20));
}
#[test]
fn private_stream_1_lpcm_substream() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBD, 0x00, 0x0C, 0x80, 0x00, 0x00, 0xA0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, ];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].sub_stream_id, Some(0xA0));
assert_eq!(packets[0].data, vec![0x01, 0x02]);
}
#[test]
fn incremental_feed() {
let mut demuxer = PsDemuxer::new();
let mut full = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x06, 0x80, 0x00, 0x00, 0xAA, 0xBB, 0xCC,
];
full.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let mid = full.len() / 2;
let p1 = demuxer.feed(&full[..mid]);
assert!(p1.is_empty(), "first half should not produce packets");
let p2 = demuxer.feed(&full[mid..]);
assert_eq!(p2.len(), 1);
assert_eq!(p2[0].data, vec![0xAA, 0xBB, 0xCC]);
}
#[test]
fn flush_emits_trailing_unbounded_video_pes() {
let mut demuxer = PsDemuxer::new();
let data = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD,
];
let fed = demuxer.feed(&data);
assert!(fed.is_empty(), "unbounded PES not emitted until delimited");
let flushed = demuxer.flush();
assert_eq!(flushed.len(), 1, "flush emits the trailing PES");
assert_eq!(flushed[0].stream_id, 0xE0);
assert_eq!(flushed[0].data, vec![0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn multiple_pes_packets() {
let mut demuxer = PsDemuxer::new();
let mut data = Vec::new();
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x11, 0x22,
]);
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xC0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x33, 0x44,
]);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 2);
assert_eq!(packets[0].stream_id, 0xE0);
assert_eq!(packets[1].stream_id, 0xC0);
}
#[test]
fn unbounded_video_pes_not_cut_by_embedded_start_codes() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00, 0x00, ];
let payload = [
0x00, 0x00, 0x01, 0xB3, 0x11, 0x22, 0x00, 0x00, 0x01, 0x00, 0x33, 0x44, 0x00, 0x00, 0x01, 0x01, 0x55, 0x66,
];
data.extend_from_slice(&payload);
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xB9]);
let packets = demuxer.feed(&data);
assert_eq!(packets.len(), 1, "one PES, not several payload fragments");
assert_eq!(packets[0].stream_id, 0xE0);
assert_eq!(packets[0].data, payload.to_vec());
}
#[test]
fn unbounded_video_pes_waits_for_boundary() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00, 0x00];
data.extend_from_slice(&[0x00, 0x00, 0x01, 0x00, 0xAA, 0xBB]); let packets = demuxer.feed(&data);
assert!(packets.is_empty(), "no PS boundary yet → hold the PES");
}
#[test]
fn unbounded_video_pes_buffer_is_bounded() {
let mut demuxer = PsDemuxer::new();
let header = vec![0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00, 0x00];
let packets = demuxer.feed(&header);
assert!(packets.is_empty());
let chunk = vec![0x55u8; 1024 * 1024];
let mut emitted = 0;
for _ in 0..(MAX_PS_BUFFER / chunk.len() + 4) {
emitted += demuxer.feed(&chunk).len();
}
assert!(
demuxer.buffer.len() <= MAX_PS_BUFFER + chunk.len(),
"buffer grew to {} (cap {})",
demuxer.buffer.len(),
MAX_PS_BUFFER
);
assert!(emitted >= 1, "over-cap unbounded PES is force-flushed");
}
#[test]
fn pts_zero() {
let pts = parse_pts(&encode_pts(0, 0x20));
assert_eq!(pts, 0);
}
#[test]
fn pts_large_value() {
let val: u64 = (1 << 32) - 1; let encoded = encode_pts(val, 0x20);
let decoded = parse_pts(&encoded);
assert_eq!(decoded, val);
}
fn mk(stream_id: u8, sub: Option<u8>) -> PsPacket {
PsPacket {
stream_id,
sub_stream_id: sub,
pts: None,
dts: None,
data: vec![0xAA],
}
}
#[test]
fn dvd_pid_matches_scanner_assignment() {
assert_eq!(mk(0xE0, None).dvd_pid(), Some(DVD_VIDEO_PID));
assert_eq!(mk(0xBD, Some(0x80)).dvd_pid(), Some(0xBD80)); assert_eq!(mk(0xBD, Some(0x81)).dvd_pid(), Some(0xBD81)); assert_eq!(mk(0xBD, Some(0x88)).dvd_pid(), Some(0xBD88)); assert_eq!(mk(0xBD, Some(0xA0)).dvd_pid(), Some(0xBDA0)); assert_eq!(mk(0xBD, Some(0x20)).dvd_pid(), Some(0x20));
assert_eq!(mk(0xBD, Some(0x21)).dvd_pid(), Some(0x21));
assert_eq!(mk(0xC0, None).dvd_pid(), None);
assert_eq!(mk(0xBF, None).dvd_pid(), None);
assert_eq!(mk(0xBD, Some(0x10)).dvd_pid(), None);
}
#[test]
fn mixed_codec_audio_does_not_collide() {
let ac3 = mk(0xBD, Some(0x80)).dvd_pid().unwrap();
let dts = mk(0xBD, Some(0x88)).dvd_pid().unwrap();
let lpcm = mk(0xBD, Some(0xA0)).dvd_pid().unwrap();
assert_ne!(ac3, dts, "AC-3 and DTS must not collide");
assert_ne!(ac3, lpcm, "AC-3 and LPCM must not collide");
assert_ne!(dts, lpcm, "DTS and LPCM must not collide");
let pid_to_track: Vec<(u16, usize)> = vec![
(DVD_VIDEO_PID, 0),
(dvd_audio_pid(0x80).unwrap(), 1),
(dvd_audio_pid(0x88).unwrap(), 2),
(dvd_audio_pid(0xA0).unwrap(), 3),
(dvd_subtitle_pid(0x20).unwrap(), 4),
];
let route = |p: PsPacket| -> Option<usize> {
let pid = p.dvd_pid()?;
pid_to_track
.iter()
.find(|(x, _)| *x == pid)
.map(|(_, t)| *t)
};
assert_eq!(route(mk(0xE0, None)), Some(0));
assert_eq!(route(mk(0xBD, Some(0x80))), Some(1)); assert_eq!(route(mk(0xBD, Some(0x88))), Some(2)); assert_eq!(route(mk(0xBD, Some(0xA0))), Some(3)); assert_eq!(route(mk(0xBD, Some(0x20))), Some(4)); }
#[test]
fn subtitle_does_not_collide_with_audio_track() {
let audio0 = mk(0xBD, Some(0x80)).dvd_pid().unwrap(); let sub0 = mk(0xBD, Some(0x20)).dvd_pid().unwrap(); assert_ne!(
audio0, sub0,
"subtitle sub-id 0x20 must NOT map to the audio PID"
);
let pid_to_track: Vec<(u16, usize)> = vec![
(DVD_VIDEO_PID, 0),
(dvd_audio_pid(0x80).unwrap(), 1),
(dvd_audio_pid(0x81).unwrap(), 2),
(dvd_subtitle_pid(0x20).unwrap(), 3),
(dvd_subtitle_pid(0x21).unwrap(), 4),
];
let route = |p: PsPacket| -> Option<usize> {
let pid = p.dvd_pid()?;
pid_to_track
.iter()
.find(|(x, _)| *x == pid)
.map(|(_, t)| *t)
};
assert_eq!(route(mk(0xE0, None)), Some(0));
assert_eq!(route(mk(0xBD, Some(0x80))), Some(1));
assert_eq!(route(mk(0xBD, Some(0x81))), Some(2));
assert_eq!(route(mk(0xBD, Some(0x20))), Some(3)); assert_eq!(route(mk(0xBD, Some(0x21))), Some(4)); }
fn encode_pts(pts: u64, marker_prefix: u8) -> [u8; 5] {
let mut buf = [0u8; 5];
buf[0] = marker_prefix | (((pts >> 30) as u8) & 0x07) << 1 | 1;
buf[1] = ((pts >> 22) & 0xFF) as u8;
buf[2] = (((pts >> 15) & 0x7F) as u8) << 1 | 1;
buf[3] = ((pts >> 7) & 0xFF) as u8;
buf[4] = (((pts) & 0x7F) as u8) << 1 | 1;
buf
}
const PROGRAM_END: [u8; 4] = [0x00, 0x00, 0x01, 0xB9];
#[test]
fn parse_pts_max_33bit() {
let max = (1u64 << 33) - 1;
assert_eq!(parse_pts(&encode_pts(max, 0x20)), max);
}
#[test]
fn parse_pts_ignores_marker_bits_in_value() {
let v = 0x1_2345_6789u64 & ((1 << 33) - 1);
let a = encode_pts(v, 0x20);
let mut b = a;
b[0] |= 0x01;
b[2] |= 0x01;
b[4] |= 0x01;
assert_eq!(parse_pts(&a), v);
assert_eq!(parse_pts(&b), v);
}
#[test]
fn pack_header_waits_for_full_14_bytes() {
let mut demuxer = PsDemuxer::new();
let partial = vec![0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01];
let p = demuxer.feed(&partial);
assert!(p.is_empty());
let mut rest = vec![0x01, 0x89, 0xC3, 0xF8]; rest.extend_from_slice(&[
0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00, 0xAB, 0xCD,
]);
rest.extend_from_slice(&PROGRAM_END);
let p2 = demuxer.feed(&rest);
assert_eq!(p2.len(), 1, "PES after a now-complete pack header parses");
assert_eq!(p2[0].data, vec![0xAB, 0xCD]);
}
#[test]
fn pack_header_stuffing_length_consumed() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBA, 0x44, 0x00, 0x04, 0x00, 0x04, 0x01, 0x01, 0x89, 0xC3,
0xFD, 0x00, 0x00, 0x01, 0xE0, 0xDE,
];
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x11, 0x22,
]);
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1, "exactly the real PES; the decoy was skipped");
assert_eq!(p[0].data, vec![0x11, 0x22]);
}
#[test]
fn system_header_length_skipped() {
let mut demuxer = PsDemuxer::new();
let body = [0x00, 0x00, 0x01, 0xE0, 0xFF, 0xFF]; let mut data = vec![0x00, 0x00, 0x01, 0xBB];
data.extend_from_slice(&(body.len() as u16).to_be_bytes());
data.extend_from_slice(&body);
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xC0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x33, 0x44,
]);
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(
p.len(),
1,
"decoy bytes inside system header not parsed as PES"
);
assert_eq!(p[0].stream_id, 0xC0);
assert_eq!(p[0].data, vec![0x33, 0x44]);
}
#[test]
fn system_header_waits_for_full_body() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![0x00, 0x00, 0x01, 0xBB, 0x00, 0x20]; data.extend_from_slice(&[0xAA; 4]); assert!(demuxer.feed(&data).is_empty());
}
#[test]
fn bounded_pes_waits_for_full_declared_length() {
let mut demuxer = PsDemuxer::new();
let head = vec![0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00];
assert!(demuxer.feed(&head).is_empty());
let p = demuxer.feed(&[0xEE, 0xFF]);
assert_eq!(p.len(), 1);
assert_eq!(p[0].data, vec![0xEE, 0xFF]);
}
#[test]
fn padding_stream_0xbe_is_dropped() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![0x00, 0x00, 0x01, 0xBE, 0x00, 0x04, 0xFF, 0xFF, 0xFF, 0xFF];
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x01, 0x02,
]);
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1, "padding stream dropped; only real PES emitted");
assert_eq!(p[0].stream_id, 0xE0);
}
#[test]
fn private_stream_2_0xbf_has_no_pes_extension() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![0x00, 0x00, 0x01, 0xBF, 0x00, 0x04, 0xDE, 0xAD, 0xBE, 0xEF];
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1);
assert_eq!(p[0].stream_id, 0xBF);
assert_eq!(p[0].pts, None, "0xBF carries no PTS");
assert_eq!(p[0].sub_stream_id, None);
assert_eq!(p[0].data, vec![0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn unknown_start_code_is_skipped_not_parsed() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![0x00, 0x00, 0x01, 0xB0]; data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x9A, 0xBC,
]);
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1);
assert_eq!(p[0].data, vec![0x9A, 0xBC]);
}
#[test]
fn private_stream_1_unknown_subid_skips_one_byte() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBD, 0x00, 0x06, 0x80, 0x00, 0x00, 0x70, 0x55, 0x66,
];
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1);
assert_eq!(p[0].sub_stream_id, Some(0x70));
assert_eq!(p[0].data, vec![0x55, 0x66], "only sub-id byte skipped");
}
#[test]
fn private_stream_1_short_payload_does_not_underflow_skip() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xBD, 0x00, 0x04, 0x80, 0x00, 0x00, 0x80, 0x01, ];
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1);
assert_eq!(p[0].sub_stream_id, Some(0x80));
assert!(
p[0].data.is_empty(),
"clamped skip yields empty ES, no panic"
);
}
#[test]
fn dvd_audio_pid_range_boundaries() {
assert_eq!(dvd_audio_pid(0x80), Some(0xBD80));
assert_eq!(dvd_audio_pid(0x8F), Some(0xBD8F));
assert_eq!(dvd_audio_pid(0xA0), Some(0xBDA0));
assert_eq!(dvd_audio_pid(0xA7), Some(0xBDA7));
assert_eq!(dvd_audio_pid(0x7F), None);
assert_eq!(dvd_audio_pid(0x90), None);
assert_eq!(dvd_audio_pid(0x9F), None);
assert_eq!(dvd_audio_pid(0xA8), None);
}
#[test]
fn dvd_subtitle_pid_range_boundaries() {
assert_eq!(dvd_subtitle_pid(0x20), Some(0x20));
assert_eq!(dvd_subtitle_pid(0x3F), Some(0x3F));
assert_eq!(dvd_subtitle_pid(0x1F), None);
assert_eq!(dvd_subtitle_pid(0x40), None);
}
#[test]
fn dvd_pid_all_video_stream_ids_map_to_video() {
for sid in 0xE0u8..=0xEF {
assert_eq!(
mk(sid, None).dvd_pid(),
Some(DVD_VIDEO_PID),
"stream_id {sid:#04x} must map to video"
);
}
}
#[test]
fn flush_discards_incomplete_bounded_pes() {
let mut demuxer = PsDemuxer::new();
let head = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x0A, 0x80, 0x00, 0x00, 0xAA, 0xBB,
];
assert!(demuxer.feed(&head).is_empty());
let flushed = demuxer.flush();
assert!(
flushed.is_empty(),
"incomplete bounded PES must not be emitted on flush"
);
}
#[test]
fn empty_feed_then_flush_is_empty() {
let mut demuxer = PsDemuxer::new();
assert!(demuxer.feed(&[]).is_empty());
assert!(demuxer.flush().is_empty());
}
#[test]
fn pes_header_data_length_skips_pts_when_flag_unset() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![
0x00, 0x00, 0x01, 0xE0, 0x00, 0x06, 0x80, 0x00, 0x00, 0x21, 0x00, 0x01,
];
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 1);
assert_eq!(p[0].pts, None);
assert_eq!(p[0].data, vec![0x21, 0x00, 0x01]);
}
#[test]
fn unbounded_video_pes_framed_by_next_pes_not_embedded_audio_code() {
let mut demuxer = PsDemuxer::new();
let mut data = vec![0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00, 0x00];
let video_payload = [0x11, 0x00, 0x00, 0x01, 0x00, 0x22]; data.extend_from_slice(&video_payload);
data.extend_from_slice(&[
0x00, 0x00, 0x01, 0xC0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x99, 0x88,
]);
data.extend_from_slice(&PROGRAM_END);
let p = demuxer.feed(&data);
assert_eq!(p.len(), 2, "video PES + audio PES");
assert_eq!(p[0].stream_id, 0xE0);
assert_eq!(
p[0].data, video_payload,
"video ES keeps its embedded start code, stops at the audio PES"
);
assert_eq!(p[1].stream_id, 0xC0);
assert_eq!(p[1].data, vec![0x99, 0x88]);
}
}