mux: fix DTS core-header false-drops + close TrueHD/mux gate coverage

DTS core decodability gate (core_header_drop_reason) — full ETSI TS 102 114
spec-conformance sweep against ffmpeg ff_dca_parse_core_frame_header and
dcadec parse_frame_header:

- deficit_samples: only require ==32 for NORMAL frames (FTYPE==1). A
  TERMINATION frame (FTYPE==0, the last frame of a stream) legitimately
  carries fewer and is fully decodable; the old unconditional check dropped
  it on every stream that ends on one — a guaranteed per-track silence gap.
  Matches ffmpeg (normal_frame && deficit != DCA_PCMBLOCK_SAMPLES) and
  dcadec (branches on normal_frame).
- reserved bit (after RATE): both reference decoders SKIP it (ffmpeg
  skip_bits1, dcadec bits_skip1 "Reserved field") and never reject on it.
  Rejecting was a false-drop that silenced any real stream whose encoder
  set the bit. Relaxed to read-and-discard; DropReason::ReservedBit removed.

Swept and confirmed spec-correct as-is (no change): npcmblocks multiple-of-8,
frame_size>=96, audio_mode>=16 (ffmpeg-permissive), sample-rate validity
table (matches avpriv_dca_sample_rates incl 96k/192k at 14/15), LFE flag==3
invalid, PCMR bits table (matches dcadec sample_res {16,16,20,20,0,24,24,0}).
Bit-read order verified field-by-field against dcadec. bit_rate is left
unvalidated (lenient, never-false-drop direction) as before.

Tests: termination frame with small deficit is kept; normal frame with bad
deficit is dropped; reserved-bit-set frame is kept. make_bad_dts_core now
uses an invalid LFE flag (duration-neutral) instead of the relaxed reserved
bit.

TrueHD: add coverage for the EXTENDED major-sync header CRC path (ms[25]&1,
mshdr=28+2+2n) — previously zero-tested, the exact path a shipped endianness
bug once used to silently drop whole 7.1/Atmos tracks. Trailer is an
independently-computed oracle (separate CRC-16/0x2D, anchored to the 0x4FF7
catalogue value, NOT crc16_mlp), stored little-endian; test asserts accept,
body-corruption reject, and big-endian-trailer reject.

mux driver: extract the finish completion mapping into pure mux_run_completed
so the finalize_failed -> completed=false branch (reachable only via real
write-thread wedge timing) is unit-tested; add an out-of-range
MuxInput::Session title_index test asserting a clean Error::MuxTrackRange
(E9011) instead of a panic.
This commit is contained in:
Matthew Jackson
2026-07-24 10:28:21 -07:00
parent eb0aa3556e
commit 967d0ac77e
3 changed files with 295 additions and 29 deletions
+97
View File
@@ -835,6 +835,103 @@ mod tests {
// `corrupt_major_sync_drops_forward_to_next_valid`).
}
/// Independent bitwise CRC-16 (poly 0x002D, init 0, MSB-first) — a SEPARATE
/// oracle from `crc16_mlp`, so a fixture built with it is not tautological
/// with the validator under test. Anchored to the catalogue check value
/// (0x4FF7 for "123456789") so the oracle itself is proven correct without
/// reference to the code under test.
fn ref_crc16_2d(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &b in data {
crc ^= (b as u16) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 {
(crc << 1) ^ 0x002D
} else {
crc << 1
};
}
}
crc
}
#[test]
fn extended_major_sync_crc_validates_and_rejects() {
// COVERAGE GAP (the endianness bug that once "silently dropped the whole
// track" on real 7.1/Atmos): the EXTENDED major-sync header path
// (ms[25]&1 set, mshdr = 28 + 2 + 2*n) had ZERO test coverage — every
// other fixture builds only the basic 28-byte header. Build an extended
// header whose trailer is an INDEPENDENTLY-computed oracle (ref_crc16_2d,
// NOT crc16_mlp) stored LITTLE-ENDIAN, and assert the validator accepts
// it, rejects a body corruption, and rejects the same trailer stored
// big-endian (which is exactly the endianness-mix regression).
assert_eq!(
ref_crc16_2d(b"123456789"),
0x4FF7,
"oracle anchored to catalogue"
);
// n = 3 extension words → mshdr = 28 + 2 + 2*3 = 36.
let n = 3usize;
let mshdr = 28 + 2 + 2 * n;
assert_eq!(mshdr, 36);
let mut ms = vec![0u8; 40]; // slack past the 36-byte header
// Non-trivial, varied body so the CRC is a meaningful function of it.
for (i, b) in ms.iter_mut().enumerate().take(mshdr - 4) {
*b = (0x37u8).wrapping_add((i as u8).wrapping_mul(0x53));
}
ms[25] |= 1; // extension flag → selects the extended header size
ms[26] = (ms[26] & 0x0F) | ((n as u8) << 4); // extension word count in high nibble
// The 2-byte "penultimate" word (between the CRC-covered body and the
// trailer). Chosen non-zero and non-palindromic so the LE/BE distinction
// is observable.
ms[mshdr - 4] = 0x12;
ms[mshdr - 3] = 0x34;
// Oracle: checksum16 = crc16_2D(body).swap_bytes() ^ le16(penultimate),
// computed with the INDEPENDENT ref CRC, then stored LITTLE-ENDIAN.
let le_word = u16::from_le_bytes([ms[mshdr - 4], ms[mshdr - 3]]);
let trailer = ref_crc16_2d(&ms[..mshdr - 4]).swap_bytes() ^ le_word;
ms[mshdr - 2] = (trailer & 0xFF) as u8;
ms[mshdr - 1] = (trailer >> 8) as u8;
assert_ne!(
ms[mshdr - 2],
ms[mshdr - 1],
"trailer bytes must differ so the LE/BE swap below is a real distinction"
);
// The extended header size is computed from ms[25]/ms[26].
assert_eq!(
mlp_major_sync_header_size(&ms),
Some(mshdr),
"extended header size = 28 + 2 + 2*n"
);
// The validator accepts the independently-built extended major sync.
assert!(
mlp_major_sync_crc_ok(&ms, mshdr),
"valid extended major-sync checksum must validate"
);
// A single corrupted body byte must be rejected.
let mut corrupt = ms.clone();
corrupt[10] ^= 0xFF;
assert!(
!mlp_major_sync_crc_ok(&corrupt, mshdr),
"a corrupted extended major sync must be rejected"
);
// The endianness regression: the SAME checksum stored big-endian must be
// rejected. A validator that reads the trailer big-endian (the shipped
// bug) would instead accept this and reject the correct LE form above.
let mut swapped = ms.clone();
swapped.swap(mshdr - 2, mshdr - 1);
assert!(
!mlp_major_sync_crc_ok(&swapped, mshdr),
"a big-endian-stored trailer must be rejected (little-endian is load-bearing)"
);
}
#[test]
fn parity_failure_is_dropped() {
// A normal AU whose header parity is broken (after a major sync sets