100% codec coverage + disc/ and aacs/ module refactors

Codec coverage (DVD + BD + UHD):
- E-AC-3 (Dolby Digital Plus): bsid detection, frame size calc — 8 tests
- DTS-HD MA/HR: extension substream (0x64582025) detection — 8 tests
- LPCM: BD header skip, raw PCM extraction — 6 tests
- DVD VobSub subtitles: passthrough parser — 5 tests
- Dolby Vision: verified RPU NAL type 62 preserved in HEVC — 1 test

Module refactors:
- disc.rs → disc/mod.rs + bluray.rs + dvd.rs + encrypt.rs
- aacs/mod.rs (1661 lines) → mod.rs (21) + keydb.rs + keys.rs + decrypt.rs
- All public APIs preserved, all tests pass

270 tests total, 0 failures.
This commit is contained in:
MattJackson
2026-04-11 17:22:18 +00:00
parent fe723a7759
commit 515f2f6bc2
14 changed files with 2414 additions and 2097 deletions
+184 -3
View File
@@ -1,12 +1,16 @@
//! DTS / DTS-HD elementary stream parser.
//!
//! DTS core syncword: 0x7FFE8001 (32 bits).
//! DTS-HD MA/HRA extension follows 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.
//! All frames are keyframes (no inter-frame dependencies).
//! Each PES packet = one frame.
use super::{pts_to_ns, CodecParser, Frame, PesPacket};
/// DTS-HD extension syncword bytes.
const DTS_HD_EXT_SYNC: [u8; 4] = [0x64, 0x58, 0x20, 0x25];
pub struct DtsParser;
impl Default for DtsParser {
@@ -27,10 +31,31 @@ impl CodecParser for DtsParser {
return Vec::new();
}
let pts_ns = pes.pts.map(pts_to_ns).unwrap_or(0);
let data = &pes.data;
// Look for a DTS-HD extension substream after the core.
// If found, include both core + extension in the output frame.
let frame_data = match find_dts_hd_ext_sync(data) {
Some(ext_offset) => {
let ext = &data[ext_offset..];
if ext.len() >= 9 {
let ext_size = dts_hd_ext_frame_size(ext);
let total_end = ext_offset + ext_size;
let end = total_end.min(data.len());
data[..end].to_vec()
} else {
// Extension header too short to parse size; include all data.
data.to_vec()
}
}
None => data.to_vec(),
};
vec![Frame {
pts_ns,
keyframe: true,
data: pes.data.clone(),
data: frame_data,
}]
}
@@ -39,6 +64,35 @@ impl CodecParser for DtsParser {
}
}
/// Find the DTS-HD extension syncword (0x64582025) in data.
/// Returns the byte offset of the sync, or None.
pub fn find_dts_hd_ext_sync(data: &[u8]) -> Option<usize> {
if data.len() < 4 {
return None;
}
for i in 0..=data.len() - 4 {
if 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]
{
return Some(i);
}
}
None
}
/// Calculate DTS-HD extension frame size from the extension header.
/// The size field is at bytes 6-8 of the extension:
/// ((ext[6] & 0x1F) << 11) | (ext[7] << 3) | (ext[8] >> 5) + 1
pub fn dts_hd_ext_frame_size(ext: &[u8]) -> usize {
debug_assert!(ext.len() >= 9);
let raw = ((ext[6] as usize & 0x1F) << 11)
| ((ext[7] as usize) << 3)
| ((ext[8] as usize) >> 5);
raw + 1
}
#[cfg(test)]
mod tests {
use super::*;
@@ -53,10 +107,137 @@ mod tests {
}
}
/// Build a DTS core frame with given payload size.
fn make_dts_core(payload_len: usize) -> Vec<u8> {
let mut data = vec![0x7F, 0xFE, 0x80, 0x01];
data.resize(4 + payload_len, 0xAA);
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();
// DTS core syncword: 7F FE 80 01 + payload
let data = vec![0x7F, 0xFE, 0x80, 0x01, 0xAA, 0xBB, 0xCC];
let pes = make_pes(data.clone(), Some(90000));
let frames = parser.parse(&pes);