Conversation
|
Thanks for the detailed writeup and for keeping the submissions separately reviewable. The judder you measured is real, but I cannot adopt this route: the feature it keys on is not a defect, and I have a counterexample that the branch itself misroutes. The counterexample
ffmpeg -f lavfi -i testsrc=size=1280x720:rate=30000/1001:duration=120 \
-c:v libx264 -profile:v main -preset veryfast -b:v 3M -pix_fmt yuv420p \
-x264-params "open-gop=1:bframes=3:keyint=30:min-keyint=30:scenecut=0:b-pyramid=normal" \
-movflags +faststart opengop.mp4
# same line with open-gop=0 for the controlOpen GOP: 120 container keys, 2 IDR NALs, 121 recovery point SEIs. Control: 120 container keys, 121 IDR NALs, 2 SEIs. Serving both through the loopback path and decoding each segment IN ISOLATION through Apple's decoder (
No loss, no error, The branch routes that file to softwareBuilt from this branch, against the two fixtures above: Every The control line also shows the cost on the healthy path: the probe reads its full 180 video packet budget before it can say no, on every H.264 VOD session, and it is a second rewind of the reused demuxer on top of what the session already does. At 10 Mbit/s that is roughly 7 MB read before the first frame. The existing InterlaceProbe only runs inside a narrow declared-interlace case, which is why it can afford the same budget. Three smaller things
What I think is actually happeningThe shape you measured (healthy from the head at 30.460 fps, 3.000 fps after a seek, item clock at 1.000019, buffer full at 5.4 to 7.5 s, no aggregate drops) is the shape #513 describes and explains: valid composition offsets at the head, zero offsets in later sequences, and a healthy-head verdict that disables repair for the whole session. A seek into the malformed region is then the trigger, with the decoder never at fault. Your measured 2.503 to 3.325 fps also matches the keyframe rate of your own segments (10 container keys per 120 samples), which is what "only the random access points survive" looks like, and not what "this segment cannot be decoded" looks like. That is a hypothesis, not a measurement, and it is testable on your side without publishing anything:
Worth naming that the three sources were only ever verified as part of the downstream core, with all four changes present at once. If #513 repairs them, this route is unnecessary; if it does not, the pktdump above says which of the four did the work. One unrelated fix came out of reviewing this: Caveat on my side: the isolation decode is macOS with |
Follow-up: native Apple TV reproduction (2026-09-08)@superuser404notfound Thanks for the concrete counterexample. I agree that recovery-point coding alone is not a defect and cannot justify automatically routing every matching stream to software. The healthy open-GOP fixture must be a negative routing control. I also agree that startup probe cost, rewind-failure behavior, the between-read deadline and the public diagnostic counter need addressing; the evidence below is not a request to merge this PR unchanged. The new results and their limitations are recorded in this sanitized, commit-pinned test report. It contains the exact candidate identity, actual playback sequence, numerical measurements, fragment hashes and source comparison. No movie bytes, private source names/URLs, device identifiers or raw logs are published. 1. Native playback still reproduces the problem with the other repairs presentOn the same physical Apple TV, we tested the first original affected source using diagnostic engine cd3e80d3, app build
The tester confirmed visible judder. Pause/resume did not restore the rate. The native backend and three recovery keys were recorded throughout active playback; post-seek rate was 1, the buffer was not empty, and presentation-axis shift was 0. Access-log stall/drop counters stayed zero. Track rate means 2. The proposed all-zero-offset signature is absent in both source and emitted target data
FFmpeg 8.1.2 software decoding of the exact init/fragment pairs gives 130/130 frames for fragment 0, 250/250 for continuous 0+1, 118/120 for each noninitial fragment in isolation, and 358/360 for continuous 175–177. The latter is not a repeated two-frame loss at each fragment boundary: the absent timestamps are at the beginning of the isolated sequence. Decoded PTS is strictly increasing, with no counted MMCO/reference/decode warnings. This is not the native 3. A clarification on the original acceptance chronologyThe original three-source acceptance was on downstream That earlier software success is historical evidence, not a fresh same-build paired A/B. Only the native half of the new comparison has been captured. Remaining discriminatorThese measurements do not support the all-zero-offset explanation for the captured interval, but they do not rehabilitate the broad software-routing predicate. The next useful discriminator is native tvOS decoding of these exact init/fragments separately from the AVPlayer/HLS seek session, alongside the healthy generated open-GOP control. Native decoded timestamps/status should help distinguish random-access decoder state, container metadata and player-session behavior. That test, the fresh tvOS negative control and a narrower replacement fix are still outstanding. Thanks also for |
|
Thank you for taking this back to the device with the other repairs present, and for being explicit about what that build was and was not. Two things I accept without reservation: the judder is not the partial-composition case, and my #513 explanation is wrong for that interval. The 300-packet samples at 696 s carry composition offsets of 0, 2002 and 4004 at the head and at the seek target alike, and the emitted fragments carry the same three values, so there is no zero-offset region for that repair to be about. I withdraw the hypothesis. What I measured sinceMy counterexample decoded segments in isolation. That answers whether Apple's decoder can open one, and it does not answer whether the SESSION survives a seek, which is the claim this route actually rests on. So I built the observable that was missing and ran the whole pipeline instead.
New fixture pair, shaped to your source rather than to my earlier one: ffmpeg -f lavfi -i testsrc=size=1280x720:rate=30000/1001:duration=240 \
-f lavfi -i sine=frequency=440:sample_rate=44100 -t 240 \
-c:v libx264 -profile:v main -preset veryfast -b:v 3M -pix_fmt yuv420p \
-x264-params "open-gop=1:bframes=3:keyint=12:min-keyint=12:scenecut=0:b-pyramid=normal" \
-c:a aac -b:a 128k -ac 2 -ar 44100 -movflags +faststart og12.mp4
# same line with open-gop=0 for the controlThe open GOP arm has ONE IDR in the whole file (frame 0), 600 container keys and 599 immediate/exact recovery points, 1001-tick packets at time base 1/30000, AAC-LC 44.1 kHz stereo. The control has 600 IDRs and no recovery points. A four second fragment therefore carries ten container keys out of ~120 samples, which is your density rather than my earlier keyint 30. The fragments the engine emits for that arm are the shape you inspected. Read off Ten non-IDR sync samples, no roll sample group, no Four seeks per run (180, 60, 150, 90 s), two runs per arm, macOS 26.5.2, engine main:
The two arms are the same session. The 0.067 s gap is a single dropped frame at startup and it is in both. Access-log drops do climb at the seeks, about 165 to 185 at the first and about 85 at each later one, but they climb the same way on the control, and one control run read 0 for the whole session, so that counter is the harness and not the content. Running each arm twice is what turned that from a finding into noise. So on this side the shape costs nothing, through the real remux, the real playlist and a real seek, while the routing predicate would move every file that has it to software. What is left, and what would settle itYour own data has cleared the fragments (358 of 360 continuous, and the two absent timestamps are the leading pictures at the start of an isolated sequence, which is what an open GOP means). This clears the container shape and a full AVPlayer seek session on macOS. What is left is tvOS, and one of two things inside it: AVFoundation with this content, or the engine's session state around the seek. The cheapest test that separates those two needs none of my code and no engine build, and you already have the exported init plus eight fragments. Serve them from any static HTTP server as a hand-written VOD playlist: Play that in a bare AVPlayer on the same Apple TV, no engine in the process, and seek into the same region. If the rate collapses there, AVFoundation is doing it to those bytes and the engine is out of the picture. If it does not, the engine's session state around the seek is implicated, and the next question is which part of it. On what to capture, in that order of value:
One thing on our side is worth naming while this is open, because it is ours rather than Apple's. A sync sample promises that every sample after it in decode order decodes; an open GOP recovery point does not keep that promise for its leading pictures, and the honest signal for it is a On the PR itself: I am not going to merge a route keyed on recovery-point coding, for the reason we now agree on, and the three smaller points stand as written. Leaving it open as the thread for the measurement. |
Summary
Companion submissions, independently reviewable against main:
Route positively identified H.264 recovery-point VOD through the existing software path to avoid persistent native/HLS judder after seeking. This is a narrowly detected compatibility route, not a native decoder repair or the missing-ctts problem from #409.
What changed
diagnostics.h264RecoveryPointKeyCountresult, add recognition/negative tests and a documented-threshold pin.1af43f017ffca973e35f8fec1e700852f2953d55; no downstream UI, diagnostic framework, dependency pins or unrelated fork history.Evidence and limitations
Three private MP4/H.264 sources show repeated recovery-point keys (NAL 1/6, not IDR 5), normal 1001-tick packet cadence at time base 1/30000, and persistent judder after seeks on the native path. Representative steady-state measurements:
These are access-log/clock observations, not pixel-level frame counting. The inspected native segments had 120 samples, 10 container keys and zero IDR NALs. VidHub played the same sources normally; we do not know its internal route.
A generated open-GOP bitstream verifies the SEI shape but has not been shown to reproduce the native Apple TV judder. Private assets, paths, URLs, credentials, raw logs and device identifiers are not included. This proposal changes CPU/power cost for detected sources and does not claim every non-IDR stream is incompatible with Apple hardware. A native random-access fix could supersede it.
Test plan
99f8c23c74884ec7d62810df9c6b9be31939f484. The upstream-isolated branch was not separately installed.bash Scripts/test-h264-recovery-point.shpassed (exact/delayed recovery, IDR exclusion, container flags, malformed/oversized input, repeated evidence).python3 Scripts/check-doc-links.pyandgit diff --checkpassed.DocumentedConstantsTestscoverage run in upstream CI; the complete Swift package test suite was not run locally.Checklist
CHANGELOG.mdupdated