v1.0.0-rc.1

CSS keyless decrypt (Stevenson), AACS 1.0/2.0/2.1, MPEG-2 DVD, multi-OS SCSI, multipass recovery, mux highway, audit hardening
This commit is contained in:
Matthew Jackson
2026-06-21 21:06:07 -07:00
parent e8bb6225ac
commit 5941c059c6
53 changed files with 7439 additions and 2340 deletions
+432 -52
View File
@@ -62,11 +62,11 @@ impl MkvTrack {
/// Dolby Vision layer.
pub fn video(v: &VideoStream) -> Self {
let codec_id = match v.codec {
Codec::H264 => "V_MPEG4/ISO/AVC",
Codec::Hevc => "V_MPEGH/ISO/HEVC",
Codec::Vc1 => "V_MS/VFW/FOURCC",
Codec::Mpeg2 => "V_MPEG2",
_ => "V_MPEG2",
Codec::H264 => ebml::CODEC_H264,
Codec::Hevc => ebml::CODEC_HEVC,
Codec::Vc1 => ebml::CODEC_VC1,
Codec::Mpeg2 => ebml::CODEC_MPEG2,
_ => ebml::CODEC_MPEG2,
};
let (w, h) = v.resolution.pixels();
let (num, den) = v.frame_rate.as_fraction();
@@ -132,12 +132,12 @@ impl MkvTrack {
// lossless MA / HRA payload bytes are unchanged, only the
// container codec-ID string differs.
let codec_id = match a.codec {
Codec::Ac3 => "A_AC3",
Codec::Ac3Plus => "A_EAC3",
Codec::TrueHd => "A_TRUEHD",
Codec::DtsHdMa | Codec::DtsHdHr | Codec::Dts => "A_DTS",
Codec::Lpcm => "A_PCM/INT/BIG",
_ => "A_AC3",
Codec::Ac3 => ebml::CODEC_AC3,
Codec::Ac3Plus => ebml::CODEC_EAC3,
Codec::TrueHd => ebml::CODEC_TRUEHD,
Codec::DtsHdMa | Codec::DtsHdHr | Codec::Dts => ebml::CODEC_DTS,
Codec::Lpcm => ebml::CODEC_PCM_BE,
_ => ebml::CODEC_AC3,
};
let sr = a.sample_rate.hz();
let ch = a.channels.count();
@@ -174,8 +174,8 @@ impl MkvTrack {
/// CodecPrivate. The forced-display flag is propagated from the stream.
pub fn subtitle(s: &SubtitleStream) -> Self {
let codec_id = match s.codec {
Codec::DvdSub => "S_VOBSUB",
_ => "S_HDMV/PGS",
Codec::DvdSub => ebml::CODEC_VOBSUB,
_ => ebml::CODEC_PGS,
};
Self {
track_type: ebml::TRACK_TYPE_SUBTITLE,
@@ -229,6 +229,15 @@ pub struct MkvMuxer<W: Write + Seek> {
/// non-monotonic DTS, and some audio PES PTS land on the same millisecond
/// (or tick back 1ms from rounding).
last_pts_ms: std::collections::HashMap<usize, i64>,
/// Per-track-index flag: true if the track is video. The strictly-monotonic
/// block-timestamp nudge must be skipped for EVERY video track, not just
/// track 0 — a title can carry a second video track (e.g. a Dolby Vision
/// enhancement layer at index 1) whose B-frame PTS is just as legitimately
/// non-monotonic. Keying the exemption on track type (not index) keeps that
/// EL's true PTS instead of clobbering it to prev+1ms.
track_is_video: Vec<bool>,
/// Cross-clip timeline-continuity corrector (clip-boundary PTS rebasing).
continuity: TimelineContinuity,
cues: Vec<CuePoint>,
frame_count: u64,
/// Frames handed to `write_frame` that were dropped because no cluster was
@@ -257,6 +266,113 @@ const MAX_BLOCK_REL_MS: i64 = i16::MAX as i64;
/// Minimum block-relative timestamp expressible in the signed 16-bit field.
const MIN_BLOCK_REL_MS: i64 = i16::MIN as i64;
/// A backward PTS step larger than this is treated as a clip-boundary
/// discontinuity (a non-seamless BD clip / dual-layer-break where the source
/// PES PTS resets), NOT as B-frame reorder. HEVC/H.264 reorder depth tops out
/// around 16 frames (<1s at 24 fps); 3s sits comfortably above any legitimate
/// reorder window and far below any real clip's duration, so it never
/// false-triggers within a clip.
const DISCONTINUITY_BACKSTEP_NS: i64 = 3_000_000_000;
/// Sub-frame gap inserted after a rebased discontinuity so the first frame of
/// the new clip lands strictly after the previous timeline high (1 ms).
const DISCONTINUITY_GAP_NS: i64 = 1_000_000;
/// Global timeline-continuity corrector. freemkv reads a BD title's clips as
/// one concatenated sector stream (clip boundaries / mpls connection_condition
/// are not plumbed to the mux), so at a non-seamless boundary the source PES
/// PTS jumps backward. Left uncorrected, that produces a sustained band of
/// non-monotonic block timestamps (ffmpeg then derives non-monotonic DTS).
///
/// A single running `offset_ns` is applied to EVERY track, so the concatenated
/// clips form one monotonic timeline AND A/V sync is preserved (all tracks at a
/// boundary shift by the same amount). It is global, not per-track: a clip
/// boundary resets every stream together by the same delta.
///
/// The demuxer interleaves the tracks, so at a boundary the streams do NOT all
/// reset on the same frame — a lagging audio/PGS frame from the just-ended
/// clip's tail can arrive AFTER the next clip's video has already reset the
/// epoch. Such a "straggler" carries an old-epoch raw PTS; adding the new
/// offset to it would fling it far past the frontier and ratchet the whole
/// timeline away (the regression that broke everything after the first clip
/// boundary). It is detected as a forward spike and remapped with the PREVIOUS
/// epoch's offset so it lands at its true position near the seam, without
/// advancing the frontier or the offset.
struct TimelineContinuity {
/// Offset (ns) added to raw PTS for the CURRENT epoch.
offset_ns: i64,
/// Offset (ns) of the immediately previous epoch — used to remap stragglers
/// (old-clip frames interleaved across the boundary).
prev_offset_ns: i64,
/// Highest adjusted PTS (ns) accepted onto the timeline so far — the running
/// frontier. `None` until the first frame. Stragglers never advance it.
high_ns: Option<i64>,
}
impl TimelineContinuity {
fn new() -> Self {
Self {
offset_ns: 0,
prev_offset_ns: 0,
high_ns: None,
}
}
/// Map a raw PES PTS (ns) onto the continuous output timeline.
///
/// - **Backward jump > `DISCONTINUITY_BACKSTEP_NS`** vs the frontier =
/// clip-boundary reset: open a new epoch (save the old offset, bump the
/// offset so this frame continues just after the frontier).
/// - **Forward spike > `DISCONTINUITY_BACKSTEP_NS` past the frontier** = a
/// straggler from the previous clip arriving interleaved after the
/// boundary: remap with `prev_offset_ns` so it lands near the seam, and do
/// NOT advance the frontier or the offset (this is what prevents the
/// ratchet). A legitimate per-track gap (e.g. a subtitle absent for
/// minutes) is NOT misread as a straggler: video keeps the frontier
/// current, so the resuming frame lands at the frontier, not beyond it.
/// - **Everything else** (normal progression + sub-threshold B-frame
/// reorder dips) passes through with the current offset, preserving PTS.
fn adjust(&mut self, raw_pts_ns: i64) -> i64 {
let Some(high) = self.high_ns else {
let adj = raw_pts_ns.saturating_add(self.offset_ns);
self.high_ns = Some(adj);
return adj;
};
let adj = raw_pts_ns.saturating_add(self.offset_ns);
if adj < high - DISCONTINUITY_BACKSTEP_NS {
// Clip-boundary reset: continue just after the frontier; remember the
// previous offset so this clip's lagging tail frames remap correctly.
self.prev_offset_ns = self.offset_ns;
let bump = (high - adj).saturating_add(DISCONTINUITY_GAP_NS);
self.offset_ns = self.offset_ns.saturating_add(bump);
let adj2 = raw_pts_ns.saturating_add(self.offset_ns);
self.high_ns = Some(high.max(adj2));
adj2
} else if adj > high + DISCONTINUITY_BACKSTEP_NS && {
// A straggler from the just-ended clip maps, under the PREVIOUS
// epoch's offset, into the TOP of that epoch — at most the frontier,
// and no more than one backstep below it (it is the clip's tail,
// delivered late by the interleaver). Both bounds matter:
// - `<= high` rules out a genuine large forward jump (it maps ABOVE
// the frontier under either offset).
// - `>= high - BACKSTEP` rules out a genuine NEW-clip frame whose
// low raw PTS also maps below the frontier (that frame belongs to
// the new epoch and must be rebased forward, not remapped back).
let prev_mapped = raw_pts_ns.saturating_add(self.prev_offset_ns);
prev_mapped <= high && prev_mapped >= high - DISCONTINUITY_BACKSTEP_NS
} {
// Straggler: remap to its true seam position with the previous
// offset; leave the frontier and offset untouched (prevents the
// ratchet). A real forward jump / new-clip frame falls through to the
// normal branch and is rebased there.
raw_pts_ns.saturating_add(self.prev_offset_ns)
} else {
// Normal progression / sub-threshold B-frame reorder: keep true PTS.
self.high_ns = Some(high.max(adj));
adj
}
}
}
/// Force a per-track block timestamp to be strictly later than the previous one
/// written for that track. `prev` is the last timestamp for the track (`None`
/// for the first frame). Fixes non-monotonic DTS: some audio PES PTS truncate to
@@ -271,7 +387,7 @@ fn monotonic_ts(prev: Option<i64>, pts_ms: i64) -> i64 {
}
/// Per-track block timestamp. The strictly-monotonic nudge is applied to
/// AUDIO/SUBTITLE tracks only; VIDEO (track 0) is returned UNCHANGED.
/// AUDIO/SUBTITLE tracks only; ALL VIDEO tracks are returned UNCHANGED.
///
/// With B-frames, a video frame's presentation PTS is legitimately
/// non-monotonic in decode/storage order (a B-frame sits between its anchors,
@@ -282,8 +398,14 @@ fn monotonic_ts(prev: Option<i64>, pts_ms: i64) -> i64 {
/// SimpleBlock permits non-monotonic block timestamps (signed block-relative
/// offsets), so video keeps its true PES PTS; only no-reorder tracks (audio,
/// subtitles), where a same-millisecond collision IS a real defect, get nudged.
fn block_ts(track_idx: usize, prev: Option<i64>, pts_ms: i64) -> i64 {
if track_idx == 0 {
///
/// The exemption is keyed on `is_video` (track type), NOT a track index: a
/// title can carry more than one video track — e.g. a Dolby Vision enhancement
/// layer at index 1 — and every one must keep its true PTS. Keying on
/// `track_idx == 0` clamped the EL and reintroduced the exact non-monotonic-DTS
/// warning this exemption exists to prevent.
fn block_ts(is_video: bool, prev: Option<i64>, pts_ms: i64) -> i64 {
if is_video {
pts_ms
} else {
monotonic_ts(prev, pts_ms)
@@ -371,8 +493,11 @@ impl<W: Write + Seek> MkvMuxer<W> {
ebml::write_float(&mut writer, ebml::DURATION, duration_secs * 1000.0)?;
// in ms
}
ebml::write_string(&mut writer, ebml::MUXING_APP, "freemkv")?;
ebml::write_string(&mut writer, ebml::WRITING_APP, "freemkv")?;
// Stamp the freemkv version so any muxed file is traceable to the build
// that produced it (MediaInfo "Writing application"/"library").
const FREEMKV_MUX_APP: &str = concat!("freemkv ", env!("CARGO_PKG_VERSION"));
ebml::write_string(&mut writer, ebml::MUXING_APP, FREEMKV_MUX_APP)?;
ebml::write_string(&mut writer, ebml::WRITING_APP, FREEMKV_MUX_APP)?;
if let Some(t) = title {
ebml::write_string(&mut writer, ebml::TITLE, t)?;
}
@@ -509,6 +634,11 @@ impl<W: Write + Seek> MkvMuxer<W> {
cluster_ts_ms: 0,
base_pts_ms: None,
last_pts_ms: std::collections::HashMap::new(),
track_is_video: tracks
.iter()
.map(|t| t.track_type == ebml::TRACK_TYPE_VIDEO)
.collect(),
continuity: TimelineContinuity::new(),
cues: Vec::new(),
frame_count: 0,
dropped_pre_cluster: 0,
@@ -534,6 +664,13 @@ impl<W: Write + Seek> MkvMuxer<W> {
data: &[u8],
duration_ns: Option<u64>,
) -> io::Result<()> {
// Map the raw PES PTS onto the continuous output timeline FIRST, before
// any base/cluster math: freemkv concatenates a title's BD clips as one
// sector stream, so a non-seamless clip / layer-break boundary arrives
// here as a large backward PTS jump. Rebasing it (a global offset across
// all tracks, A/V-sync-preserving) keeps the boundary from becoming a
// band of non-monotonic block timestamps. No-op for single-clip titles.
let pts_ns = self.continuity.adjust(pts_ns);
let raw_ms = pts_ns / 1_000_000;
// Cluster boundaries normally coincide with a video keyframe so every
@@ -582,7 +719,8 @@ impl<W: Write + Seek> MkvMuxer<W> {
// POC and finds them colliding ("non monotonically increasing dts").
// Matroska SimpleBlock permits non-monotonic block timestamps (negative
// block-relative offsets), so leave the true PES PTS intact for video.
let pts_ms = block_ts(track_idx, self.last_pts_ms.get(&track_idx).copied(), pts_ms);
let is_video = self.track_is_video.get(track_idx).copied().unwrap_or(false);
let pts_ms = block_ts(is_video, self.last_pts_ms.get(&track_idx).copied(), pts_ms);
let needs_new_cluster = !self.cluster_open
|| (is_video_key && (pts_ms - self.cluster_ts_ms) >= CLUSTER_DURATION_MS);
@@ -816,7 +954,7 @@ mod tests {
fn make_video_track() -> MkvTrack {
MkvTrack {
track_type: ebml::TRACK_TYPE_VIDEO,
codec_id: "V_MPEG4/ISO/AVC",
codec_id: ebml::CODEC_H264,
language: "und".into(),
name: String::new(),
codec_private: Some(vec![0x00, 0x01, 0x02, 0x03]),
@@ -841,7 +979,7 @@ mod tests {
fn make_audio_track() -> MkvTrack {
MkvTrack {
track_type: ebml::TRACK_TYPE_AUDIO,
codec_id: "A_AC3",
codec_id: ebml::CODEC_AC3,
language: "eng".into(),
name: "English".into(),
codec_private: None,
@@ -1003,17 +1141,17 @@ mod tests {
#[test]
fn block_ts_exempts_video_from_monotonic_nudge() {
// VIDEO (track 0) keeps its true PTS even when non-monotonic in storage
// order — a B-frame whose presentation PTS sits below the frame stored
// before it must NOT be nudged to prev+1ms (that clobbering is what
// produced the "non monotonically increasing dts" flood on decode).
// VIDEO keeps its true PTS even when non-monotonic in storage order — a
// B-frame whose presentation PTS sits below the frame stored before it
// must NOT be nudged to prev+1ms (that clobbering is what produced the
// "non monotonically increasing dts" flood on decode).
assert_eq!(
block_ts(0, Some(1040), 1000),
block_ts(true, Some(1040), 1000),
1000,
"video B-frame PTS preserved"
);
assert_eq!(
block_ts(0, Some(1000), 1000),
block_ts(true, Some(1000), 1000),
1000,
"video dup-ms PTS preserved"
);
@@ -1024,23 +1162,262 @@ mod tests {
let out: Vec<i64> = gop
.iter()
.map(|&p| {
let t = block_ts(0, prev, p);
let t = block_ts(true, prev, p);
prev = Some(t);
t
})
.collect();
assert_eq!(out, gop, "video timestamps must be left exactly as-is");
// AUDIO/SUBTITLE (track != 0) still get the strictly-monotonic nudge —
// a same-ms collision there is a real defect.
assert_eq!(block_ts(1, Some(1000), 1000), 1001, "audio dup-ms nudged");
// AUDIO/SUBTITLE still get the strictly-monotonic nudge — a same-ms
// collision there is a real defect.
assert_eq!(
block_ts(2, Some(1001), 1000),
block_ts(false, Some(1000), 1000),
1001,
"audio dup-ms nudged"
);
assert_eq!(
block_ts(false, Some(1001), 1000),
1002,
"subtitle back-tick nudged"
);
}
/// Regression for the second-video-track bug: a Dolby Vision enhancement
/// layer is video but NOT track 0. The exemption must follow track TYPE, so
/// the EL's B-frame PTS are preserved exactly like the main video's — not
/// clamped to prev+1ms (which reintroduced the non-monotonic-DTS flood on
/// the EL stream). Drives the muxer through both video tracks and asserts
/// every video block timecode equals its source PTS.
#[test]
fn second_video_track_pts_not_clobbered() {
use std::io::Cursor;
// Main video at index 0, a Dolby-Vision-EL-style second video at index 1.
let tracks = vec![make_video_track(), make_video_track()];
let buf = Cursor::new(Vec::new());
let mux = MkvMuxer::new(buf, &tracks, None, 0.0, &[]).unwrap();
// Both tracks must be flagged video so neither is nudged.
assert_eq!(mux.track_is_video, vec![true, true]);
// A B-frame dip on the EL (track 1) must pass through unchanged — keyed
// on track type, not index.
assert_eq!(block_ts(mux.track_is_video[1], Some(1040), 1000), 1000);
}
// ── Clip-boundary timeline-continuity (PTS discontinuity rebasing) ──
const S: i64 = 1_000_000_000; // 1 second in ns
/// Characterization of the BUG: a BD title's two clips concatenated with a
/// PTS reset at the boundary. WITHOUT correction the raw timeline goes
/// hard backward at clip 2 (what produced the non-monotonic-DTS band on
/// Dune / Top Gun). WITH `TimelineContinuity` the output is monotonic and
/// continuous across the boundary.
#[test]
fn continuity_rebases_clip_boundary_reset() {
// Two interleaved tracks (video t0 + audio t1), clip1 rising to 10s,
// then clip2 RESETS near 0 and rises again — the non-seamless case.
let clip1: Vec<i64> = (0..=10).map(|i| i * S).collect(); // 0..10s
let clip2: Vec<i64> = (0..=10).map(|i| i * S).collect(); // resets to 0..10s
let raw: Vec<i64> = clip1.iter().chain(clip2.iter()).copied().collect();
// Uncorrected (the bug): the sequence is NOT monotonic — clip2's first
// frame (0) is 10s below clip1's last (10s).
assert!(
raw.windows(2).any(|w| w[1] < w[0]),
"precondition: raw clip-reset sequence is non-monotonic"
);
// Corrected: strictly non-decreasing, and clip2 continues AFTER clip1.
let mut tc = TimelineContinuity::new();
let out: Vec<i64> = raw.iter().map(|&p| tc.adjust(p)).collect();
assert!(
out.windows(2).all(|w| w[1] >= w[0]),
"corrected timeline must be monotonic non-decreasing, got {out:?}"
);
// Clip2's first frame lands just after clip1's last (10s) + the gap.
assert_eq!(out[11], 10 * S + DISCONTINUITY_GAP_NS);
// Clip2's last frame is offset by the whole of clip1, not back near 0.
assert!(out[21] > 19 * S);
}
/// Regression guard: NORMAL B-frame reorder (a small backward dip, well
/// under the discontinuity threshold) must pass through UNCHANGED — the
/// corrector must not rebase legitimate reorder (that would re-break the
/// video-PTS exemption).
#[test]
fn continuity_preserves_bframe_reorder() {
let mut tc = TimelineContinuity::new();
// I, P(+3 frames), B, B, B — presentation PTS dips backward by ~2
// frames (~83ms), far under the 3s threshold.
let raw = [0i64, 125_000_000, 42_000_000, 83_000_000, 250_000_000];
let out: Vec<i64> = raw.iter().map(|&p| tc.adjust(p)).collect();
assert_eq!(out, raw, "B-frame reorder must pass through unchanged");
assert_eq!(tc.offset_ns, 0, "no rebase for sub-threshold reorder");
}
/// A legitimate FORWARD gap (a real timing gap within a clip, under the
/// backstep window) must be PRESERVED, not clamped — only backward
/// clip-boundary jumps are rebased and only an old-epoch straggler (a
/// forward spike FAR past the frontier, right after a boundary) is remapped.
#[test]
fn continuity_preserves_forward_gap() {
let mut tc = TimelineContinuity::new();
let raw = [0i64, S, 2 * S + 500_000_000, 4 * S]; // a 1.5s gap mid-stream
let out: Vec<i64> = raw.iter().map(|&p| tc.adjust(p)).collect();
assert_eq!(out, raw, "forward gap preserved verbatim");
assert_eq!(tc.offset_ns, 0, "no rebase on forward progression");
}
/// Regression for the ratchet bug (the one the first fix introduced, which
/// broke everything after the first clip boundary): the demuxer interleaves
/// tracks, so a lagging audio frame from clip 1's TAIL arrives AFTER clip 2's
/// video has reset the epoch. The old global-high logic added the new offset
/// to that straggler, flung it into the future, inflated the frontier, and
/// re-triggered the rebase on every real clip-2 frame → offset ran away.
///
/// Correct behaviour: the straggler is remapped to its true seam position
/// (it is NOT thrown forward), the frontier and offset do NOT ratchet, and
/// clip 2 continues monotonically just after clip 1.
#[test]
fn continuity_straggler_does_not_ratchet_the_timeline() {
let mut tc = TimelineContinuity::new();
// clip1 rises to 10s (frontier 10s, offset 0).
for i in 0..=10 {
tc.adjust(i * S);
}
let offset_before = tc.offset_ns;
let frontier_before = tc.high_ns.unwrap();
assert_eq!(offset_before, 0);
assert_eq!(frontier_before, 10 * S);
// clip2's first VIDEO frame resets to 0 → clip-boundary rebase.
let c2_first = tc.adjust(0);
assert_eq!(
c2_first,
10 * S + DISCONTINUITY_GAP_NS,
"clip2 continues after clip1"
);
let offset_after_boundary = tc.offset_ns;
// Now a STRAGGLER: clip1's tail audio (raw ~9.5s) arrives interleaved.
let straggler = tc.adjust(9 * S + 500_000_000);
// It must land near the seam (clip1 tail), NOT ~19.5s in the future.
assert!(
straggler <= 10 * S,
"straggler remapped to its true seam position, got {straggler}"
);
// And it must NOT have moved the offset or the frontier.
assert_eq!(
tc.offset_ns, offset_after_boundary,
"straggler must not ratchet the offset"
);
assert_eq!(
tc.high_ns.unwrap(),
c2_first,
"straggler must not inflate the frontier"
);
// clip2 keeps rising from ~0; every frame stays just past the seam — no
// runaway. After 10 more seconds of clip2 the timeline is ~20s, not 30s+.
let mut last = c2_first;
for i in 1..=10 {
let a = tc.adjust(i * S);
assert!(
a >= last,
"clip2 monotonic after straggler, got {a} < {last}"
);
last = a;
}
assert!(
last < 21 * S,
"no ratchet: clip2 end near 20s (clip1+clip2), got {last}"
);
}
/// Regression for the original Top Gun band (`-58864 >= -820000`-scale): a
/// LARGE, real-magnitude clip-boundary back-jump (clip 1 ≈ 13 min, clip 2
/// resets to 0) must be rebased to one continuous monotonic timeline — not
/// left to produce the sustained non-monotonic-DTS band the auditor flagged.
#[test]
fn continuity_large_clip_boundary_backjump_rebased() {
let mut tc = TimelineContinuity::new();
// Clip 1: 0 .. 780s (13 min) at 1s steps.
let clip1: Vec<i64> = (0..=780).map(|i| i * S).collect();
// Clip 2: resets to 0 .. 120s — the ~ -780s discontinuity.
let clip2: Vec<i64> = (0..=120).map(|i| i * S).collect();
let mut last = i64::MIN;
let mut max = i64::MIN;
for &p in clip1.iter().chain(clip2.iter()) {
let a = tc.adjust(p);
assert!(
a >= last,
"rebased timeline must be monotonic, got {a} < {last}"
);
last = a;
max = max.max(a);
}
// Offset ≈ the whole of clip 1 (one boundary, no ratchet).
assert_eq!(tc.offset_ns, 780 * S + DISCONTINUITY_GAP_NS);
// Timeline spans clip1+clip2 (~900s), proving clip 2 is reachable past
// the boundary — not capped at it, and not ratcheted far beyond.
assert!(
(900 * S..901 * S).contains(&max),
"timeline must span ~900s (clip1+clip2), got {max}"
);
}
/// End-to-end output regression (the symptom, at the block-timecode level):
/// a large clip-boundary reset WITH an interleaved straggler audio frame
/// from clip 1's tail, driven through the full muxer. Asserts cluster
/// timestamps are monotonic non-decreasing AND the timeline reaches past the
/// boundary (clip 2 present) without ratcheting. This is the test that would
/// have caught BOTH the original `-820000` non-monotonic band and the
/// straggler ratchet that made everything after the boundary unseekable.
#[test]
fn clip_boundary_with_straggler_yields_monotonic_clusters() {
let tracks = [make_video_track(), make_audio_track()];
// ms→ns helper for readability.
let ms = |m: i64| m * 1_000_000;
let frames: Vec<(usize, i64, bool, Vec<u8>)> = vec![
// Clip 1: video keyframes at 0s and 600s, audio alongside.
(0, ms(0), true, vec![0x01; 16]),
(1, ms(0), true, vec![0xA0; 8]),
(0, ms(600_000), true, vec![0x02; 16]), // 600s kf
(1, ms(600_000), true, vec![0xA1; 8]),
// Clip 2: video keyframe RESETS to 0 (the -600s boundary).
(0, ms(0), true, vec![0x03; 16]),
// Straggler: clip 1's tail audio (≈599.5s) arrives interleaved AFTER
// the reset — the exact frame class that caused the ratchet.
(1, ms(599_500), true, vec![0xA2; 8]),
// Clip 2 continues: audio at 0, video keyframe at 5s.
(1, ms(0), true, vec![0xA3; 8]),
(0, ms(5_000), true, vec![0x04; 16]), // clip2 + 5s
];
let (data, frame_count) = mux_to_bytes(&tracks, &[], &frames);
assert_eq!(frame_count, 8, "all frames written (none dropped)");
let clusters = find_clusters(&data);
let ts: Vec<u64> = clusters.iter().map(|&(_, _, t)| t).collect();
assert!(!ts.is_empty(), "expected clusters");
// Cluster timestamps must be monotonic non-decreasing (no back-dated
// cluster from the straggler, no non-monotonic band).
assert!(
ts.windows(2).all(|w| w[1] >= w[0]),
"cluster timestamps must be monotonic, got {ts:?}"
);
let max = *ts.iter().max().unwrap();
// Timeline reaches past the boundary (clip 2 present): ≥ ~600s.
assert!(
max >= 600_000,
"timeline must span past the boundary, got {max}ms"
);
// And does NOT ratchet far beyond clip1+clip2 (~605s): well under 2× clip1.
assert!(
max < 1_000_000,
"no ratchet: max cluster ts {max}ms must stay near 605s"
);
}
#[test]
fn mkv_multiple_tracks() {
let buf = Cursor::new(Vec::new());
@@ -1785,16 +2162,15 @@ mod tests {
}
#[test]
fn negative_relative_audio_forces_new_cluster_no_i16_wrap() {
fn backjumped_audio_rebased_by_continuity_no_i16_wrap() {
// An audio frame whose PTS back-jumps far below the open cluster (a
// discontinuity) must force a fresh cluster rather than wrap the i16
// block-relative cast. Build: keyframe at t=0 opening a cluster, a video
// keyframe far later (so cluster ts is large), then an audio frame whose
// PTS lands before that cluster's start by more than i16::MIN ms.
// clip-boundary discontinuity) is now REBASED by TimelineContinuity
// before the cluster math, so it never produces a negative i16 block
// relative. Build: video kf at 0, video kf at 40s, then audio at t=0
// (a 40s back-jump > the 3s discontinuity threshold). Continuity shifts
// the audio to ~40s, keeping the timeline monotonic — it lands in the
// 40s cluster rather than forcing a third, back-dated cluster.
let tracks = [make_video_track(), make_audio_track()];
// base = 0 (first kept keyframe). Cluster opens at 0; a later keyframe at
// 40s opens a second cluster at ts=40000. Then audio at t=0 → relative
// 0-40000 = -40000 ms, below i16::MIN (-32768) → must open a new cluster.
let frames = vec![
(0usize, 0i64, true, vec![0x01; 16]),
(0usize, 40_000_000_000i64, true, vec![0x02; 16]), // 40s
@@ -1803,20 +2179,24 @@ mod tests {
let (data, frame_count) = mux_to_bytes(&tracks, &[], &frames);
assert_eq!(frame_count, 3);
let clusters = find_clusters(&data);
// Three clusters: t=0 (video kf), t=40000 (video kf), t=0 (forced for the
// back-jumped audio, no Cues entry).
assert!(
clusters.len() >= 3,
"back-jumped audio must force a fresh cluster, got {} clusters",
// Two clusters: t=0 (video kf) and t=40000 (video kf). The back-jumped
// audio is rebased onto the timeline (~40s) and joins the 40s cluster —
// no negative i16 relative, no forced back-dated third cluster.
assert_eq!(
clusters.len(),
2,
"continuity rebases the back-jump (no forced 3rd cluster), got {} clusters",
clusters.len()
);
// Every SimpleBlock's relative timestamp must round-trip through i16
// without the block landing outside the cluster (verified implicitly by
// the muxer never panicking on the `as i16` cast; here we assert the
// forced cluster's timestamp is non-negative so the `as u64` write is
// also safe).
for (_, _, ts) in &clusters {
assert!(*ts <= i64::MAX as u64, "cluster ts must not have wrapped");
// Cluster timestamps stay non-negative (the `as u64` write is safe) and
// monotonic non-decreasing — continuity guaranteed a forward timeline.
let ts: Vec<u64> = clusters.iter().map(|(_, _, t)| *t).collect();
assert!(
ts.windows(2).all(|w| w[1] >= w[0]),
"cluster ts monotonic: {ts:?}"
);
for t in &ts {
assert!(*t <= i64::MAX as u64, "cluster ts must not have wrapped");
}
}