use alloc::collections::BTreeSet;
use broadcast_common::traits::Parse;
use dvb_si::tables::pat::PatSection;
use dvb_si::tables::pmt::PmtSection;
use mpeg_ts::ts::{SectionReassembler, TS_PACKET_SIZE, TsHeader, extract_ts_payload};
use crate::ops::{Op, StreamModel};
const PAT_PID: u16 = 0x0000;
const NULL_PID: u16 = 0x1FFF;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum PidFilter {
Keep {
pids: BTreeSet<u16>,
},
Service {
program_number: u16,
},
}
impl PidFilter {
pub fn keep(pids: impl IntoIterator<Item = u16>) -> Self {
let mut set: BTreeSet<u16> = pids.into_iter().collect();
set.insert(PAT_PID);
Self::Keep { pids: set }
}
pub fn service(program_number: u16) -> Self {
Self::Service { program_number }
}
}
enum ServiceState {
WaitingPat {
program_number: u16,
pat_reasm: SectionReassembler,
},
WaitingPmt {
pmt_pid: u16,
pmt_reasm: SectionReassembler,
},
Resolved { keep: BTreeSet<u16> },
}
fn ts_payload_and_pusi(packet: &[u8]) -> Option<(&[u8], bool)> {
let header = TsHeader::parse(&packet[..4]).ok()?;
let payload = extract_ts_payload(packet)?;
Some((payload, header.pusi))
}
pub(crate) struct PidFilterOp {
state: FilterState,
}
enum FilterState {
KeepSet(BTreeSet<u16>),
Service(ServiceState),
}
impl PidFilterOp {
pub(crate) fn new(cfg: PidFilter) -> Self {
let state = match cfg {
PidFilter::Keep { pids } => FilterState::KeepSet(pids),
PidFilter::Service { program_number } => {
FilterState::Service(ServiceState::WaitingPat {
program_number,
pat_reasm: SectionReassembler::default(),
})
}
};
Self { state }
}
fn should_keep(&self, pid: u16) -> bool {
match &self.state {
FilterState::KeepSet(set) => set.contains(&pid),
FilterState::Service(svc_state) => match svc_state {
ServiceState::WaitingPat { .. } => {
pid == PAT_PID
}
ServiceState::WaitingPmt { pmt_pid, .. } => {
pid == PAT_PID || pid == *pmt_pid
}
ServiceState::Resolved { keep } => keep.contains(&pid),
},
}
}
fn observe(&mut self, packet: &[u8]) {
let state = match &mut self.state {
FilterState::KeepSet(_) => return,
FilterState::Service(s) => s,
};
match state {
ServiceState::WaitingPat {
program_number,
pat_reasm,
} => {
let pid = (((packet[1] & 0x1F) as u16) << 8) | packet[2] as u16;
if pid != PAT_PID {
return;
}
let Some((payload, pusi)) = ts_payload_and_pusi(packet) else {
return;
};
pat_reasm.feed(payload, pusi);
let pn = *program_number;
while let Some(section) = pat_reasm.pop_section() {
let Ok(pat) = PatSection::parse(§ion) else {
continue;
};
if let Some(entry) = pat.entries.iter().find(|e| e.program_number == pn) {
let pmt_pid = entry.pid;
*state = ServiceState::WaitingPmt {
pmt_pid,
pmt_reasm: SectionReassembler::default(),
};
return;
}
}
}
ServiceState::WaitingPmt { pmt_pid, pmt_reasm } => {
let pid = (((packet[1] & 0x1F) as u16) << 8) | packet[2] as u16;
if pid != *pmt_pid {
return;
}
let Some((payload, pusi)) = ts_payload_and_pusi(packet) else {
return;
};
pmt_reasm.feed(payload, pusi);
let pmt_pid = *pmt_pid;
while let Some(section) = pmt_reasm.pop_section() {
let Ok(pmt) = PmtSection::parse(§ion) else {
continue;
};
let mut keep = BTreeSet::new();
keep.insert(PAT_PID);
keep.insert(pmt_pid);
keep.insert(pmt.pcr_pid);
for stream in &pmt.streams {
keep.insert(stream.elementary_pid);
}
*state = ServiceState::Resolved { keep };
return;
}
}
ServiceState::Resolved { .. } => {
}
}
}
}
impl Op for PidFilterOp {
fn process(&mut self, packet: &[u8], _model: &mut StreamModel, out: &mut dyn FnMut(&[u8])) {
if packet.len() != TS_PACKET_SIZE {
out(packet);
return;
}
let pid = (((packet[1] & 0x1F) as u16) << 8) | packet[2] as u16;
if pid == NULL_PID {
return;
}
self.observe(packet);
if self.should_keep(pid) {
out(packet);
}
}
fn flush(&mut self, _model: &mut StreamModel, _out: &mut dyn FnMut(&[u8])) {
}
}