use super::{CodecParser, Frame, PesPacket, pts_to_ns};
const AU_DURATION_NS: i64 = 833_333;
const MAX_TRUEHD_BUF: usize = 256 * 1024;
pub struct TrueHdParser {
buf: Vec<u8>,
next_pts_ns: i64,
}
impl Default for TrueHdParser {
fn default() -> Self {
Self::new()
}
}
impl TrueHdParser {
pub fn new() -> Self {
Self {
buf: Vec::with_capacity(32768),
next_pts_ns: 0,
}
}
fn ac3_frame_at_head(&self) -> Ac3Size {
if self.buf.len() < 6 {
return Ac3Size::NeedMore;
}
let frame_bytes = super::ac3::ac3_frame_size(&self.buf);
if frame_bytes == 0 {
return Ac3Size::Unmappable;
}
if self.buf.len() < frame_bytes {
return Ac3Size::NeedMore;
}
Ac3Size::Frame(frame_bytes)
}
}
fn ac3_boundary_corroborated(buf: &[u8], frame_bytes: usize) -> bool {
if frame_bytes >= buf.len() {
return true;
}
let tail = &buf[frame_bytes..];
if tail.len() < 2 {
return true;
}
if tail[0] == 0x0B && tail[1] == 0x77 {
return true;
}
let next_words = (((tail[0] as usize) << 8) | tail[1] as usize) & 0xFFF;
next_words != 0 && next_words * 2 <= 32768
}
enum Ac3Size {
Unmappable,
NeedMore,
Frame(usize),
}
impl CodecParser for TrueHdParser {
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
if pes.data.is_empty() {
return Vec::new();
}
if self.buf.is_empty() {
if let Some(pts) = pes.pts {
self.next_pts_ns = pts_to_ns(pts);
}
}
self.buf.extend_from_slice(&pes.data);
let mut frames = Vec::new();
loop {
if self.buf.len() < 4 {
break;
}
if self.buf[0] == 0x0B && self.buf[1] == 0x77 {
match self.ac3_frame_at_head() {
Ac3Size::Unmappable => {
self.buf.drain(..2);
continue;
}
Ac3Size::NeedMore => break, Ac3Size::Frame(skip) => {
if ac3_boundary_corroborated(&self.buf, skip) {
self.buf.drain(..skip);
continue;
}
}
}
}
let unit_words = (((self.buf[0] as usize) << 8) | self.buf[1] as usize) & 0xFFF;
if unit_words == 0 {
self.buf.drain(..4);
continue;
}
let unit_bytes = unit_words * 2;
if self.buf.len() < unit_bytes {
break; }
let is_major_sync = unit_bytes >= 8
&& (u32::from_be_bytes([self.buf[4], self.buf[5], self.buf[6], self.buf[7]])
& 0xFFFF_FFFE)
== 0xF872_6FBA;
frames.push(Frame {
pts_ns: self.next_pts_ns,
keyframe: is_major_sync,
data: self.buf[..unit_bytes].to_vec(),
duration_ns: None,
});
self.buf.drain(..unit_bytes);
self.next_pts_ns += AU_DURATION_NS;
}
if self.buf.len() > MAX_TRUEHD_BUF {
self.buf.clear();
}
frames
}
fn codec_private(&self) -> Option<Vec<u8>> {
None
}
}
const THD_8CH: [u8; 13] = [2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1];
const THD_6CH: [u8; 5] = [2, 1, 1, 2, 1];
pub fn truehd_channels(format_info: u32) -> Option<u8> {
let ch8 = (format_info & 0x1FFF) as u16; let ch6 = ((format_info >> 15) & 0x1F) as u16; let count = |mask: u16, tbl: &[u8]| -> u8 {
tbl.iter()
.enumerate()
.filter(|(i, _)| mask & (1 << i) != 0)
.map(|(_, &c)| c)
.sum()
};
if ch8 != 0 {
Some(count(ch8, &THD_8CH))
} else if ch6 != 0 {
Some(count(ch6, &THD_6CH))
} else {
None
}
}
pub fn truehd_channels_from_stream(data: &[u8]) -> Option<u8> {
let mut p = 0;
while p + 8 <= data.len() {
let w = u32::from_be_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]);
if (w & 0xFFFF_FFFE) == 0xF872_6FBA {
let fi = u32::from_be_bytes([data[p + 4], data[p + 5], data[p + 6], data[p + 7]]);
return truehd_channels(fi);
}
p += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mux::ts::PesPacket;
fn make_pes(data: Vec<u8>, pts: Option<i64>) -> PesPacket {
PesPacket {
pid: 0x1100,
pts,
dts: None,
data,
}
}
fn make_truehd_unit(size_bytes: usize) -> Vec<u8> {
let words = size_bytes / 2;
let mut data = vec![0u8; size_bytes];
data[0] = ((words >> 8) & 0x0F) as u8;
data[1] = (words & 0xFF) as u8;
data
}
fn make_ac3_frame() -> Vec<u8> {
let mut data = vec![0u8; 128];
data[0] = 0x0B;
data[1] = 0x77;
data[4] = 0x00; data
}
#[test]
fn parse_empty_pes() {
let mut parser = TrueHdParser::new();
let pes = make_pes(Vec::new(), Some(0));
assert!(parser.parse(&pes).is_empty());
}
#[test]
fn parse_single_unit() {
let mut parser = TrueHdParser::new();
let unit = make_truehd_unit(200);
let pes = make_pes(unit, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data.len(), 200);
}
#[test]
fn parse_unit_spanning_two_pes() {
let mut parser = TrueHdParser::new();
let unit = make_truehd_unit(200);
let mid = 100;
let pes1 = make_pes(unit[..mid].to_vec(), Some(90000));
assert!(parser.parse(&pes1).is_empty());
let pes2 = make_pes(unit[mid..].to_vec(), Some(93000));
let frames = parser.parse(&pes2);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data.len(), 200);
}
#[test]
fn parse_multiple_units_incrementing_pts() {
let mut parser = TrueHdParser::new();
let mut data = make_truehd_unit(100);
data.extend_from_slice(&make_truehd_unit(120));
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 2);
assert_eq!(frames[0].data.len(), 100);
assert_eq!(frames[1].data.len(), 120);
assert_eq!(frames[1].pts_ns - frames[0].pts_ns, AU_DURATION_NS);
}
#[test]
fn skip_interleaved_ac3() {
let mut parser = TrueHdParser::new();
let ac3 = make_ac3_frame();
let truehd = make_truehd_unit(200);
let mut data = ac3;
data.extend_from_slice(&truehd);
let pes = make_pes(data, Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data.len(), 200);
}
#[test]
fn continuation_pes_pts_does_not_override_au_in_progress() {
let mut parser = TrueHdParser::new();
let unit = make_truehd_unit(200);
let mid = 100;
let pes1 = make_pes(unit[..mid].to_vec(), Some(90000));
assert!(parser.parse(&pes1).is_empty(), "AU held mid-assembly");
let pes2 = make_pes(unit[mid..].to_vec(), Some(99999));
let frames = parser.parse(&pes2);
assert_eq!(frames.len(), 1);
assert_eq!(
frames[0].pts_ns,
pts_to_ns(90000),
"AU keeps the PTS of the PES that began it, not the continuation PES"
);
}
#[test]
fn new_au_after_empty_buffer_takes_new_pes_pts() {
let mut parser = TrueHdParser::new();
let f1 = parser.parse(&make_pes(make_truehd_unit(200), Some(90000)));
assert_eq!(f1.len(), 1);
assert_eq!(f1[0].pts_ns, pts_to_ns(90000));
let f2 = parser.parse(&make_pes(make_truehd_unit(200), Some(180000)));
assert_eq!(f2.len(), 1);
assert_eq!(
f2[0].pts_ns,
pts_to_ns(180000),
"new AU after empty buffer adopts the new PES PTS"
);
}
#[test]
fn zero_length_au_drains_full_header() {
let mut parser = TrueHdParser::new();
let mut data = vec![0x00, 0x00, 0x01, 0x90]; data.extend_from_slice(&make_truehd_unit(200));
let frames = parser.parse(&make_pes(data, Some(90000)));
assert_eq!(frames.len(), 1, "real unit parses after zero-length header");
assert_eq!(frames[0].data.len(), 200);
}
#[test]
fn unmappable_ac3_header_resyncs_not_stalls() {
let mut parser = TrueHdParser::new();
let mut data = vec![0x0B, 0x77, 0x00, 0x00, 0xC0, 0x00];
data.extend_from_slice(&make_truehd_unit(200));
let frames = parser.parse(&make_pes(data, Some(90000)));
assert_eq!(
frames.len(),
1,
"TrueHD AU behind a bad header is recovered"
);
assert_eq!(frames[0].data.len(), 200);
assert!(parser.buf.is_empty(), "buffer fully consumed, no stall");
}
#[test]
fn truehd_au_with_0b77_head_not_stolen_by_ac3() {
let mut parser = TrueHdParser::new();
let mut unit = vec![0u8; 5870];
unit[0] = 0x0B; unit[1] = 0x77; let frames = parser.parse(&make_pes(unit, Some(90000)));
assert_eq!(frames.len(), 1, "0x0B77-headed TrueHD AU kept whole");
assert_eq!(
frames[0].data.len(),
5870,
"AU sized by TrueHD length, not AC-3 frame size"
);
}
#[test]
fn codec_private_none() {
let parser = TrueHdParser::new();
assert!(parser.codec_private().is_none());
}
#[test]
fn truehd_channels_71_from_8ch_presentation() {
let format_info = 0x1F; assert_eq!(truehd_channels(format_info), Some(8));
}
#[test]
fn truehd_channels_51_from_6ch_presentation() {
let format_info = 0xF << 15; assert_eq!(truehd_channels(format_info), Some(6));
}
#[test]
fn truehd_channels_scan_finds_major_sync() {
let mut data = vec![0xAA, 0xBB];
data.extend_from_slice(&0xF872_6FBAu32.to_be_bytes());
data.extend_from_slice(&0x0000_001Fu32.to_be_bytes());
assert_eq!(truehd_channels_from_stream(&data), Some(8));
}
}