Repository navigation
Android AutoMix: mix across sample rates, and correct the clock after a lead-in - #236
Merged
Merged
Conversation
When B's sample rate differs from A's, DefaultAudioSink can't join gaplessly: it drains A, flushes once still naming A (the new configuration is swapped in after that flush), then flushes for B with no end of stream in between. TransitionMixer took that second flush as a seek, so the mix was dropped without a log line and A cut off at its clip point. Every flush between an end of stream and the next frame now belongs to the join. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sink's clock counts output frames, and a mix outputs the tail's first incoming_at_ms before B from incoming_from_ms, so B's clock runs ahead by the difference. The backend mapped that back for B, but the lead carried into the next item: ExoPlayer moved on early (gapless) and the renderer, whose position never goes back, held the next item at the lead (a 48 kHz track reported 14 s in at its start). MixClockSink wraps the sink: once the tail has sounded the backend asks it to take the lead out, as a position discontinuity the renderer accepts; the correction drops when the sink re-bases its clock at the next item's first frame, or at a flush. The backend keeps adding the offset until ExoPlayer's position shows the step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k switch-over - A flush continues a join only at the item's start: a seek while the sink waits for A to play out (a format change) no longer starts the mix at the seek target. - The backend switches to ExoPlayer's corrected position once the sink has applied the correction and ExoPlayer's own reading stepped by the lead, read every 40 ms until then, instead of guessing from wall time (a pause right after the correction could keep the wrong one). - No correction within 3 s of B's end, where it could land on the next item after the sink's re-base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
From diagnostics report
R-20261110-af7k("automix cut from Ten to Perfect Pinterest Garden without fading").Sample rate change dropped the mix
Every transition between a 44.1 kHz and a 48 kHz track in that log rendered its tail but never started. When the format changes,
DefaultAudioSinkcan't join gaplessly. It drains A, flushes once still naming A (the new configuration is swapped in after that flush), then flushes for B with no end of stream in between.TransitionMixerread that second flush as a seek, so the mix ended with no log line and A cut off at its clip point.Fix: every flush between an end of stream and the next frame belongs to the join (
TransitionMixer.startItem). Once frames flow, a flush without an end of stream is a seek again.ExoPlayer's clock after a mix with a lead-in
The same log has B reported at 14031 ms when it should have been 0. The sink's clock counts output frames, and a mix outputs
incoming_at_msof tail before B fromincoming_from_ms, so the clock runs ahead by the difference (15.4 s for Ten). The backend mapped that back for B, but the lead carried into the next item. ExoPlayer moves on early by the lead at a gapless join. The renderer's position never goes back, so it held the next item at the lead.Fix:
MixClockSink(aForwardingAudioSink) takes the lead out once the tail has sounded. The step is signalled as a position discontinuity so the renderer accepts it. The correction drops when the sink re-bases its clock at the next item's first frame (checkpoint or new AudioTrack), or at a flush.ExoBackendkeeps adding the offset until ExoPlayer's position shows the step, so reports stay continuous. Leads under 100 ms are left alone.Testing
TransitionMixerTest.aFormatChangeAtTheJoinKeepsTheMixreplays the sink's flush sequence. NewMixClockSinkTestcovers both directions of the lead, the re-base, pause and flush.:playback:testDebugUnitTest: 158 tests pass.DefaultAudioSink/MediaCodecAudioRenderer/ExoPlayerImplInternal.🤖 Generated with Claude Code