AACS pipeline reshape + TrueHD metadata + central consts + clippy/fmt clean

- AACS: delete in-lib keydb parser (Step 3); boil-down primitives
  (mk_from_dk/vuk_from_mk/uk_from_vuk) + newtypes; KeySource->get_uk(ctx)+
  ResolveCtx; Unlocker->unlock()->Result<Vid,UnlockError> + AacsCertUnlocker;
  OEM bus-key gate (AacsBusKeyUnavailable); structured ResolutionTrace (Step 4).
- TrueHD: sample-rate from major-sync, Atmos label, 44.1k AU duration.
- consts: central media/format constants module; 17 duplicate const-defs
  centralized (sector/TS-packet/source-packet); mpls stream-entry + category
  codes named.
- clippy --all-targets -D warnings clean (1.86); fmt clean; 2199 lib tests.
This commit is contained in:
Matthew Jackson
2026-06-26 12:19:24 -07:00
parent 05729f5dfe
commit decb87a250
48 changed files with 2208 additions and 2031 deletions
+307 -2
View File
@@ -14,9 +14,19 @@
use super::{CodecParser, Frame, PesPacket, pts_to_ns};
/// Duration of one TrueHD access unit in nanoseconds (1/1200 second).
/// Duration of one TrueHD access unit in nanoseconds for the 48 kHz family
/// (48 / 96 / 192 kHz). `access_unit_size = 40 << (ratebits & 7)` and
/// `sample_rate = 48000 << (ratebits & 7)`; the shared shift cancels in
/// `samples_per_AU / sample_rate = 40/48000 = 1/1200 s`, so this constant is
/// exact for the whole 48 kHz family — 48, 96 and 192 kHz alike. Used as the
/// default until a major sync reveals the actual rate family.
const AU_DURATION_NS: i64 = 833_333;
/// Duration of one TrueHD access unit in nanoseconds for the 44.1 kHz family
/// (44.1 / 88.2 / 176.4 kHz): `40/44100 = 1/1102.5 s = 907_029.478… ns`. The
/// 48 kHz constant would run ~8.95 % fast on these (rare) streams.
const AU_DURATION_NS_441: i64 = 907_029;
/// Hard cap on the reassembly buffer. A valid TrueHD/MAT access unit is
/// well under 32 KiB; if the buffer grows far past that without yielding a
/// frame the stream is malformed, so we drop it and resync rather than grow
@@ -26,6 +36,12 @@ const MAX_TRUEHD_BUF: usize = 256 * 1024;
pub struct TrueHdParser {
buf: Vec<u8>,
next_pts_ns: i64,
/// Per-AU PTS increment. Defaults to the 48 kHz-family value (833_333) and
/// is refined to the 44.1 kHz-family value once the first major sync reveals
/// the actual rate. Stays at the default for streams whose major sync is not
/// yet seen (head of stream) — preserving byte-identical timing for the
/// common 48 kHz case.
au_duration_ns: i64,
}
impl Default for TrueHdParser {
@@ -39,6 +55,7 @@ impl TrueHdParser {
Self {
buf: Vec::with_capacity(32768),
next_pts_ns: 0,
au_duration_ns: AU_DURATION_NS,
}
}
@@ -185,6 +202,17 @@ impl CodecParser for TrueHdParser {
& 0xFFFF_FFFE)
== 0xF872_6FBA;
// On a major sync the 32-bit `format_info` word (immediately after
// the 4-byte sync, i.e. AU bytes 8..12) carries the rate nibble.
// Refine the per-AU PTS increment to the actual rate family. The
// 48 kHz family resolves to the unchanged 833_333 default, so the
// common case stays byte-identical; only the 44.1 kHz family shifts.
if is_major_sync && unit_bytes >= 12 {
let format_info =
u32::from_be_bytes([self.buf[8], self.buf[9], self.buf[10], self.buf[11]]);
self.au_duration_ns = truehd_au_duration_ns(format_info);
}
frames.push(Frame {
coding: None,
source: None,
@@ -194,7 +222,7 @@ impl CodecParser for TrueHdParser {
duration_ns: None,
});
self.buf.drain(..unit_bytes);
self.next_pts_ns += AU_DURATION_NS;
self.next_pts_ns += self.au_duration_ns;
}
// Bound memory on malformed input: a stream that never yields a
@@ -257,6 +285,93 @@ pub fn truehd_channels_from_stream(data: &[u8]) -> Option<u8> {
None
}
/// Real sample rate (Hz) from a TrueHD major-sync `format_info` word.
///
/// The 4-bit `ratebits` nibble sits in `format_info` bits 31..28 (the top
/// nibble), the same word `truehd_channels` reads for the channel masks. The
/// MLP rate formula is `(ratebits & 8 ? 44100 : 48000) << (ratebits & 7)`;
/// rather than evaluate it blindly this is a **strict whitelist** of the only
/// six rates that occur on real BD/UHD TrueHD. Every other code — the invalid
/// `0xF`, the formula-only `0x3`/`0xB`, and all reserved values — returns
/// `None`, so a malformed or unexpected field can never produce a wrong
/// `SamplingFrequency`; the caller falls back to its container-derived rate.
pub fn truehd_sample_rate_hz(format_info: u32) -> Option<u32> {
match (format_info >> 28) & 0xF {
0x0 => Some(48000),
0x1 => Some(96000),
0x2 => Some(192000),
0x8 => Some(44100),
0x9 => Some(88200),
0xA => Some(176400),
_ => None,
}
}
/// Per-AU PTS increment (ns) for the rate family encoded in `format_info`.
///
/// Derived from the same whitelisted rate as [`truehd_sample_rate_hz`]: the
/// 44.1 kHz family (44.1 / 88.2 / 176.4 kHz) is `907_029` ns; everything else —
/// the entire 48 kHz family AND any unrecognised rate — keeps the exact current
/// `833_333` default, so the common case and all unknown/garbage inputs are
/// byte-identical to prior behaviour.
pub fn truehd_au_duration_ns(format_info: u32) -> i64 {
match truehd_sample_rate_hz(format_info) {
Some(44100) | Some(88200) | Some(176400) => AU_DURATION_NS_441,
_ => AU_DURATION_NS,
}
}
/// First TrueHD major sync found in a demuxed elementary-stream chunk: the
/// `format_info` word plus the Atmos signal. A single scan that the per-field
/// helpers below share, so the host probes the bitstream once for channels,
/// sample rate and Atmos.
pub struct TrueHdSyncInfo {
/// The 32-bit word immediately after the 0xF8726FBA sync (channel masks +
/// rate nibble). Feed to `truehd_channels` / `truehd_sample_rate_hz`.
pub format_info: u32,
/// `num_substreams >= 4` ⟺ a 4th (Atmos object/OAMD) substream is present.
/// `num_substreams = msync[16] >> 4`, where `msync[0]` is the sync's 0xF8.
/// `None` when the AU is too short to reach that byte — never guess Atmos.
pub is_atmos: Option<bool>,
}
/// Scan a demuxed TrueHD chunk for the first major sync and return its
/// `format_info` and Atmos signal. The stream may interleave AC-3; the scan
/// advances one byte at a time and matches the sync word anywhere.
pub fn truehd_sync_info_from_stream(data: &[u8]) -> Option<TrueHdSyncInfo> {
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 format_info =
u32::from_be_bytes([data[p + 4], data[p + 5], data[p + 6], data[p + 7]]);
// num_substreams is the top nibble of the 17th sync byte (p + 16).
// .get() yields None — not a panic and not a false Atmos — when the
// AU is truncated before that byte.
let is_atmos = data.get(p + 16).map(|&b| (b >> 4) >= 4);
return Some(TrueHdSyncInfo {
format_info,
is_atmos,
});
}
p += 1;
}
None
}
/// Real sample rate (Hz) from the first major sync in a demuxed chunk, or
/// `None` if no major sync is found or its rate code is not whitelisted.
pub fn truehd_sample_rate_from_stream(data: &[u8]) -> Option<u32> {
truehd_sync_info_from_stream(data).and_then(|s| truehd_sample_rate_hz(s.format_info))
}
/// Whether the first major sync in a demuxed chunk carries an Atmos substream.
/// `None` when no major sync is found or the AU is too short to read the
/// substream count — callers must treat `None` as "not Atmos" (never label).
pub fn truehd_is_atmos_from_stream(data: &[u8]) -> Option<bool> {
truehd_sync_info_from_stream(data).and_then(|s| s.is_atmos)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -720,4 +835,194 @@ mod tests {
let f = parser.parse(&make_pes(vec![0x0B, 0x77, 0x00], Some(0)));
assert!(f.is_empty());
}
// --- #2 sample rate from the major-sync rate nibble ---
/// Build a `format_info` word with the given `ratebits` (top nibble) and a
/// 7.1 8-channel mask (ch8 = 0x1F) in the low 13 bits — exactly the layout
/// §1.A pins, so the rate nibble and the channel masks are co-located in one
/// real word.
fn format_info_with(ratebits: u32) -> u32 {
((ratebits & 0xF) << 28) | 0x1F
}
#[test]
fn sample_rate_whitelist_real_rates() {
assert_eq!(truehd_sample_rate_hz(format_info_with(0x0)), Some(48000));
assert_eq!(truehd_sample_rate_hz(format_info_with(0x1)), Some(96000));
assert_eq!(truehd_sample_rate_hz(format_info_with(0x2)), Some(192000));
assert_eq!(truehd_sample_rate_hz(format_info_with(0x8)), Some(44100));
assert_eq!(truehd_sample_rate_hz(format_info_with(0x9)), Some(88200));
assert_eq!(truehd_sample_rate_hz(format_info_with(0xA)), Some(176400));
}
#[test]
fn sample_rate_unknown_rate_falls_back_to_none() {
// 0xF is the explicit invalid code; 0x3/0xB are formula-only and not
// whitelisted; 0x7/0xE are reserved. None of them may produce a rate —
// the host must fall back to its container value, never write a wrong
// SamplingFrequency.
for bad in [0x3u32, 0x7, 0xB, 0xC, 0xD, 0xE, 0xF] {
assert_eq!(
truehd_sample_rate_hz(format_info_with(bad)),
None,
"ratebits {bad:#x} must not yield a rate"
);
}
}
#[test]
fn sample_rate_nibble_does_not_disturb_channel_decode() {
// Internal-consistency guard: with the 96 kHz nibble AND a 7.1 mask in
// the same word, the rate reads 96000 and the channels still read 8 —
// proving the rate nibble (bits 31..28) and the channel masks
// (bits 19..0) do not collide.
let fi = format_info_with(0x1);
assert_eq!(truehd_sample_rate_hz(fi), Some(96000));
assert_eq!(truehd_channels(fi), Some(8));
}
#[test]
fn sample_rate_from_stream_scans_major_sync() {
// [junk][0xF8726FBA][format_info: ratebits=0x1 (96k), ch8=0x1F]
let mut data = vec![0xAA, 0xBB];
data.extend_from_slice(&0xF872_6FBAu32.to_be_bytes());
data.extend_from_slice(&format_info_with(0x1).to_be_bytes());
assert_eq!(truehd_sample_rate_from_stream(&data), Some(96000));
}
#[test]
fn sample_rate_from_stream_none_without_sync() {
let data = vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
assert_eq!(truehd_sample_rate_from_stream(&data), None);
}
// --- #3 per-AU duration: family-aware, 48 kHz family byte-identical ---
#[test]
fn au_duration_48k_family_unchanged() {
// 48 / 96 / 192 kHz (ratebits 0x0/0x1/0x2) all keep the exact current
// 833_333 constant — the common case must never shift.
for rb in [0x0u32, 0x1, 0x2] {
assert_eq!(truehd_au_duration_ns(format_info_with(rb)), 833_333);
}
}
#[test]
fn au_duration_441k_family_is_907029() {
// 44.1 / 88.2 / 176.4 kHz (ratebits 0x8/0x9/0xA) → 907_029 ns.
for rb in [0x8u32, 0x9, 0xA] {
assert_eq!(truehd_au_duration_ns(format_info_with(rb)), 907_029);
}
}
#[test]
fn au_duration_unknown_rate_keeps_default() {
// An unrecognised/garbage rate nibble must not pick the 44.1 k value
// (note 0xF & 8 != 0): it falls back to the 833_333 default.
for rb in [0x3u32, 0x7, 0xB, 0xF] {
assert_eq!(truehd_au_duration_ns(format_info_with(rb)), 833_333);
}
}
#[test]
fn parser_44k_major_sync_sets_907029_increment() {
// Two AUs: the first carries a major sync with ratebits=0x8 (44.1 k).
// After the parser reads it, the per-AU PTS increment must be 907_029.
let mut parser = TrueHdParser::new();
let mut a1 = make_truehd_unit(200);
a1[4..8].copy_from_slice(&0xF872_6FBAu32.to_be_bytes()); // major sync
a1[8..12].copy_from_slice(&format_info_with(0x8).to_be_bytes()); // 44.1 k
let mut data = a1;
data.extend_from_slice(&make_truehd_unit(200));
let frames = parser.parse(&make_pes(data, Some(90000)));
assert_eq!(frames.len(), 2);
assert_eq!(
frames[1].pts_ns - frames[0].pts_ns,
907_029,
"44.1 k-family AU increments by 907_029 once the major sync is read"
);
}
#[test]
fn parser_48k_major_sync_keeps_833333_increment() {
// Regression: a 48 k-family (ratebits=0x0) major sync keeps the exact
// current 833_333 increment.
let mut parser = TrueHdParser::new();
let mut a1 = make_truehd_unit(200);
a1[4..8].copy_from_slice(&0xF872_6FBAu32.to_be_bytes());
a1[8..12].copy_from_slice(&format_info_with(0x0).to_be_bytes()); // 48 k
let mut data = a1;
data.extend_from_slice(&make_truehd_unit(200));
let frames = parser.parse(&make_pes(data, Some(90000)));
assert_eq!(frames.len(), 2);
assert_eq!(frames[1].pts_ns - frames[0].pts_ns, 833_333);
}
// --- #1 Atmos detection from num_substreams (msync[16] >> 4) ---
/// Build a demuxed chunk with one major sync whose 17th sync byte (offset
/// 16 from the 0xF8) has top nibble `num_substreams`. The AU is padded past
/// byte 16 so the substream count is reachable.
fn major_sync_with_substreams(num_substreams: u8) -> Vec<u8> {
let mut data = vec![0x00, 0x00]; // leading junk; scan is byte-aligned
let sync_off = data.len();
data.extend_from_slice(&0xF872_6FBAu32.to_be_bytes()); // bytes [off..off+4]
data.extend_from_slice(&format_info_with(0x0).to_be_bytes()); // format_info
// Pad up to and including byte `sync_off + 16`.
while data.len() <= sync_off + 16 {
data.push(0x00);
}
data[sync_off + 16] = (num_substreams & 0xF) << 4;
data
}
#[test]
fn atmos_true_when_four_substreams() {
// num_substreams = 4 → byte 16 = 0x40 → Atmos object substream present.
let data = major_sync_with_substreams(4);
assert_eq!(truehd_is_atmos_from_stream(&data), Some(true));
}
#[test]
fn atmos_false_when_three_substreams() {
// num_substreams = 3 (plain 7.1 TrueHD) → byte 16 = 0x30 → not Atmos.
let data = major_sync_with_substreams(3);
assert_eq!(truehd_is_atmos_from_stream(&data), Some(false));
}
#[test]
fn atmos_none_when_au_too_short_for_substream_byte() {
// Major sync present but the chunk ends before byte sync_off+16 → None,
// never a false Atmos. Sync at offset 0; only format_info follows.
let mut data = 0xF872_6FBAu32.to_be_bytes().to_vec();
data.extend_from_slice(&format_info_with(0x0).to_be_bytes()); // 8 bytes total
assert_eq!(truehd_is_atmos_from_stream(&data), None);
}
#[test]
fn atmos_none_without_major_sync() {
let data = vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
assert_eq!(truehd_is_atmos_from_stream(&data), None);
}
#[test]
fn sync_info_combines_channels_rate_and_atmos() {
// One scan yields all three facts: 7.1 channels, 96 kHz, 4 substreams.
let data = {
let mut d = vec![0x00, 0x00];
let off = d.len();
d.extend_from_slice(&0xF872_6FBAu32.to_be_bytes());
d.extend_from_slice(&format_info_with(0x1).to_be_bytes()); // 96k + 7.1
while d.len() <= off + 16 {
d.push(0x00);
}
d[off + 16] = 0x40; // 4 substreams
d
};
let info = truehd_sync_info_from_stream(&data).expect("major sync found");
assert_eq!(truehd_channels(info.format_info), Some(8));
assert_eq!(truehd_sample_rate_hz(info.format_info), Some(96000));
assert_eq!(info.is_atmos, Some(true));
}
}