use alloc::collections::btree_map::BTreeMap;
use alloc::vec::Vec;
use broadcast_common::Parse;
use dvb_si::tables::pat::PatSection;
use dvb_si::tables::pmt::{PmtSection, StreamType};
use mpeg_pes::PesAssembler;
use mpeg_ts::ts::{SectionReassembler, TS_PACKET_SIZE, TsPacket};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DeclaredStream {
pub pid: u16,
pub stream_type: StreamType,
}
pub(crate) fn collect_pmt_streams(ts: &[u8]) -> Vec<DeclaredStream> {
let n_packets = ts.len() / TS_PACKET_SIZE;
let mut reassemblers: BTreeMap<u16, SectionReassembler> = BTreeMap::new();
reassemblers.entry(dvb_si::tables::pat::PID).or_default();
let mut pmt_pids: Vec<u16> = Vec::new();
let mut declared: Vec<DeclaredStream> = Vec::new();
for i in 0..n_packets {
let offset = i * TS_PACKET_SIZE;
let raw = &ts[offset..offset + TS_PACKET_SIZE];
let Ok(pkt) = TsPacket::parse(raw) else {
continue;
};
let pid = pkt.header.pid;
if !reassemblers.contains_key(&pid) {
continue;
}
let Some(payload) = pkt.payload else {
continue;
};
let pusi = pkt.header.pusi;
reassemblers.get_mut(&pid).unwrap().feed(payload, pusi);
let mut new_pmt_pids: Vec<u16> = Vec::new();
while let Some(section) = reassemblers.get_mut(&pid).unwrap().pop_section() {
if pid == dvb_si::tables::pat::PID {
if let Ok(pat) = PatSection::parse(§ion) {
for entry in &pat.entries {
if entry.program_number != dvb_si::tables::pat::PROGRAM_NUMBER_NIT
&& !pmt_pids.contains(&entry.pid)
&& !new_pmt_pids.contains(&entry.pid)
{
new_pmt_pids.push(entry.pid);
}
}
}
} else if let Ok(pmt) = PmtSection::parse(§ion) {
for stream in &pmt.streams {
declared.push(DeclaredStream {
pid: stream.elementary_pid,
stream_type: stream.stream_type,
});
}
}
}
for pmt_pid in new_pmt_pids {
pmt_pids.push(pmt_pid);
reassemblers.entry(pmt_pid).or_default();
}
}
declared
}
pub(crate) fn pids_with_stream_type(
streams: &[DeclaredStream],
stream_type: StreamType,
) -> Vec<u16> {
streams
.iter()
.filter(|s| s.stream_type == stream_type)
.map(|s| s.pid)
.collect()
}
pub(crate) fn for_each_access_unit(
ts: &[u8],
mut wanted: impl FnMut(u16) -> bool,
mut on_payload: impl FnMut(&[u8], usize, u16),
) {
let n_packets = ts.len() / TS_PACKET_SIZE;
let mut assemblers: BTreeMap<u16, PesAssembler> = BTreeMap::new();
for i in 0..n_packets {
let offset = i * TS_PACKET_SIZE;
let raw = &ts[offset..offset + TS_PACKET_SIZE];
let Ok(pkt) = TsPacket::parse(raw) else {
continue;
};
let pid = pkt.header.pid;
if !wanted(pid) {
continue;
}
let Some(payload) = pkt.payload else {
continue;
};
if payload.is_empty() {
continue;
}
let pusi = pkt.header.pusi;
let assembler = assemblers.entry(pid).or_default();
if let Some(pes_bytes) = assembler.feed(pusi, payload)
&& let Ok(pes) = mpeg_pes::PesPacket::parse(&pes_bytes)
{
on_payload(pes.payload, i, pid);
}
}
let last = n_packets.saturating_sub(1);
for (&pid, assembler) in assemblers.iter_mut() {
if let Some(pes_bytes) = assembler.flush()
&& let Ok(pes) = mpeg_pes::PesPacket::parse(&pes_bytes)
{
on_payload(pes.payload, last, pid);
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use alloc::vec::Vec;
use broadcast_common::Serialize;
use dvb_si::descriptors::any::DescriptorLoop;
use dvb_si::tables::pat::{PatEntry, PatSection};
use dvb_si::tables::pmt::{PmtSection, PmtStream, StreamType};
use mpeg_ts::mux::SectionPacketiser;
use mpeg_ts::ts::TS_PACKET_SIZE;
pub(crate) const TEST_PMT_PID: u16 = 0x0100;
fn serialize_section<S: Serialize>(section: &S) -> Vec<u8>
where
S::Error: core::fmt::Debug,
{
let mut buf = alloc::vec![0u8; section.serialized_len()];
let n = section.serialize_into(&mut buf).expect("serialize section");
buf.truncate(n);
buf
}
pub(crate) fn build_pat_pmt_ts(streams: &[(u16, StreamType)]) -> Vec<u8> {
let pat = PatSection {
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
entries: alloc::vec![PatEntry {
program_number: 1,
pid: TEST_PMT_PID,
}],
};
let pmt_streams: Vec<PmtStream<'_>> = streams
.iter()
.map(|&(pid, stream_type)| PmtStream {
stream_type,
elementary_pid: pid,
es_info: DescriptorLoop::new(&[]),
})
.collect();
let pcr_pid = streams.first().map(|&(pid, _)| pid).unwrap_or(0x1FFF);
let pmt = PmtSection::new(
1,
0,
true,
0,
0,
pcr_pid,
DescriptorLoop::new(&[]),
pmt_streams,
);
let pat_bytes = serialize_section(&pat);
let pmt_bytes = serialize_section(&pmt);
let mut ts = Vec::new();
for pkt in SectionPacketiser::new(dvb_si::tables::pat::PID).packetise(&[&pat_bytes]) {
ts.extend_from_slice(&pkt);
}
for pkt in SectionPacketiser::new(TEST_PMT_PID).packetise(&[&pmt_bytes]) {
ts.extend_from_slice(&pkt);
}
assert_eq!(ts.len() % TS_PACKET_SIZE, 0);
ts
}
pub(crate) fn build_pes(stream_id: u8, payload: &[u8]) -> Vec<u8> {
let pes_len = 3 + payload.len();
let mut pes = alloc::vec![0x00, 0x00, 0x01, stream_id];
pes.extend_from_slice(&(pes_len as u16).to_be_bytes());
pes.push(0x80); pes.push(0x00); pes.push(0x00); pes.extend_from_slice(payload);
pes
}
pub(crate) fn make_pes_packet(pid: u16, cc: u8, pes_bytes: &[u8]) -> Vec<u8> {
let mut pkt = alloc::vec![0x47u8; TS_PACKET_SIZE];
pkt[1] = 0x40 | (((pid >> 8) as u8) & 0x1F); pkt[2] = (pid & 0xFF) as u8;
pkt[3] = 0x10 | (cc & 0x0F); let len = pes_bytes.len().min(TS_PACKET_SIZE - 4);
pkt[4..4 + len].copy_from_slice(&pes_bytes[..len]);
pkt
}
}