use dvb_bbframe::header::{Bbheader, Mode, BBHEADER_LEN};
use dvb_bbframe::packet::{CarryOverExtractor, NM_UP_SIZE};
use crate::payload::AnyPayload;
use crate::pump::{Stats, T2miPump};
pub struct InnerTsRecovery {
pump: T2miPump,
extractor: CarryOverExtractor,
out: Vec<[u8; NM_UP_SIZE]>,
up_buf: Vec<[u8; NM_UP_SIZE]>,
}
impl InnerTsRecovery {
#[must_use]
pub fn new(t2mi_pid: u16) -> Self {
Self {
pump: T2miPump::new(t2mi_pid),
extractor: CarryOverExtractor::new(),
out: Vec::new(),
up_buf: Vec::new(),
}
}
pub fn feed(&mut self, ts_packet: &[u8]) -> &[[u8; NM_UP_SIZE]] {
self.out.clear();
let events: Vec<_> = self.pump.feed_ts(ts_packet).collect();
for event in events {
let Ok(AnyPayload::Bbframe(bb)) = event.payload() else {
continue;
};
if bb.bbframe.len() < BBHEADER_LEN {
continue;
}
let Ok(hdr) = Bbheader::parse(bb.bbframe) else {
continue;
};
let header_bytes: [u8; BBHEADER_LEN] = match bb.bbframe[..BBHEADER_LEN].try_into() {
Ok(b) => b,
Err(_) => continue,
};
let data_field = &bb.bbframe[BBHEADER_LEN..];
match hdr.mode {
Mode::Normal => {
self.extractor
.feed_nm_into(&header_bytes, data_field, &mut self.up_buf);
}
Mode::HighEfficiency if !hdr.matype.npd => {
self.extractor.feed_hem_into(
&header_bytes,
data_field,
false,
&mut self.up_buf,
);
}
_ => continue,
}
self.out.append(&mut self.up_buf);
}
&self.out
}
#[must_use]
pub fn stats(&self) -> Stats {
self.pump.stats()
}
}
#[cfg(test)]
mod tests {
use super::*;
use dvb_bbframe::crc::crc8;
use dvb_bbframe::header::{Matype, TsGs};
use dvb_common::crc32_mpeg2;
const TS_SYNC: u8 = 0x47;
const TS_LEN: usize = 188;
fn inner_packet() -> [u8; TS_LEN] {
let mut p = [0xAAu8; TS_LEN];
p[0] = TS_SYNC;
p[1] = 0x41; p[2] = 0x00;
p[3] = 0x10; p
}
fn nm_bbframe(inner: &[u8; TS_LEN]) -> Vec<u8> {
let hdr = Bbheader {
matype: Matype {
ts_gs: TsGs::Ts,
sis: true,
ccm: true,
issyi: false,
npd: false,
ext: 0,
isi: 0,
},
upl: 1504,
sync: TS_SYNC,
dfl: 1504,
syncd: 0,
mode: Mode::Normal,
issy_in_header: None,
};
let mut frame = hdr.serialize().to_vec();
let mut data = [0u8; TS_LEN];
data[0] = crc8(&[0u8; TS_LEN]); data[1..].copy_from_slice(&inner[1..]);
frame.extend_from_slice(&data);
frame
}
fn t2mi_packet(bbframe: &[u8]) -> Vec<u8> {
let mut payload = vec![0x00, 0x05, 0x80]; payload.extend_from_slice(bbframe);
let mut pkt = vec![0x00u8, 0x01, 0x00, 0x00];
pkt.extend_from_slice(&((payload.len() * 8) as u16).to_be_bytes());
pkt.extend_from_slice(&payload);
let crc = crc32_mpeg2::compute(&pkt);
pkt.extend_from_slice(&crc.to_be_bytes());
pkt
}
fn outer_ts(pid: u16, data: &[u8]) -> Vec<[u8; TS_LEN]> {
let mut out = Vec::new();
let first_cap = TS_LEN - 5;
let cont_cap = TS_LEN - 4;
let mut off = 0;
let mut first = true;
while off < data.len() {
let mut pkt = [0xFFu8; TS_LEN];
pkt[0] = TS_SYNC;
let cap = if first { first_cap } else { cont_cap };
pkt[1] = (if first { 0x40 } else { 0x00 }) | (((pid >> 8) as u8) & 0x1F);
pkt[2] = (pid & 0xFF) as u8;
pkt[3] = 0x10;
let hdr_len = if first {
pkt[4] = 0x00; 5
} else {
4
};
let n = (data.len() - off).min(cap);
pkt[hdr_len..hdr_len + n].copy_from_slice(&data[off..off + n]);
out.push(pkt);
off += n;
first = false;
}
out
}
#[test]
fn recovers_inner_ts_from_nm_bbframe_chain() {
let pid = 0x1000;
let inner = inner_packet();
let outer = outer_ts(pid, &t2mi_packet(&nm_bbframe(&inner)));
let mut rec = InnerTsRecovery::new(pid);
let mut recovered: Vec<[u8; TS_LEN]> = Vec::new();
for pkt in &outer {
recovered.extend_from_slice(rec.feed(pkt));
}
assert_eq!(recovered.len(), 1, "exactly one inner TS packet expected");
assert_eq!(recovered[0][0], TS_SYNC, "sync byte restored");
assert_eq!(&recovered[0][1..], &inner[1..]);
}
#[test]
fn wrong_pid_yields_nothing() {
let inner = inner_packet();
let outer = outer_ts(0x1000, &t2mi_packet(&nm_bbframe(&inner)));
let mut rec = InnerTsRecovery::new(0x0064); let mut n = 0;
for pkt in &outer {
n += rec.feed(pkt).len();
}
assert_eq!(n, 0);
}
#[test]
fn garbage_packet_no_panic_no_output() {
let mut rec = InnerTsRecovery::new(0x1000);
let junk = [0u8; TS_LEN];
assert!(rec.feed(&junk).is_empty());
}
}