wip: top gun EL/decrypt follow-up (in progress, rc3)

This commit is contained in:
Matthew Jackson
2026-06-22 10:02:53 -07:00
parent e960c2f1be
commit 9250f5bb30
+81 -36
View File
@@ -391,44 +391,48 @@ impl TimelineContinuity {
/// Map a raw PES PTS (ns) onto the continuous output timeline. /// Map a raw PES PTS (ns) onto the continuous output timeline.
/// ///
/// `is_video` gates EVERY epoch decision. Only video may advance the frontier /// `drives_epoch` gates EVERY epoch decision. It is `true` for the PRIMARY
/// or open a new epoch; non-video tracks are passive riders. /// video track (base layer, track 0) ONLY. Every other track — audio, PGS
/// subtitle, and a second video track such as a Dolby Vision enhancement
/// layer — passes `false` and is a passive rider. (The DV EL is video but
/// runs its own PTS timeline interleaved with the base layer's; letting it
/// drive epochs would false-trigger a reset on every GOP.)
/// ///
/// **Non-video tracks** (`is_video == false`) — audio, PGS subtitle. Always /// **Passive tracks** (`drives_epoch == false`). Always remapped under the
/// remapped under the CURRENT offset. They never advance `high_ns`, never /// CURRENT offset. They never advance `high_ns`, never trigger a clip-boundary
/// trigger a clip-boundary reset, and never bump `offset_ns`. This is what /// reset, and never bump `offset_ns`. This is what kills the single-clip
/// kills the single-clip ratchet: a sparse, lagging subtitle/audio PTS can /// ratchet: a sparse/lagging subtitle/audio PTS, or an interleaved EL frame,
/// no longer push the frontier up and make the next video frame look like a /// can no longer push the frontier up and make the next base-video frame look
/// boundary. A/V sync is preserved because the offset they ride is the same /// like a boundary. A/V sync is preserved because the offset they ride is the
/// one video established for the epoch. /// same one the base video established for the epoch.
/// ///
/// **Video track** (`is_video == true`): /// **Primary video** (`drives_epoch == true`):
/// - **Backward jump > `DISCONTINUITY_BACKSTEP_NS`** vs the frontier = /// - **Backward jump > `DISCONTINUITY_BACKSTEP_NS`** vs the frontier =
/// clip-boundary reset: open a new epoch (bump the offset so this frame /// clip-boundary reset: open a new epoch (bump the offset so this frame
/// continues just after the frontier). This is the genuine multi-clip /// continues just after the frontier). This is the genuine multi-clip
/// seamless rebasing, now driven only by real video back-jumps. /// seamless rebasing, now driven only by real base-video back-jumps.
/// - **Everything else** (normal progression + sub-threshold B-frame reorder /// - **Everything else** (normal progression + sub-threshold B-frame reorder
/// dips) passes through with the current offset and advances the frontier, /// dips) passes through with the current offset and advances the frontier,
/// preserving PTS. /// preserving PTS.
fn adjust(&mut self, raw_pts_ns: i64, is_video: bool) -> i64 { fn adjust(&mut self, raw_pts_ns: i64, drives_epoch: bool) -> i64 {
// Non-video: ride the current epoch's offset. Never advance the frontier // Passive track: ride the current epoch's offset. Never advance the
// and never open an epoch — these tracks are too sparse/laggy to make a // frontier and never open an epoch — these tracks each run on their own
// reliable boundary signal and would false-trigger the ratchet. // (sparse/laggy/independent) timeline and would false-trigger the ratchet.
if !is_video { if !drives_epoch {
let mapped = raw_pts_ns.saturating_add(self.offset_ns); let mapped = raw_pts_ns.saturating_add(self.offset_ns);
// Tail-straggler remap: at a REAL (video-driven) multi-clip boundary // Tail-straggler remap: at a REAL (base-video-driven) multi-clip
// the offset has just jumped forward by ~a whole clip, but a lagging // boundary the offset has just jumped forward by ~a whole clip, but a
// tail frame from the just-ended clip still carries an OLD-epoch raw // lagging tail frame from the just-ended clip still carries an
// PTS. Adding the NEW offset flings it ~a clip past the frontier and // OLD-epoch raw PTS. Adding the NEW offset flings it ~a clip past the
// would force a forward-dated split cluster (breaking cluster // frontier and would force a forward-dated split cluster (breaking
// monotonicity). Such a straggler is recognised precisely: its // cluster monotonicity). Such a straggler is recognised precisely: its
// current-offset mapping lands more than a backstep PAST the frontier // current-offset mapping lands more than a backstep PAST the frontier
// AND its PREVIOUS-offset mapping lands at/below the frontier (i.e. it // AND its PREVIOUS-offset mapping lands at/below the frontier (i.e. it
// belongs to the prior epoch). Remap it with the previous offset so // belongs to the prior epoch). Remap it with the previous offset so
// it lands at its true seam position. This is what distinguishes a // it lands at its true seam position. This is what distinguishes a
// tail straggler from a frame that legitimately runs ahead of the // tail straggler from a frame that legitimately runs ahead of the
// (video-only) frontier — a long audio-only tail, or a sparse // (base-video-only) frontier — a long audio-only tail, a sparse
// subtitle — which is left on the current offset. // subtitle, or an EL frame — which is left on the current offset.
if let Some(high) = self.high_ns { if let Some(high) = self.high_ns {
if mapped > high + DISCONTINUITY_BACKSTEP_NS { if mapped > high + DISCONTINUITY_BACKSTEP_NS {
let prev_mapped = raw_pts_ns.saturating_add(self.prev_offset_ns); let prev_mapped = raw_pts_ns.saturating_add(self.prev_offset_ns);
@@ -767,22 +771,29 @@ impl<W: Write + Seek> MkvMuxer<W> {
data: &[u8], data: &[u8],
duration_ns: Option<u64>, duration_ns: Option<u64>,
) -> io::Result<()> { ) -> io::Result<()> {
// Is this a video track? Needed both for the continuity epoch decision // Is this a video track? Used for the monotonic block-timestamp nudge
// (only video may open/advance an epoch — non-video tracks are too // below, which must exempt EVERY video track (incl. a Dolby Vision EL).
// sparse/laggy to be a reliable clip-boundary signal and would otherwise
// false-trigger the rebase on single-clip titles) and for the monotonic
// block-timestamp nudge below.
let is_video = self.track_is_video.get(track_idx).copied().unwrap_or(false); let is_video = self.track_is_video.get(track_idx).copied().unwrap_or(false);
// The clip-boundary epoch decision is driven by the PRIMARY video track
// ONLY (track 0). A title can carry a SECOND video track — a Dolby Vision
// enhancement layer — whose PTS runs on its OWN timeline, interleaved
// with the base layer's. The two video PTS sequences overlap, so the EL's
// frames look like multi-second backward jumps against the base layer's
// frontier and would false-trigger an epoch reset on every GOP (the exact
// ratchet that inflated Top Gun's 1-clip timeline to ~7 h). Only the base
// video layer establishes/advances the frontier and opens epochs; the EL
// — like audio and subtitles — rides the current offset.
let drives_epoch = track_idx == 0;
// Map the raw PES PTS onto the continuous output timeline FIRST, before // 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 // any base/cluster math: at a non-seamless clip / layer-break boundary
// sector stream, so a non-seamless clip / layer-break boundary arrives // the source PES PTS jumps backward. Rebasing it (a global offset across
// 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 // all tracks, A/V-sync-preserving) keeps the boundary from becoming a
// band of non-monotonic block timestamps. Only VIDEO drives the // band of non-monotonic block timestamps. Only the PRIMARY video track
// boundary decision; non-video tracks ride the current offset. No-op for // drives the boundary decision; every other track (audio, subtitle, DV
// single-clip titles. // EL) rides the current offset. No-op for single-clip titles.
let pts_ns = self.continuity.adjust(pts_ns, is_video); let pts_ns = self.continuity.adjust(pts_ns, drives_epoch);
let raw_ticks = pts_ns / TIMESTAMP_SCALE_NS; let raw_ticks = pts_ns / TIMESTAMP_SCALE_NS;
// Cluster boundaries normally coincide with a video keyframe so every // Cluster boundaries normally coincide with a video keyframe so every
@@ -1450,6 +1461,40 @@ mod tests {
assert!(max_out <= 60 * S, "no timeline inflation, max={max_out}"); assert!(max_out <= 60 * S, "no timeline inflation, max={max_out}");
} }
/// PRIMARY rc3 regression (Dolby Vision dual-layer): a SECOND video track —
/// the DV enhancement layer — runs its OWN PTS timeline interleaved with the
/// base layer's, so the two video PTS sequences OVERLAP. The EL must be a
/// PASSIVE rider (drives_epoch == false): if it drove epochs, every EL GOP
/// would look like a multi-second backward jump against the base-layer
/// frontier and false-trigger a clip-boundary reset — the exact ratchet that
/// inflated Top Gun's 1-clip 1h49m timeline to ~7 h. Here the base layer
/// advances 0..60s while the EL re-emits the SAME 0..60s interleaved; the
/// timeline must stay at 60s with offset 0.
#[test]
fn dv_enhancement_layer_does_not_drive_epochs() {
let mut tc = TimelineContinuity::new();
let mut max_out = i64::MIN;
for sec in 0..=60 {
// Base layer (track 0) drives the epoch.
let bl = adj_video(&mut tc, sec * S);
// EL (track 1) re-emits the same time — a passive rider. Its raw PTS
// equals the base layer's, but it arrives just AFTER the base frame
// for the NEXT second sometimes; simulate the overlap by feeding the
// PREVIOUS second's time, which is a backward swing vs the frontier.
let el_raw = if sec > 0 { (sec - 1) * S } else { 0 };
let el = adj_other(&mut tc, el_raw);
assert_eq!(el, el_raw, "EL rides current offset, true PTS preserved");
max_out = max_out.max(bl).max(el);
}
assert_eq!(
tc.offset_ns, 0,
"DV EL interleave must not ratchet offset (was {})",
tc.offset_ns
);
assert_eq!(tc.high_ns, Some(60 * S), "frontier tracks base video only");
assert!(max_out <= 60 * S, "no timeline inflation, max={max_out}");
}
/// Companion: a non-video frame must never ADVANCE the frontier. Even a /// Companion: a non-video frame must never ADVANCE the frontier. Even a
/// non-video PTS far ABOVE the current video frontier (a subtitle/audio /// non-video PTS far ABOVE the current video frontier (a subtitle/audio
/// timestamp that leads the video momentarily) leaves `high_ns` untouched, /// timestamp that leads the video momentarily) leaves `high_ns` untouched,