test: constrain MP4 composition timing, MLP substream directory, and codec-private absence
Mutation testing over src/mux/. No production change — 49 survivors killed, all proven red before green. The MP4 composition-time chain was entirely unconstrained: VideoTiming::ctts, build_ctts and parse_ctts could each return a constant and the suite stayed green. Confirmed on HEAD: build_ctts -> vec![] passes all 1,220 mux tests. A demuxed B-frame title presenting in decode order would have shipped. The cause is a test whose name asserts coverage its body does not deliver — stts_and_ctts_expand builds an stts box and never touches ctts, and write_then_read_round_trip asserts sample sizes and keyframe flags but not one PTS. Same shape as the set_speed forwarding finding, different disguise. mlp_num_substreams / mlp_substr_header_size: every TrueHD fixture in the crate uses one substream and no extraword, so both could return a constant and agree with all of them. These position mlp_parity_ok's window over the AU header, so a constant mis-windows the parity check on exactly the multi-substream AUs that carry 7.1 and Atmos. CodecPrivate absent vs empty: mkv.rs writes Some(bytes) verbatim and omits the element on None (RFC 9559 5.1.4.1.24), so a zero-length Some emits a track header asserting the config IS empty. Four parsers could return Some(vec![]) before any frame. Also: mandatory ISO/IEC 14496-12 boxes (tkhd, vmhd, smhd, dinf, mdhd) could each build empty; HEVC num_extra_slice_header_bits (H.265 7.3.2.3) was never non-zero in any fixture, so the slice-type offset skip was unexercised; chapter names from the disc go straight into <ChapterString> and the & escape must run first; a stray 0x47 in a payload must not latch a TS resync. Documented as equivalent rather than killed: CodecParser::flush and the three parser flush bodies that differ from the mutant only by a tracing call, and DropTally::log_summary.
This commit is contained in:
@@ -1370,6 +1370,73 @@ mod tests {
|
||||
assert_eq!(p[0].data, vec![0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
}
|
||||
|
||||
/// `is_nav()` exists to separate the ONE unmappable stream a DVD is expected
|
||||
/// to contain — private_stream_2 (0xBF), the PCI/DSI navigation packs
|
||||
/// (ISO/IEC 13818-1 Table 2-22) — from every other packet whose `dvd_pid()`
|
||||
/// comes back `None`, which is an unexpected, possibly-lost real stream. The
|
||||
/// mux loops use the distinction to choose between a silent tally and a
|
||||
/// per-packet WARN, so collapsing it to a constant either buries a genuine
|
||||
/// stream loss in the nav tally, or floods the log with one warning per
|
||||
/// navigation pack on every DVD ever ripped.
|
||||
///
|
||||
/// The invariant that ties the two together: `is_nav()` may only ever be true
|
||||
/// where `dvd_pid()` is `None` — a packet that routes to a real track must
|
||||
/// never be silently classified as navigation.
|
||||
#[test]
|
||||
fn only_private_stream_2_is_navigation_and_never_a_routable_stream() {
|
||||
// Demux a program stream carrying, in order: a navigation pack, MPEG-2
|
||||
// video, an AC-3 audio substream, and an MPEG audio stream (unmappable on
|
||||
// DVD, but NOT navigation).
|
||||
let mut demuxer = PsDemuxer::new();
|
||||
let mut data = Vec::new();
|
||||
// private_stream_2: no PES extension, payload follows the 6-byte prefix.
|
||||
data.extend_from_slice(&[0x00, 0x00, 0x01, 0xBF, 0x00, 0x02, 0x00, 0x01]);
|
||||
// video 0xE0
|
||||
data.extend_from_slice(&[
|
||||
0x00, 0x00, 0x01, 0xE0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x11, 0x22,
|
||||
]);
|
||||
// private_stream_1 with AC-3 sub-stream 0x80 (4 bytes of substream header
|
||||
// follow the sub-id on DVD).
|
||||
data.extend_from_slice(&[
|
||||
0x00, 0x00, 0x01, 0xBD, 0x00, 0x0A, 0x80, 0x00, 0x00, 0x80, 0x01, 0x00, 0x03, 0x00,
|
||||
0xAA, 0xBB,
|
||||
]);
|
||||
// MPEG audio 0xC0
|
||||
data.extend_from_slice(&[
|
||||
0x00, 0x00, 0x01, 0xC0, 0x00, 0x05, 0x80, 0x00, 0x00, 0x33, 0x44,
|
||||
]);
|
||||
data.extend_from_slice(&PROGRAM_END);
|
||||
|
||||
let packets = demuxer.feed(&data);
|
||||
assert_eq!(packets.len(), 4, "four PES packets demuxed");
|
||||
|
||||
let nav: Vec<u8> = packets
|
||||
.iter()
|
||||
.filter(|p| p.is_nav())
|
||||
.map(|p| p.stream_id)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
nav,
|
||||
vec![0xBF],
|
||||
"exactly the private_stream_2 pack is navigation"
|
||||
);
|
||||
|
||||
for p in &packets {
|
||||
if p.is_nav() {
|
||||
assert_eq!(
|
||||
p.dvd_pid(),
|
||||
None,
|
||||
"a navigation pack must not also route to a track"
|
||||
);
|
||||
}
|
||||
}
|
||||
// The MPEG-audio packet is equally unroutable on DVD, yet must NOT be
|
||||
// absorbed into the nav tally — that is the distinction being drawn.
|
||||
let mpa = packets.iter().find(|p| p.stream_id == 0xC0).unwrap();
|
||||
assert_eq!(mpa.dvd_pid(), None, "MPEG audio is unmappable on DVD");
|
||||
assert!(!mpa.is_nav(), "...but it is a lost stream, not navigation");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_start_code_is_skipped_not_parsed() {
|
||||
// A start code with an ID outside the known PS-layer set
|
||||
|
||||
Reference in New Issue
Block a user