Key the seam-crossing rule on reorder, not on driving epochs

A Dolby Vision enhancement layer is a second video track: it does not
drive epochs, but it does carry B-frame reorder. The crossing rule was
keyed on driving epochs, so the EL took the branch whose premise is that
the track has no reorder — and its ordinary reorder dip near the end of a
clip, which during an overlap also lands inside the next clip's range,
was read as a join. The EL was then placed on the next clip's offset, out
of step with the base-layer frame it must be co-timed with by the width
of the overlap: the same desync the per-track cursor was added to remove,
reintroduced for one track.

The property the rule actually depends on is whether a backward step can
be reorder, so it is now keyed on that. Both sinks derive it from the
track kind rather than from the epoch driver.
This commit is contained in:
Matthew Jackson
2026-08-05 17:52:13 -07:00
parent c8fafec393
commit f4fb5c65e0
3 changed files with 83 additions and 21 deletions
+10 -1
View File
@@ -866,9 +866,18 @@ impl Stream for DemuxSink {
// an audio ES before the video ES, in which case track 0 is audio and a
// non-video epoch driver would ratchet the frontier on sparse/lagging PTS.
let drives = Some(frame.track) == self.ref_video_track;
// Keyed on the track KIND, not on driving epochs: a second video track
// (a Dolby Vision enhancement layer) does not drive epochs but does
// carry B-frame reorder, and the seam-crossing rule differs on exactly
// that property.
let is_video = self
.tracks
.get(frame.track)
.and_then(|s| s.as_ref())
.is_some_and(|t| t.kind == TrackKind::Video);
// See `MkvMuxer::write_frame`: `None` is material outside the
// playlist's clip marks and is dropped rather than emitted.
let Some(pts) = self.timeline.map(frame.pts, drives, frame.track) else {
let Some(pts) = self.timeline.map(frame.pts, drives, frame.track, is_video) else {
return Ok(());
};
if drives {