Skip to content

6.74.0 - Display order from a partial ctts table, and a software VOD read-ahead

Choose a tag to compare

@superuser404notfound superuser404notfound released this 08 Sep 16:22
· 338 commits to main since this release

Three contributed demuxer and cache fixes from @orut34iop, a live join that could publish an unrepresentable axis, and two diagnostics that each cost an investigation round before they existed.

Fixed

An MP4 with valid composition offsets at its head and none behind it gets that region's display order back (#513). A healthy head is not proof of a healthy table: some writers fill ctts for the first sequences and leave zeros after them, so a whole-file verdict reads healthy where it looks and every reordered sequence past that point is delivered in coding order for the rest of the file. A corroborated healthy origin now also arms a bounded watch for zero-offset IDR sequences, and a picture in one claims the timestamp slot its own display rank owns, read from the file rather than fitted to a cadence.

Measured on a generated twin whose second zero-offset sequence is 420 pictures: a deepest wait of 6 packets, and 1800 of 1800 packet times identical to the healthy twin's own axis. Decode timestamps, the published keyframe index, packet payloads and audio never move, a picture waits its mini-GOP rather than the end of its sequence, and every refusal hands the held packets back exactly as they arrived and lets the rest of that sequence stream through. The judder this removes is a far smaller failure than a session that stops.

Software VOD reads packets ahead of the decoder and keeps them across a seek that lands inside them (#512). The software path had no reservoir of its own: the demux loop read on renderer backpressure alone, so arrived-but-unplayed media was the fraction of a second the decode queue held, measured at 0.33 s to 0.36 s on real hardware, and a backward seek out of the byte reader's resident window paid for the same bytes twice while blocking the demux thread.

Measured on a 600 s H.264 source over a 16 Mbit origin, seeking back 198 s after 200 s of playback:

before   two 4 MB detour fetches, blocking reads of 2658 ms and 2623 ms, display cushion 0.00 s
after    no request at all, cushion untouched

A seek whose target is still retained moves only the consumer cursor; the source reader stays at its own frontier. A local path stays on the direct loop, because the spool exists to avoid a second trip to a source and re-reading a file is a page-cache hit. Live, DVR and the native path are untouched.

A Matroska H.264 stream whose block timestamps rise in coding order is presented in display order again (#511). Matroska stores presentation timestamps, so a writer filling them packet by packet hands every slot to the picture decoded at that position rather than the one displayed there, and the result is a presentation clock that steps back once per mini-GOP for the length of the file. Each picture now gets the slot its own display rank owns, read from the file rather than computed, so the repaired times are the container's own set including its rounding. Measured on a generated twin through the software decoder: 15 of 30 frame times stepped backwards before, 0 after.

A live join just below the 33-bit PTS wrap publishes a zero axis instead of an unsigned six million years. The producer pinned an epoch's first frame to the demuxed value and that value was free to go negative, which for tfdt is not unusual but unrepresentable: the box carries unsigned int(64), so movenc writes the bits and AVPlayer reads baseMediaDecodeTime = 2^64 - |dts| against a playlist that starts at 0.

libavformat produces those timestamps by design. An MPEG-TS whose first DTS falls within 60 s of the wrap at 2^33 / 90000 is classified AV_PTS_WRAP_SUB_OFFSET, after which every timestamp comes out 2^33 ticks low. That is an ordinary live join, not the early-open case the pin exists for. Measured on a seed 53.7 s below the wrap:

before   seg0 baseMediaDecodeTime=18446744073704717024, video traf only, clock never moved
after    tfdt 0 on both trafs, audio back in seg0, clock runs 25 s with 0 stalls

Found while investigating #509, whose own capture has different gate values and stays open.

Added

The subtitle forward prefetcher's cancel says who cancelled it (#496). #151 forward prefetch exited (reason=cancelled cancelled=true) reported that a cancel() had happened and nothing else, and it lands whenever the parked loop next looks, seconds after the fact. A field capture showing a prefetcher that stops eight seconds into a session and never returns was therefore indistinguishable from a teardown, a track switch and a rebuild, and the actual cause had to be reconstructed from what else happened to log nearby.

Every teardown route now carries a reason to the cancel, which emits #151 forward prefetch cancelled (reason=sidecarSelected) and its siblings when a session was actually running. The sidecar path had the matching gap on the other side: the drainer announced itself with overlay fed by packet-store drainer and the whole-file path announced nothing, so a complete track publishing into the overlay read as a drainer that had stopped filling. It now says sidecar decode start: and overlay fed by sidecar decode: … (N cues), and a decode that starts and never publishes leaves a trace instead of an empty overlay with no author.

aetherctl play --present-times: how many frames actually reached the screen on the native path. --frame-times reads the software renderer's own reports, so the AVPlayer route had no frame observable at all and every judder report against it could only be argued about from a track-rate estimate. The flag attaches an AVPlayerItemVideoOutput to the engine's item, counts distinct presentation times and reports the largest gap between two of them, which is what separates a late picture from a session presenting nothing but its random access points.

Acknowledgements

@orut34iop for three separate diagnoses in one week, each with a reproduction and a proposed patch, and for accepting a rewrite where the shared shape of a fix mattered more than the fix. @RadicalMuffinMan for a full diagnostic capture on #496 that turned out to answer its own question once the log could say what it was doing.

Full changelog: CHANGELOG.md | 6.73.0...6.74.0