Buffer audio parsers across PES boundaries

- DTS: buffer with core sync detection + frame size from header
- TrueHD: buffer with unit length field parsing
- Same pattern as AC3 fix: incomplete frames held for next PES
- When PES boundaries align (normal case), buffering is a no-op
This commit is contained in:
MattJackson
2026-04-16 19:02:23 +00:00
parent 59d4eb8854
commit 911d260695
2 changed files with 224 additions and 237 deletions
+127 -196
View File
@@ -2,16 +2,17 @@
//! //!
//! DTS core syncword: 0x7FFE8001 (32 bits). //! DTS core syncword: 0x7FFE8001 (32 bits).
//! DTS-HD MA/HRA extension syncword: 0x64582025 (32 bits), appears after the core frame. //! DTS-HD MA/HRA extension syncword: 0x64582025 (32 bits), appears after the core frame.
//! The extension contains high-resolution audio data and is appended to the core frame. //! Buffers across PES boundaries so frames spanning two PES packets
//! All frames are keyframes (no inter-frame dependencies). //! are emitted complete.
//! Each PES packet = one frame.
use super::{pts_to_ns, CodecParser, Frame, PesPacket}; use super::{pts_to_ns, CodecParser, Frame, PesPacket};
/// DTS-HD extension syncword bytes. const DTS_CORE_SYNC: [u8; 4] = [0x7F, 0xFE, 0x80, 0x01];
const DTS_HD_EXT_SYNC: [u8; 4] = [0x64, 0x58, 0x20, 0x25]; const DTS_HD_EXT_SYNC: [u8; 4] = [0x64, 0x58, 0x20, 0x25];
pub struct DtsParser; pub struct DtsParser {
buf: Vec<u8>,
}
impl Default for DtsParser { impl Default for DtsParser {
fn default() -> Self { fn default() -> Self {
@@ -21,7 +22,9 @@ impl Default for DtsParser {
impl DtsParser { impl DtsParser {
pub fn new() -> Self { pub fn new() -> Self {
Self Self {
buf: Vec::with_capacity(32768),
}
} }
} }
@@ -32,31 +35,77 @@ impl CodecParser for DtsParser {
} }
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0); let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
let data = &pes.data; self.buf.extend_from_slice(&pes.data);
// Look for a DTS-HD extension substream after the core. let data = &self.buf;
// If found, include both core + extension in the output frame. let mut frames = Vec::new();
let frame_data = match find_dts_hd_ext_sync(data) { let mut pos = 0;
Some(ext_offset) => {
let ext = &data[ext_offset..]; while pos < data.len() {
if ext.len() >= 9 { // Find DTS core sync
let ext_size = dts_hd_ext_frame_size(ext); let start = match find_sync(&data[pos..], &DTS_CORE_SYNC) {
let total_end = ext_offset + ext_size; Some(offset) => pos + offset,
let end = total_end.min(data.len()); None => break,
data[..end].to_vec()
} else {
// Extension header too short to parse size; include all data.
data.to_vec()
}
}
None => data.to_vec(),
}; };
vec![Frame { // Need at least 10 bytes for core header to get frame size
if start + 10 > data.len() {
break;
}
let core_size = dts_core_frame_size(&data[start..]);
if core_size == 0 || core_size > 32768 {
pos = start + 4;
continue;
}
if start + core_size > data.len() {
// Incomplete core frame
break;
}
// Check for DTS-HD extension after core
let mut total_size = core_size;
if start + core_size + 4 <= data.len() {
if let Some(0) = find_sync(
&data[start + core_size..start + core_size + 4],
&DTS_HD_EXT_SYNC,
) {
let ext = &data[start + core_size..];
if ext.len() >= 9 {
let ext_size = dts_hd_ext_frame_size(ext);
if start + core_size + ext_size <= data.len() {
total_size = core_size + ext_size;
}
// If ext incomplete, just emit core
}
}
}
frames.push(Frame {
pts_ns, pts_ns,
keyframe: true, keyframe: true,
data: frame_data, data: data[start..start + total_size].to_vec(),
}] });
pos = start + total_size;
}
// Keep unconsumed data
let keep_from = if pos < data.len() {
find_sync(&data[pos..], &DTS_CORE_SYNC)
.map(|o| pos + o)
.unwrap_or(data.len())
} else {
data.len()
};
if keep_from < data.len() {
self.buf = data[keep_from..].to_vec();
} else {
self.buf.clear();
}
frames
} }
fn codec_private(&self) -> Option<Vec<u8>> { fn codec_private(&self) -> Option<Vec<u8>> {
@@ -64,23 +113,27 @@ impl CodecParser for DtsParser {
} }
} }
/// Find the DTS-HD extension syncword (0x64582025) in data. fn find_sync(data: &[u8], pattern: &[u8; 4]) -> Option<usize> {
/// Returns the byte offset of the sync, or None.
pub fn find_dts_hd_ext_sync(data: &[u8]) -> Option<usize> {
if data.len() < 4 { if data.len() < 4 {
return None; return None;
} }
(0..=data.len() - 4).find(|&i| { (0..=data.len() - 4).find(|&i| data[i..i + 4] == *pattern)
data[i] == DTS_HD_EXT_SYNC[0]
&& data[i + 1] == DTS_HD_EXT_SYNC[1]
&& data[i + 2] == DTS_HD_EXT_SYNC[2]
&& data[i + 3] == DTS_HD_EXT_SYNC[3]
})
} }
/// Calculate DTS-HD extension frame size from the extension header. /// DTS core frame size from header bits.
/// The size field is at bytes 6-8 of the extension: /// fsize is at bits 46-59 (14 bits) of the header: bytes 5-7.
/// ((ext[6] & 0x1F) << 11) | (ext[7] << 3) | (ext[8] >> 5) + 1 fn dts_core_frame_size(data: &[u8]) -> usize {
if data.len() < 10 {
return 0;
}
// fsize field: 14 bits starting at bit 46
// byte 5 bits 1-0, byte 6 all 8, byte 7 bits 7-4
let fsize =
((data[5] as usize & 0x03) << 12) | ((data[6] as usize) << 4) | ((data[7] as usize) >> 4);
fsize + 1
}
/// DTS-HD extension frame size from extension header.
pub fn dts_hd_ext_frame_size(ext: &[u8]) -> usize { pub fn dts_hd_ext_frame_size(ext: &[u8]) -> usize {
if ext.len() < 9 { if ext.len() < 9 {
return 0; return 0;
@@ -90,6 +143,10 @@ pub fn dts_hd_ext_frame_size(ext: &[u8]) -> usize {
raw + 1 raw + 1
} }
pub fn find_dts_hd_ext_sync(data: &[u8]) -> Option<usize> {
find_sync(data, &DTS_HD_EXT_SYNC)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -104,164 +161,16 @@ mod tests {
} }
} }
/// Build a DTS core frame with given payload size. fn make_dts_core(size: usize) -> Vec<u8> {
fn make_dts_core(payload_len: usize) -> Vec<u8> { let fsize = size - 1;
let mut data = vec![0x7F, 0xFE, 0x80, 0x01]; let mut data = vec![0u8; size];
data.resize(4 + payload_len, 0xAA); data[0..4].copy_from_slice(&DTS_CORE_SYNC);
data[5] = (data[5] & 0xFC) | ((fsize >> 12) & 0x03) as u8;
data[6] = ((fsize >> 4) & 0xFF) as u8;
data[7] = (data[7] & 0x0F) | (((fsize & 0x0F) << 4) as u8);
data data
} }
/// Build a DTS-HD extension header + payload.
/// ext_size is the value to encode (frame size = ext_size + 1 reported by dts_hd_ext_frame_size,
/// but we encode raw = ext_size so that dts_hd_ext_frame_size returns ext_size + 1).
fn make_dts_hd_ext(raw_size_field: usize, payload_fill: u8) -> Vec<u8> {
let total = raw_size_field + 1; // the size dts_hd_ext_frame_size will return
let byte6 = ((raw_size_field >> 11) & 0x1F) as u8;
let byte7 = ((raw_size_field >> 3) & 0xFF) as u8;
let byte8 = ((raw_size_field & 0x07) << 5) as u8;
let mut data = vec![0x64, 0x58, 0x20, 0x25, 0x00, 0x00, byte6, byte7, byte8];
while data.len() < total {
data.push(payload_fill);
}
data.truncate(total);
data
}
// --- DTS-HD extension sync detection ---
#[test]
fn find_ext_sync_at_offset() {
let mut data = vec![0x7F, 0xFE, 0x80, 0x01, 0x00, 0x00];
data.extend_from_slice(&[0x64, 0x58, 0x20, 0x25]);
assert_eq!(find_dts_hd_ext_sync(&data), Some(6));
}
#[test]
fn find_ext_sync_none() {
let data = vec![0x7F, 0xFE, 0x80, 0x01, 0x00, 0x00];
assert_eq!(find_dts_hd_ext_sync(&data), None);
}
#[test]
fn find_ext_sync_at_start() {
let data = vec![0x64, 0x58, 0x20, 0x25, 0x00];
assert_eq!(find_dts_hd_ext_sync(&data), Some(0));
}
#[test]
fn find_ext_sync_too_short() {
let data = vec![0x64, 0x58, 0x20];
assert_eq!(find_dts_hd_ext_sync(&data), None);
}
// --- DTS-HD extension frame size ---
#[test]
fn ext_frame_size_basic() {
// raw_size_field = 100 → frame size = 101
let ext = make_dts_hd_ext(100, 0xBB);
assert_eq!(dts_hd_ext_frame_size(&ext), 101);
}
#[test]
fn ext_frame_size_zero() {
// raw_size_field = 0 → frame size = 1
let ext = vec![0x64, 0x58, 0x20, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(dts_hd_ext_frame_size(&ext), 1);
}
#[test]
fn ext_frame_size_large() {
// raw = 0x1F << 11 | 0xFF << 3 | 0x07 = 0xFFFF = 65535
// frame_size = 65536
let ext = vec![0x64, 0x58, 0x20, 0x25, 0x00, 0x00, 0x1F, 0xFF, 0xFF];
// byte6=0x1F, byte7=0xFF, byte8=0xFF
// (0x1F << 11) | (0xFF << 3) | (0xFF >> 5) = 63488 | 2040 | 7 = 65535
assert_eq!(dts_hd_ext_frame_size(&ext), 65536);
}
// --- parse: core + extension frame ---
#[test]
fn parse_core_plus_extension() {
let mut parser = DtsParser::new();
let core = make_dts_core(20); // 24 bytes total
let ext = make_dts_hd_ext(50, 0xCC); // 51 bytes
let mut data = core.clone();
data.extend_from_slice(&ext);
let pes = make_pes(data.clone(), Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
// Frame should include core (24) + extension (51) = 75 bytes
assert_eq!(frames[0].data.len(), 24 + 51);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
assert!(frames[0].keyframe);
}
#[test]
fn parse_core_only() {
let mut parser = DtsParser::new();
let data = make_dts_core(10);
let pes = make_pes(data.clone(), Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, data);
}
#[test]
fn parse_core_plus_extension_truncated_at_buffer_end() {
let mut parser = DtsParser::new();
let core = make_dts_core(4); // 8 bytes
// Extension claims 200 bytes but we only provide 20
let ext = make_dts_hd_ext(199, 0xDD); // wants 200 bytes
let mut data = core;
// Only append partial extension (first 20 bytes)
data.extend_from_slice(&ext[..20.min(ext.len())]);
let total_len = data.len();
let pes = make_pes(data, Some(0));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
// Should be clamped to actual data length
assert_eq!(frames[0].data.len(), total_len);
}
// --- basic tests (carried over) ---
#[test]
fn parse_basic_frame() {
let mut parser = DtsParser::new();
let data = vec![0x7F, 0xFE, 0x80, 0x01, 0xAA, 0xBB, 0xCC];
let pes = make_pes(data.clone(), Some(90000));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, data);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
#[test]
fn all_keyframes() {
let mut parser = DtsParser::new();
for i in 0..3 {
let data = vec![0x7F, 0xFE, 0x80, 0x01, i];
let pes = make_pes(data, Some(90000 * i as i64));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "DTS frame should always be keyframe");
}
}
#[test]
fn codec_private_none() {
let parser = DtsParser::new();
assert!(parser.codec_private().is_none());
}
#[test] #[test]
fn parse_empty_pes() { fn parse_empty_pes() {
let mut parser = DtsParser::new(); let mut parser = DtsParser::new();
@@ -270,11 +179,33 @@ mod tests {
} }
#[test] #[test]
fn no_pts() { fn parse_single_frame() {
let mut parser = DtsParser::new(); let mut parser = DtsParser::new();
let pes = make_pes(vec![0x7F, 0xFE, 0x80, 0x01], None); let frame = make_dts_core(512);
let pes = make_pes(frame, Some(90000));
let frames = parser.parse(&pes); let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1); assert_eq!(frames.len(), 1);
assert_eq!(frames[0].pts_ns, 0); assert_eq!(frames[0].data.len(), 512);
}
#[test]
fn parse_frame_spanning_two_pes() {
let mut parser = DtsParser::new();
let frame = make_dts_core(512);
let mid = 256;
let pes1 = make_pes(frame[..mid].to_vec(), Some(90000));
assert!(parser.parse(&pes1).is_empty());
let pes2 = make_pes(frame[mid..].to_vec(), Some(93000));
let frames = parser.parse(&pes2);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data.len(), 512);
}
#[test]
fn codec_private_none() {
let parser = DtsParser::new();
assert!(parser.codec_private().is_none());
} }
} }
+95 -39
View File
@@ -1,14 +1,18 @@
//! Dolby TrueHD / Atmos elementary stream parser. //! Dolby TrueHD / Atmos elementary stream parser.
//! //!
//! TrueHD major sync: 0xF8726FBA at a 4-byte aligned position. //! TrueHD access units are 2560-byte fixed-size units (40 per major sync).
//! Access units consist of a major sync followed by minor syncs. //! Each unit starts with a 4-byte header: [length_hi, length_lo, timestamp_hi, timestamp_lo].
//! An embedded AC3 core is in substream 0 for backward compatibility. //! Major sync: 0xF8726FBA appears within a unit.
//! All access units are keyframes. //! Buffers across PES boundaries for complete unit delivery.
//! Each PES packet = one access unit.
use super::{pts_to_ns, CodecParser, Frame, PesPacket}; use super::{pts_to_ns, CodecParser, Frame, PesPacket};
pub struct TrueHdParser; /// TrueHD access unit size (fixed).
const TRUEHD_UNIT_SIZE: usize = 2560;
pub struct TrueHdParser {
buf: Vec<u8>,
}
impl Default for TrueHdParser { impl Default for TrueHdParser {
fn default() -> Self { fn default() -> Self {
@@ -18,7 +22,9 @@ impl Default for TrueHdParser {
impl TrueHdParser { impl TrueHdParser {
pub fn new() -> Self { pub fn new() -> Self {
Self Self {
buf: Vec::with_capacity(TRUEHD_UNIT_SIZE * 4),
}
} }
} }
@@ -28,11 +34,41 @@ impl CodecParser for TrueHdParser {
return Vec::new(); return Vec::new();
} }
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0); let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
vec![Frame {
self.buf.extend_from_slice(&pes.data);
let mut frames = Vec::new();
// TrueHD units are variable-length but each starts with a 2-byte
// big-endian length field (in 16-bit words, includes the 4-byte header).
// Extract complete units from the buffer.
while self.buf.len() >= 4 {
let unit_words = ((self.buf[0] as usize) << 8) | self.buf[1] as usize;
if unit_words == 0 {
// Padding — skip 2 bytes
self.buf.drain(..2);
continue;
}
let unit_bytes = unit_words * 2;
if unit_bytes > 65536 {
// Invalid — skip 2 bytes to resync
self.buf.drain(..2);
continue;
}
if self.buf.len() < unit_bytes {
// Incomplete unit — wait for more data
break;
}
frames.push(Frame {
pts_ns, pts_ns,
keyframe: true, keyframe: true,
data: pes.data.clone(), data: self.buf[..unit_bytes].to_vec(),
}] });
self.buf.drain(..unit_bytes);
}
frames
} }
fn codec_private(&self) -> Option<Vec<u8>> { fn codec_private(&self) -> Option<Vec<u8>> {
@@ -54,35 +90,12 @@ mod tests {
} }
} }
#[test] fn make_truehd_unit(size_bytes: usize) -> Vec<u8> {
fn parse_basic_frame() { let words = size_bytes / 2;
let mut parser = TrueHdParser::new(); let mut data = vec![0u8; size_bytes];
// TrueHD major sync: F8 72 6F BA (at 4-byte aligned position) + payload data[0] = (words >> 8) as u8;
let data = vec![0xF8, 0x72, 0x6F, 0xBA, 0x01, 0x02, 0x03, 0x04]; data[1] = (words & 0xFF) as u8;
let pes = make_pes(data.clone(), Some(90000)); data
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, data);
assert_eq!(frames[0].pts_ns, 1_000_000_000);
}
#[test]
fn all_keyframes() {
let mut parser = TrueHdParser::new();
for i in 0..3 {
let data = vec![0xF8, 0x72, 0x6F, 0xBA, i];
let pes = make_pes(data, Some(90000 * i as i64));
let frames = parser.parse(&pes);
assert_eq!(frames.len(), 1);
assert!(frames[0].keyframe, "TrueHD frame should always be keyframe");
}
}
#[test]
fn codec_private_none() {
let parser = TrueHdParser::new();
assert!(parser.codec_private().is_none());
} }
#[test] #[test]
@@ -91,4 +104,47 @@ mod tests {
let pes = make_pes(Vec::new(), Some(0)); let pes = make_pes(Vec::new(), Some(0));
assert!(parser.parse(&pes).is_empty()); 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_in_one_pes() {
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);
}
#[test]
fn codec_private_none() {
let parser = TrueHdParser::new();
assert!(parser.codec_private().is_none());
}
} }