fix(video): prime rotated muxers with the last muxed audio frame - #340
Merged
superuser404notfound merged 1 commit intoAug 9, 2026
Conversation
A mid-session muxer rotation (same-PID parameter-set change after a live reconnect join, SSAI program switch) allocates a brand-new MP4SegmentMuxer, and the only moov-prime carry-in was the construction -time audioMoovPrimeFrame — nil for every session that did not start audio-less. With bridged E-AC-3, video leads audio across the seam and the bridge adds encoder latency, so the rotated muxer's first cut can arrive before any post-seam audio packet: the cut defers awaiting a sample entry (AE#222), the producer turns the deferral into a pump exit, teardown finalize fails on the same precondition, and the live session dies with muxerFailed. The AE#222 exit-scan cannot convert the exit either — it rightly excludes bridged sessions, since a raw source frame cannot prime a bridge-encoded track. Have the producer retain the payload of the last audio frame a muxer accepted (copied before the write — movenc consumes the data ref — and committed only on write success) and hand it to every later muxer allocation, so a rotation is always primed and the deferral cannot fire mid-session. Scoped to AC-3/E-AC-3/TrueHD via the muxer's own parsed-packet predicate, now exposed as a static helper; AAC never copies. One retained frame, pump-thread confined. Tests pin the incident shape (unprimed rotated muxer over a real bridge-encoder track: video-only cut defers, finalize salvages nothing) and the fix's load-bearing claim: a BRIDGE-OUTPUT E-AC-3 frame is a valid dec3 prime, not just a source frame as in AE#222. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 8, 2026
superuser404notfound
merged commit Aug 9, 2026
63319f7
into
superuser404notfound:main
3 checks passed
superuser404notfound
added a commit
that referenced
this pull request
Aug 9, 2026
…hable Follow-up to #340. The rotation prime is right; three things around it were not quite finished. `lastMuxedAudioPrimeFrame` and `audioMoovPrimeFrame` were two fields for one value: the retained frame is seeded from the construction-time prime and is only ever assigned non-nil, so `lastMuxedAudioPrimeFrame ?? audioMoovPrimeFrame` at the single use site could never take its right branch. One var now, documented with both origins and with why it is safe unlocked (written from the two audio write sites, read from allocateMuxer, and every allocation goes through ensureMuxer on the pump thread). The predicate now says what makes the fix safe for the codecs that reach it. AC-3 and E-AC-3 prime from ANY frame, every frame is a complete syncframe, so an arbitrary retained one is as good as the first. TrueHD would not: movenc latches track->extradata from the first packet written and mov_write_dmlp_tag rejects it without the major-sync word 0xF8726FBA, which most frames lack. It is unreachable today (AudioCodecCompat.requiresBridge sends TrueHD through AudioBridge, so the muxer's audio track is the bridge's E-AC-3 or FLAC encoder), and that is now written down where someone would go to unblock it. Plus a test pinning the predicate itself, since it is the gate the per-frame retention hangs on and narrowing it would disarm the rotation prime silently, and the house-style em-dashes removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HX5zV3Fcf7Nzq4DYGdXvQE
superuser404notfound
added a commit
that referenced
this pull request
Aug 9, 2026
…ed as unreachable (#340)
Owner
|
Merged. The branch was based on 6.15.2, so this went through a merge onto current main first: full suite green at 1688 tests, tvOS Simulator build clean, and the new suite holds as a pair (the unprimed case defers and finalizes to nothing, the primed one cuts a plain fragment). The two log lines in the trace line up with the code exactly: the deferral returns nil out of advanceMuxer, ensureMuxer turns that into muxerFailed, and the exit scan's Two things pulled in on top in 09cabf5:
Ships in the next release. Thanks for this one, the deferral to pump-exit chain in the trace was what made it readable end to end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A live session with bridged E-AC-3 audio dies with
muxerFailedwhenever a mid-session muxer rotation (same-PID parameter-set change after a reconnect join, or an SSAI program switch) cuts its first segment before any post-seam audio packet has been muxed.Field trace (live TV, reconnect join into a new encoder session — DTS jump + SPS/PPS change):
Why it happens:
MP4SegmentMuxer; E-AC-3/AC-3/TrueHD need a parsed packet for their mp4 sample entry (dec3/dac3/dmlp), so the fresh muxer starts un-primed.audioMoovPrimeFramelet— nil for every session that did not start audio-less..needsAudioSampleEntryPrime: it rightly excludes bridged sessions (a raw source frame cannot prime a bridge-encoded track), so the reason staysmuxerFailed.Fix
The producer retains the payload of the last audio frame a muxer accepted (copied before the write — movenc consumes the packet's data ref — and committed only on write success) and hands it to every later muxer allocation. A rotation is then always primed:
audioPacketWritten/moovFlushedlatch at init and the deferral cannot fire mid-session.sessionAudioMoovPrimeFramekeeps covering the session-start case; the exit-scan and its bridge guard are unchanged.dec3is exact.Tests
LiveRotationAudioPrimeTestsruns a real WAV →AudioBridge(.surroundCompat)→ E-AC-3 →MP4SegmentMuxerpipeline:dec3prime — moov+dec3 at init, video-only cut completes, delivered segment is a plain fragment.🤖 Generated with Claude Code