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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user