fix(audio): a silent transcoding bridge names itself instead of dying as a muxer error (#396) - #397
Merged
Conversation
… as a muxer error (#396) A plain SD MKV with mono MP3 audio failed on the native route at nine of nine start positions, ending on "Source audio cannot be muxed (code -22)" after three identical revive attempts. The muxer was right and innocent: movenc can only build an AC-3/E-AC-3 sample entry from a packet that has been written, and on a bridged source those packets come from the bridge's encoder, which had emitted none. Nothing anywhere in the session said that. Every step between a source packet and an encoded frame ends in a `return` or in a loop that stops on a negative code: a packet the decoder rejects, a decoder that answers nothing, a resample that converts to zero samples, an encoder that keeps its output. Per packet that is correct, one bad frame must not end a session. In aggregate it made a bridge that emitted nothing for a whole first segment indistinguishable from one that had simply not been asked yet, and the only sentence the session produced named the muxer and the source, which are the two subsystems that were fine. - AudioBridge counts each arm (fed, rejected, decoded, dropped before the FIFO, enqueued, emitted, decode/encode errors) and keeps the decoder's own error code instead of discarding it. One loud line the moment enough source has gone in for the silence to be structural rather than start-up latency. - The deferred first cut prints the bridge's account on the bridged path instead of announcing a prime scan that `scanForAudioMoovPrimeFrame` does not run there (it is scoped to stream-copy audio by design): the line described an action that never happened, on the one path where the interesting question had no line at all. - Zero decoded frames ends the session at once. A restart rebuilds the muxer and re-opens the encoder, both downstream of a failing decoder, so the budget bought the same answer three times (the reporter measured 12 to 23 ms per attempt with identical packet counts). Frames decoded with nothing emitted is the encoder side, which a rebuild does heal, and keeps its revive. - New `PlaybackErrorKind.audioBridgeProducedNoOutput`. This used to arrive as `.vodSourceFailed`, which reads as "the source is gone" and ends a host's fallback ladder; the source is neither gone nor unreadable, and a second player that decodes the track itself plays the file. A ladder should demote on it. Verified end to end on a fixture that reproduces the reported log 1:1 (mono MP3 44.1 kHz MKV with its audio payloads made undecodable): before, three revive attempts and "Source audio cannot be muxed" with no mention of audio decoding anywhere; after, the arm is named at the deferred cut and the session ends at once on "Audio track could not be decoded". Healthy bridged playback is byte-identical (same fixture undamaged: ec-3 track, no new lines). 1936 swift-testing + 547 XCTest green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E5UztmCZtNYPQpzdmSdmWG
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.
Closes nothing yet: AE#396 stays open for the reporter's capture. This is the instrumentation and the classification that make their next run answer the question.
What the report is
A plain SD MKV with mono MP3 audio fails on the native route at nine of nine start positions, ending on
Source audio cannot be muxed (code -22)after three identical revive attempts of 12 to 23 ms each.What is actually happening
The muxer is right and innocent. FFmpeg's mp4 muxer can only build an AC-3/E-AC-3 sample entry from a packet it has PARSED, and on a bridged source those packets come from the bridge's encoder, not from the source. The bridge had emitted none, so the first cut deferred (AE#222's guard, doing its job), the pump exited
muxerFailed, and the revive cap ended the session.Reproduced 1:1 on a synthetic fixture (mono MP3 44.1 kHz MKV whose audio payloads were made undecodable): same log lines, same three attempts, same terminal string.
Nothing in the session said any of that. Every step between a source packet and an encoded frame ends in a
returnor in a loop that stops on a negative code:avcodec_send_packetansweringinvalidData/einval(a deliberate per-packet skip, Blu-ray ISO playback failed, audio bridge.feed failed at pkt #64)while avcodec_receive_frame(...) >= 0, which reads "drain what is there" and behaves as "drop the reason there is nothing"resampleAndPushIntoFIFOavcodec_receive_packetbreaking on any negative codewritePacket's rc on the bridged armPer packet all of that is correct: one bad frame must not end a session. In aggregate it made a bridge that emitted nothing for a whole first segment indistinguishable from one that had simply not been asked yet, and the only sentence the session ever produced named the muxer and the source, which are the two subsystems that were fine.
What this changes
AudioBridge.FeedStats: fed, rejected, decoded, dropped-before-FIFO, enqueued, emitted, decode/encode errors, and the decoder's own last error code instead of discarding it. One loud line the moment enough source has gone in for the silence to be structural rather than start-up latency (64 packets, two orders past the worst-case first output).scanForAudioMoovPrimeFramereturns without looking there (it is scoped to stream-copy audio by design, a bridged session has nothing in the source to scan for). A log line describing an action that never happens, on the one path where the interesting question had no line at all. It now prints the bridge's account.PlaybackErrorKind.audioBridgeProducedNoOutput. This used to arrive as.vodSourceFailed, which reads as "the source is gone" and ends a host's fallback ladder. The source is neither gone nor unreadable here, and a second player that decodes the track itself plays the file, so a ladder should DEMOTE on this kind, not stop.Verification
Fixture A/B with
aetherctl play:state=error("Source audio cannot be muxed (code -22)"), no mention of audio decoding anywhere in the sessionAE#396 seg-0.m4s cut deferred: the audio sample entry is built from a BRIDGED packet and the bridge has muxed none. Bridge: fed=2 decoded=0 enqueued=0 emitted=0 rejected=2 lastDecodeError=Invalid data found when processing input (-1094995529), thenstate=error("Audio track could not be decoded (code -22)")immediatelyControl: the same fixture undamaged plays unchanged (
item.audioTrack codec='ec-3' sr=44100 ch=1, no new lines, no early exit). Long-GOP and normal-GOP variants both clean.Issue396SilentAudioBridgeTests: 4 tests, including a control that a bridge which DID emit keeps the muxer verdict (verified to fail when the branch is made unconditional).1936 swift-testing + 547 XCTest green, no new warnings.
🤖 Generated with Claude Code
https://claude.ai/code/session_01E5UztmCZtNYPQpzdmSdmWG