audit: void empty-timeline duration, cover sniff overlap
Round-11 findings from the 10-phase release audit (no real HIGH): - When a no-declared-duration source (HD-DVD) muxes a degenerate single frame at tick 0 with no per-frame duration, max_block_ticks stays 0 and the reserved DURATION placeholder was left as a literal 0.0 (players read that as a zero-length file). Void the element instead, so the Segment omits DURATION as an unknown-duration source did before the back-patch. - Add a regression test for the sniff_video_codec overlap fix (a picture_start_code whose payload begins 00 00 followed by a real start code) so the i+=4 marker skip can't silently regress to i+=3.
This commit is contained in:
@@ -708,6 +708,22 @@ mod tests {
|
||||
// A slice/picture-only sample (no SPS/sequence) is indeterminate.
|
||||
assert_eq!(sniff_video_codec(&[0x00, 0x00, 0x01, 0x61, 0x9A]), None);
|
||||
assert_eq!(sniff_video_codec(&[0xDE, 0xAD, 0xBE, 0xEF]), None);
|
||||
|
||||
// Overlap regression: a picture_start_code (0x00) whose payload begins
|
||||
// with 00 00 must advance a full 4 bytes so the code byte isn't re-read
|
||||
// as the start of a new marker. Here the picture is followed by a real
|
||||
// MPEG-2 sequence header — the scan must reach it cleanly and return
|
||||
// Mpeg2 (and, critically, not be confused by the 1-byte overlap).
|
||||
assert_eq!(
|
||||
sniff_video_codec(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xB3, 0x2D]),
|
||||
Some(Codec::Mpeg2)
|
||||
);
|
||||
// A lone picture_start_code with a 00-heavy payload and no following real
|
||||
// start code stays indeterminate (the overlap must not fabricate one).
|
||||
assert_eq!(
|
||||
sniff_video_codec(&[0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1365,6 +1365,19 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
self.writer.seek(std::io::SeekFrom::Start(pos))?;
|
||||
self.writer
|
||||
.write_all(&(self.max_block_ticks as f64).to_be_bytes())?;
|
||||
} else {
|
||||
// The timeline never advanced past tick 0 (a degenerate
|
||||
// single-frame recovery at t=0 with no per-frame duration): we
|
||||
// can't derive a runtime, so DON'T leave a literal DURATION=0.0
|
||||
// (players read that as a zero-length/corrupt file). Void the
|
||||
// whole 11-byte DURATION element (ID 2 + size 1 + 8-byte payload)
|
||||
// so the Segment simply omits it, as an unknown-duration source
|
||||
// did before the back-patch. `pos` is the payload start (+3 from
|
||||
// the element start), so back up 3.
|
||||
self.writer.seek(std::io::SeekFrom::Start(pos - 3))?;
|
||||
ebml::write_id(&mut self.writer, ebml::VOID)?;
|
||||
ebml::write_size(&mut self.writer, 9)?; // 11 - 1 (Void id) - 1 (size)
|
||||
self.writer.write_all(&[0u8; 9])?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2907,6 +2920,23 @@ mod tests {
|
||||
.map(|b| f64::from_be_bytes(b.try_into().unwrap()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_placeholder_voided_when_timeline_never_advances() {
|
||||
// Degenerate recovery: a source with no declared duration muxes exactly
|
||||
// one keyframe at tick 0 with no per-frame duration, so max_block_ticks
|
||||
// stays 0. The reserved DURATION placeholder must be VOIDED (element
|
||||
// omitted) rather than left as a bogus 0.0 that players read as a
|
||||
// zero-length file.
|
||||
let tracks = [make_video_track()];
|
||||
let one_frame = vec![(0usize, 0i64, true, vec![0xAAu8; 16])];
|
||||
let (data, _) = mux_to_bytes(&tracks, &[], &one_frame);
|
||||
assert_eq!(
|
||||
find_duration_ticks(&data),
|
||||
None,
|
||||
"no DURATION element (placeholder voided), not a 0.0 duration"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_backpatched_from_timeline_when_source_gives_none() {
|
||||
// `mux_to_bytes` muxes with `duration_secs = 0.0` (as an HD-DVD title
|
||||
|
||||
Reference in New Issue
Block a user