use std::collections::{HashMap, VecDeque};
use oxideav_core::{
CodecId, CodecParameters, CodecResolver, Demuxer, Error as CoreError, Packet, ReadSeek,
Result as CoreResult, StreamInfo, TimeBase,
};
use crate::{
PesPacket, PesReassembler, ProgramAssociationTable, ProgramMapTable, PsiSectionAssembler,
StreamType, TsPacket, PAT_PID, TS_PACKET_LEN, TS_SYNC_BYTE,
};
pub fn open(input: Box<dyn ReadSeek>, codecs: &dyn CodecResolver) -> CoreResult<Box<dyn Demuxer>> {
let _ = codecs; MpegTsDemuxer::new(input).map(|d| Box::new(d) as Box<dyn Demuxer>)
}
pub struct MpegTsDemuxer {
input: Box<dyn ReadSeek>,
streams: Vec<StreamInfo>,
pid_to_stream: HashMap<u16, u32>,
reassemblers: HashMap<u16, PesReassembler>,
pending: VecDeque<Packet>,
eof_reached: bool,
bytes_read: u64,
putback: VecDeque<u8>,
}
impl std::fmt::Debug for MpegTsDemuxer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MpegTsDemuxer")
.field("streams", &self.streams.len())
.field("pending", &self.pending.len())
.field("eof", &self.eof_reached)
.finish()
}
}
impl MpegTsDemuxer {
fn new(mut input: Box<dyn ReadSeek>) -> CoreResult<Self> {
let mut pmt_pid: Option<u16> = None;
let mut programs_found: Option<ProgramAssociationTable> = None;
let mut probe_bytes: u64 = 0;
let mut probe_putback: VecDeque<u8> = VecDeque::new();
let mut pat_assembler = PsiSectionAssembler::new();
loop {
probe_bytes += TS_PACKET_LEN as u64;
let buf = match read_one_packet(
&mut input,
&mut probe_putback,
probe_bytes - TS_PACKET_LEN as u64,
)? {
Some(b) => b,
None => {
return Err(CoreError::invalid(
"mpegts: stream ended before a PAT was seen",
))
}
};
let pkt = TsPacket::parse(&buf).map_err(map_ts_err)?;
if pkt.pid != PAT_PID || pkt.payload.is_empty() {
continue;
}
let sections = pat_assembler
.feed(pkt.payload, pkt.payload_unit_start, pkt.continuity_counter)
.map_err(map_ts_err)?;
for section in §ions {
if let Ok(pat) = ProgramAssociationTable::parse(section) {
for (prog, pid) in &pat.programs {
if *prog != 0 {
pmt_pid = Some(*pid);
break;
}
}
programs_found = Some(pat);
if pmt_pid.is_some() {
break;
}
}
}
if pmt_pid.is_some() {
break;
}
}
let pmt_pid = pmt_pid
.ok_or_else(|| CoreError::invalid("mpegts: PAT carried no program with a PMT PID"))?;
let _ = programs_found;
let mut pmt_assembler = PsiSectionAssembler::new();
let pmt = loop {
probe_bytes += TS_PACKET_LEN as u64;
let buf = match read_one_packet(
&mut input,
&mut probe_putback,
probe_bytes - TS_PACKET_LEN as u64,
)? {
Some(b) => b,
None => {
return Err(CoreError::invalid(
"mpegts: stream ended before the PMT was seen",
))
}
};
let pkt = TsPacket::parse(&buf).map_err(map_ts_err)?;
if pkt.pid != pmt_pid || pkt.payload.is_empty() {
continue;
}
let sections = pmt_assembler
.feed(pkt.payload, pkt.payload_unit_start, pkt.continuity_counter)
.map_err(map_ts_err)?;
let mut got: Option<ProgramMapTable> = None;
for section in §ions {
if let Ok(pmt) = ProgramMapTable::parse(section) {
got = Some(pmt);
break;
}
}
if let Some(pmt) = got {
break pmt;
}
};
let tb = TimeBase::new(1, 90_000);
let mut streams: Vec<StreamInfo> = Vec::new();
let mut pid_to_stream: HashMap<u16, u32> = HashMap::new();
let mut reassemblers: HashMap<u16, PesReassembler> = HashMap::new();
for pmt_stream in &pmt.streams {
let params = match codec_params_for_stream_type(pmt_stream.stream_type) {
Some(p) => p,
None => continue,
};
let idx = streams.len() as u32;
streams.push(StreamInfo {
index: idx,
time_base: tb,
duration: None,
start_time: None,
params,
});
pid_to_stream.insert(pmt_stream.elementary_pid, idx);
reassemblers.insert(pmt_stream.elementary_pid, PesReassembler::new());
}
if streams.is_empty() {
return Err(CoreError::invalid(
"mpegts: PMT advertised no streams we recognise (BD-relevant types: \
0x02/0x1B/0x24/0xEA video, 0x80-0x86 audio, 0x90/0x92 subtitle)",
));
}
Ok(Self {
input,
streams,
pid_to_stream,
reassemblers,
pending: VecDeque::new(),
eof_reached: false,
bytes_read: probe_bytes,
putback: probe_putback,
})
}
fn read_one_into_pending(&mut self) -> CoreResult<usize> {
let buf = match read_one_packet(&mut self.input, &mut self.putback, self.bytes_read)? {
Some(b) => b,
None => return Ok(0),
};
self.bytes_read += TS_PACKET_LEN as u64;
let pkt = TsPacket::parse(&buf).map_err(map_ts_err)?;
let stream_idx = match self.pid_to_stream.get(&pkt.pid).copied() {
Some(i) => i,
None => return Ok(0),
};
let reassembler = self
.reassemblers
.get_mut(&pkt.pid)
.expect("pid_to_stream and reassemblers are kept in sync");
let emitted = reassembler.feed(&pkt).map_err(map_ts_err)?;
let pushed = if let Some(pes) = emitted {
self.pending.push_back(pes_to_packet(stream_idx, pes));
1
} else {
0
};
Ok(pushed)
}
fn flush_reassemblers(&mut self) {
let pids: Vec<u16> = self.reassemblers.keys().copied().collect();
for pid in pids {
let stream_idx = self.pid_to_stream[&pid];
if let Some(reassembler) = self.reassemblers.get_mut(&pid) {
if let Ok(Some(pes)) = reassembler.flush() {
self.pending.push_back(pes_to_packet(stream_idx, pes));
}
}
}
}
}
impl Demuxer for MpegTsDemuxer {
fn format_name(&self) -> &str {
"mpegts"
}
fn streams(&self) -> &[StreamInfo] {
&self.streams
}
fn next_packet(&mut self) -> CoreResult<Packet> {
loop {
if let Some(pkt) = self.pending.pop_front() {
return Ok(pkt);
}
if self.eof_reached {
return Err(CoreError::Eof);
}
let pushed = self.read_one_into_pending()?;
if pushed == 0 {
match read_one_packet(&mut self.input, &mut self.putback, self.bytes_read)? {
None => {
self.eof_reached = true;
self.flush_reassemblers();
}
Some(buf) => {
self.bytes_read += TS_PACKET_LEN as u64;
let pkt = TsPacket::parse(&buf).map_err(map_ts_err)?;
if let Some(stream_idx) = self.pid_to_stream.get(&pkt.pid).copied() {
if let Some(reassembler) = self.reassemblers.get_mut(&pkt.pid) {
if let Some(pes) = reassembler.feed(&pkt).map_err(map_ts_err)? {
self.pending.push_back(pes_to_packet(stream_idx, pes));
}
}
}
}
}
}
}
}
}
const RESYNC_PACKET_LIMIT: usize = 256;
fn read_one_packet(
input: &mut Box<dyn ReadSeek>,
putback: &mut VecDeque<u8>,
bytes_read: u64,
) -> CoreResult<Option<[u8; TS_PACKET_LEN]>> {
use std::io::Read;
let mut buf = [0u8; TS_PACKET_LEN];
let mut filled = 0;
while filled < TS_PACKET_LEN {
match putback.pop_front() {
Some(b) => {
buf[filled] = b;
filled += 1;
}
None => break,
}
}
while filled < TS_PACKET_LEN {
match input.read(&mut buf[filled..]) {
Ok(0) => {
if filled == 0 {
return Ok(None);
}
return Err(CoreError::invalid(format!(
"mpegts: short read at packet boundary ({filled}/{TS_PACKET_LEN} bytes, offset {bytes_read})"
)));
}
Ok(n) => filled += n,
Err(e) => return Err(CoreError::Io(e)),
}
}
if buf[0] != TS_SYNC_BYTE {
return resync(input, putback, buf, bytes_read).map(Some);
}
Ok(Some(buf))
}
fn resync(
input: &mut Box<dyn ReadSeek>,
putback: &mut VecDeque<u8>,
initial: [u8; TS_PACKET_LEN],
bytes_read: u64,
) -> CoreResult<[u8; TS_PACKET_LEN]> {
use std::io::Read;
let mut buf = [0u8; TS_PACKET_LEN];
let mut probe = [0u8; 1];
for _ in 0..RESYNC_PACKET_LIMIT {
let mut filled = 0;
while filled < TS_PACKET_LEN {
match input.read(&mut buf[filled..]) {
Ok(0) => {
return Err(CoreError::invalid(format!(
"mpegts: bad sync byte 0x{:02X} at offset {bytes_read} \
and EOF reached during resync",
initial[0]
)));
}
Ok(n) => filled += n,
Err(e) => return Err(CoreError::Io(e)),
}
}
if buf[0] != TS_SYNC_BYTE {
continue;
}
match input.read(&mut probe) {
Ok(0) => {
return Ok(buf);
}
Ok(_) => {}
Err(e) => return Err(CoreError::Io(e)),
}
if probe[0] == TS_SYNC_BYTE {
putback.push_back(probe[0]);
return Ok(buf);
}
}
Err(CoreError::invalid(format!(
"mpegts: bad sync byte 0x{:02X} at offset {bytes_read} (packet {}), \
resync failed after {RESYNC_PACKET_LIMIT} 188-byte chunks",
initial[0],
bytes_read / TS_PACKET_LEN as u64,
)))
}
fn map_ts_err(e: crate::TsError) -> CoreError {
CoreError::invalid(format!("mpegts: {e}"))
}
fn pes_to_packet(stream_index: u32, pes: PesPacket) -> Packet {
let tb = TimeBase::new(1, 90_000);
let mut pkt = Packet::new(stream_index, tb, pes.payload);
if let Some(p) = pes.pts_90k {
pkt = pkt.with_pts(p as i64);
}
if let Some(d) = pes.dts_90k {
pkt = pkt.with_dts(d as i64);
}
pkt
}
fn codec_params_for_stream_type(st: u8) -> Option<CodecParameters> {
let s = StreamType::from_raw(st);
let cid: &'static str = match s {
StreamType::Mpeg2Video => "mpeg2video",
StreamType::AvcVideo => "h264",
StreamType::HevcVideo => "hevc",
StreamType::Vc1Video => "vc1",
StreamType::LpcmAudio => "pcm_s16be",
StreamType::Ac3Audio | StreamType::EAc3SecondaryAudio => "ac3",
StreamType::EAc3Audio => "eac3",
StreamType::TruehdAudio => "truehd",
StreamType::DtsAudio
| StreamType::DtsHdAudio
| StreamType::DtsHdMaAudio
| StreamType::DtsHdSecondaryAudio => "dts",
StreamType::PgsSubtitle => "hdmv_pgs_subtitle",
StreamType::TextSubtitle => "hdmv_textst_subtitle",
StreamType::IgsInteractive => return None,
StreamType::Other(_) => return None,
};
let codec_id = CodecId::new(cid);
Some(match s {
StreamType::Mpeg2Video
| StreamType::AvcVideo
| StreamType::HevcVideo
| StreamType::Vc1Video => CodecParameters::video(codec_id),
StreamType::PgsSubtitle | StreamType::TextSubtitle => CodecParameters::subtitle(codec_id),
StreamType::IgsInteractive | StreamType::Other(_) => unreachable!(),
_ => CodecParameters::audio(codec_id),
})
}
pub fn probe(p: &oxideav_core::ProbeData) -> oxideav_core::ProbeScore {
let mut hits = 0u8;
for off in [0, 188, 376, 564] {
if p.buf.get(off).copied() == Some(TS_SYNC_BYTE) {
hits += 1;
}
}
match hits {
4 => 100,
3 => 80,
2 => 60,
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn synth_minimal_ts() -> Vec<u8> {
fn crc32_mpeg2(data: &[u8]) -> u32 {
let mut c: u32 = 0xFFFF_FFFF;
for &b in data {
c ^= (b as u32) << 24;
for _ in 0..8 {
c = if c & 0x8000_0000 != 0 {
(c << 1) ^ 0x04C1_1DB7
} else {
c << 1
};
}
}
c
}
fn ts_packet(
pid: u16,
pusi: bool,
cc: u8,
payload: &[u8],
is_psi: bool,
) -> [u8; TS_PACKET_LEN] {
let mut pkt = [0u8; TS_PACKET_LEN];
pkt[0] = TS_SYNC_BYTE;
let pusi_bit = if pusi { 0b0100_0000 } else { 0 };
pkt[1] = pusi_bit | ((pid >> 8) as u8 & 0b0001_1111);
pkt[2] = pid as u8;
pkt[3] = 0b0001_0000 | (cc & 0x0F);
let mut off = 4;
if pusi && is_psi {
pkt[off] = 0; off += 1;
}
let take = (TS_PACKET_LEN - off).min(payload.len());
pkt[off..off + take].copy_from_slice(&payload[..take]);
pkt
}
let mut pat = vec![
0x00, 0xB0,
0x0D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0x00, 0x01, 0xE1, 0x00, ];
let pat_crc = crc32_mpeg2(&pat);
pat.extend_from_slice(&pat_crc.to_be_bytes());
let mut pmt = vec![
0x02, 0xB0, 0x12, 0x00, 0x01, 0xC1, 0x00, 0x00, 0xE1, 0x01, 0xF0, 0x00, 0x1B, 0xE1, 0x01, 0xF0, 0x00, ];
let pmt_crc = crc32_mpeg2(&pmt);
pmt.extend_from_slice(&pmt_crc.to_be_bytes());
fn encode_pts(pts: u64, marker_nibble: u8) -> [u8; 5] {
let mut b = [0u8; 5];
b[0] = (marker_nibble << 4) | (((pts >> 29) & 0x0E) as u8) | 1;
b[1] = ((pts >> 22) & 0xFF) as u8;
b[2] = (((pts >> 14) & 0xFE) as u8) | 1;
b[3] = ((pts >> 7) & 0xFF) as u8;
b[4] = (((pts << 1) & 0xFE) as u8) | 1;
b
}
let pts_bytes = encode_pts(12345, 0b0010);
let pes_payload = b"hello";
let opt_hdr_len: u8 = 5;
let pes_packet_length: u16 = 3 + opt_hdr_len as u16 + pes_payload.len() as u16; let mut pes = vec![
0x00,
0x00,
0x01, 0xE0, (pes_packet_length >> 8) as u8,
pes_packet_length as u8,
0b1000_0000, 0b1000_0000, opt_hdr_len,
];
pes.extend_from_slice(&pts_bytes);
pes.extend_from_slice(pes_payload);
let mut buf = Vec::new();
buf.extend_from_slice(&ts_packet(0x0000, true, 0, &pat, true));
buf.extend_from_slice(&ts_packet(0x0100, true, 0, &pmt, true));
buf.extend_from_slice(&ts_packet(0x0101, true, 0, &pes, false));
buf
}
#[test]
fn demux_synth_ts_yields_one_avc_stream_with_pts() {
let bytes = synth_minimal_ts();
let cursor: Box<dyn ReadSeek> = Box::new(Cursor::new(bytes));
let mut dmx = MpegTsDemuxer::new(cursor).expect("open");
assert_eq!(dmx.streams().len(), 1);
assert_eq!(dmx.streams()[0].params.codec_id.as_str(), "h264");
let pkt = dmx.next_packet().expect("first PES");
assert_eq!(pkt.pts, Some(12345));
let next = dmx.next_packet();
assert!(matches!(next, Err(CoreError::Eof)));
}
fn ts_packets_for_section(pid: u16, section: &[u8], cc_start: u8) -> Vec<[u8; TS_PACKET_LEN]> {
let mut out = Vec::new();
let mut consumed = 0usize;
let mut cc = cc_start & 0x0F;
let mut first = true;
while consumed < section.len() {
let mut pkt = [0u8; TS_PACKET_LEN];
pkt[0] = TS_SYNC_BYTE;
let pusi_bit = if first { 0b0100_0000 } else { 0 };
pkt[1] = pusi_bit | ((pid >> 8) as u8 & 0b0001_1111);
pkt[2] = pid as u8;
pkt[3] = 0b0001_0000 | cc;
let mut off = 4;
if first {
pkt[off] = 0; off += 1;
first = false;
}
let room = TS_PACKET_LEN - off;
let take = room.min(section.len() - consumed);
pkt[off..off + take].copy_from_slice(§ion[consumed..consumed + take]);
for b in &mut pkt[off + take..] {
*b = 0xFF;
}
consumed += take;
cc = (cc + 1) & 0x0F;
out.push(pkt);
}
out
}
#[test]
fn demux_handles_pmt_spanning_two_ts_packets() {
fn crc32_mpeg2(data: &[u8]) -> u32 {
let mut c: u32 = 0xFFFF_FFFF;
for &b in data {
c ^= (b as u32) << 24;
for _ in 0..8 {
c = if c & 0x8000_0000 != 0 {
(c << 1) ^ 0x04C1_1DB7
} else {
c << 1
};
}
}
c
}
let mut pat = vec![
0x00, 0xB0, 0x0D, 0x00, 0x01, 0xC1, 0x00, 0x00, 0x00, 0x01, 0xE1, 0x00,
];
let pat_crc = crc32_mpeg2(&pat);
pat.extend_from_slice(&pat_crc.to_be_bytes());
let descr: Vec<u8> = {
let mut d = vec![0xC0u8, 200u8]; d.extend(std::iter::repeat(0xAA).take(200));
d
};
let es_info_len = descr.len() as u16; let section_body_len = 4 + 5 + descr.len(); let section_length = 5 + section_body_len + 4; let mut pmt = vec![
0x02,
(0xB0 | ((section_length >> 8) & 0x0F) as u8),
(section_length & 0xFF) as u8,
0x00,
0x01,
0xC1,
0x00,
0x00,
0xE1,
0x01, 0xF0,
0x00, 0x1B, 0xE1,
0x01, (0xF0 | ((es_info_len >> 8) & 0x0F) as u8),
(es_info_len & 0xFF) as u8,
];
pmt.extend_from_slice(&descr);
let pmt_crc = crc32_mpeg2(&pmt);
pmt.extend_from_slice(&pmt_crc.to_be_bytes());
assert!(
pmt.len() > 184,
"PMT must overflow a single TS payload to exercise the assembler"
);
fn encode_pts(pts: u64, marker_nibble: u8) -> [u8; 5] {
let mut b = [0u8; 5];
b[0] = (marker_nibble << 4) | (((pts >> 29) & 0x0E) as u8) | 1;
b[1] = ((pts >> 22) & 0xFF) as u8;
b[2] = (((pts >> 14) & 0xFE) as u8) | 1;
b[3] = ((pts >> 7) & 0xFF) as u8;
b[4] = (((pts << 1) & 0xFE) as u8) | 1;
b
}
let pts_bytes = encode_pts(7777, 0b0010);
let pes_payload = b"spans-pmt";
let opt_hdr_len: u8 = 5;
let pes_packet_length: u16 = 3 + opt_hdr_len as u16 + pes_payload.len() as u16;
let mut pes = vec![
0x00,
0x00,
0x01,
0xE0,
(pes_packet_length >> 8) as u8,
pes_packet_length as u8,
0b1000_0000,
0b1000_0000,
opt_hdr_len,
];
pes.extend_from_slice(&pts_bytes);
pes.extend_from_slice(pes_payload);
let mut buf = Vec::new();
for p in ts_packets_for_section(0x0000, &pat, 0) {
buf.extend_from_slice(&p);
}
for p in ts_packets_for_section(0x0100, &pmt, 0) {
buf.extend_from_slice(&p);
}
let mut pes_pkt = [0u8; TS_PACKET_LEN];
pes_pkt[0] = TS_SYNC_BYTE;
pes_pkt[1] = 0b0100_0000 | ((0x0101 >> 8) as u8 & 0b0001_1111);
pes_pkt[2] = 0x01;
pes_pkt[3] = 0b0001_0000;
let take = (TS_PACKET_LEN - 4).min(pes.len());
pes_pkt[4..4 + take].copy_from_slice(&pes[..take]);
buf.extend_from_slice(&pes_pkt);
let cursor: Box<dyn ReadSeek> = Box::new(Cursor::new(buf));
let mut dmx = MpegTsDemuxer::new(cursor).expect("open with multi-TS-packet PMT");
assert_eq!(dmx.streams().len(), 1);
assert_eq!(dmx.streams()[0].params.codec_id.as_str(), "h264");
let pkt = dmx.next_packet().expect("first PES across multi-PMT path");
assert_eq!(pkt.pts, Some(7777));
}
#[test]
fn probe_recognises_sync_aligned_ts() {
let mut buf = vec![0u8; 800];
for off in [0, 188, 376, 564] {
buf[off] = TS_SYNC_BYTE;
}
let p = oxideav_core::ProbeData {
buf: &buf,
ext: None,
};
assert_eq!(probe(&p), 100);
}
#[test]
fn resync_recovers_after_short_garbage_run() {
fn make_pkt(cc: u8) -> [u8; TS_PACKET_LEN] {
let mut p = [0xFFu8; TS_PACKET_LEN];
p[0] = TS_SYNC_BYTE;
p[1] = 0x10; p[2] = 0x00;
p[3] = 0x10 | (cc & 0x0F);
p
}
let p0 = make_pkt(0);
let p1 = make_pkt(1);
let p2 = make_pkt(2);
let mut buf = Vec::new();
buf.extend_from_slice(&p0);
buf.extend_from_slice(&vec![0xAAu8; 11 * TS_PACKET_LEN]);
buf.extend_from_slice(&p1);
buf.extend_from_slice(&p2);
let mut cursor: Box<dyn ReadSeek> = Box::new(Cursor::new(buf));
let mut putback = VecDeque::new();
let r0 = read_one_packet(&mut cursor, &mut putback, 0)
.expect("first")
.expect("Some");
assert_eq!(r0[3] & 0x0F, 0);
let r1 = read_one_packet(&mut cursor, &mut putback, 188)
.expect("resync")
.expect("Some");
assert_eq!(r1[3] & 0x0F, 1, "expected CC=1, got CC={}", r1[3] & 0x0F);
let r2 = read_one_packet(&mut cursor, &mut putback, 376)
.expect("p2")
.expect("Some");
assert_eq!(r2[3] & 0x0F, 2, "expected CC=2, got CC={}", r2[3] & 0x0F);
}
#[test]
fn probe_rejects_random_bytes() {
let buf = vec![0u8; 800];
let p = oxideav_core::ProbeData {
buf: &buf,
ext: None,
};
assert_eq!(probe(&p), 0);
}
}