Fix AudioStream catch-up clicks: add cooldown + crossfade#260
Merged
MaxHeimbrock merged 7 commits intomainfrom Apr 28, 2026
Merged
Fix AudioStream catch-up clicks: add cooldown + crossfade#260MaxHeimbrock merged 7 commits intomainfrom
MaxHeimbrock merged 7 commits intomainfrom
Conversation
The drift-correction block in AudioStream.OnAudioRead was tripping on almost every audio callback and calling RingBuffer.SkipRead, which is a raw pointer move that produces a step discontinuity in the waveform — audible as a click/pop train under any sustained tone. Two causes: HighWaterMarkPercent at 0.50 meant normal jitter (a 30–40 ms burst of 10 ms WebRTC frames) routinely parked the buffer above the skip threshold, so the skip fired every callback; and each skip dropped samples without any crossfade. - HighWaterMarkPercent 0.50 -> 0.80 so only genuine drift near overflow triggers catch-up, not normal jitter. - Add SkipCooldownCallbacks = 10 so we never back-to-back skip, which is what produces the gravelly artifact train. - When we do skip, read a 128-frame (~2.7 ms @ 48 kHz) post-skip window and linearly crossfade it into the tail of the output. The seam becomes a short linear ramp instead of a step; inaudible on voice and music. - Guard with an AvailableRead() >= skipBytes + crossfadeBytes check so we never trade a catch-up click for an underrun click. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bc69c08 to
6b3f700
Compare
|
|
||
| // Pre-buffering state to prevent audio underruns | ||
| private bool _isPrimed = false; | ||
| private const float BufferSizeSeconds = 0.2f; // 200ms ring buffer for all platforms |
Contributor
There was a problem hiding this comment.
I wonder if we should bump up the buffer size to 250ms for now ? to make sure we can handle the drift better ?
| // HWM at 50% (100ms of 200ms) so normal network jitter does not trip catch-up. | ||
| // Cooldown prevents back-to-back skips, which sound like a gravelly click train; | ||
| // one occasional skip is inaudible thanks to the crossfade in OnAudioRead. | ||
| private const float HighWaterMarkPercent = 0.50f; |
Contributor
There was a problem hiding this comment.
it is still 50% for the HIghWatermarkPercent ?
| // Cooldown prevents back-to-back skips, which sound like a gravelly click train; | ||
| // one occasional skip is inaudible thanks to the crossfade in OnAudioRead. | ||
| private const float HighWaterMarkPercent = 0.50f; | ||
| private const float SkipPerCallbackPercent = 0.05f; |
Contributor
There was a problem hiding this comment.
I think we'd better re-visit the logic, does skipping 5% of audio noticable ?
if it is noticeable, we'd better re-consider the approach. Like will it work better if we just skip initial 50ms of audio in the buffer so that we will have a continuous audio stream later ? or should we increase the buffer size ?
xianshijing-lk
approved these changes
Apr 27, 2026
Contributor
xianshijing-lk
left a comment
There was a problem hiding this comment.
lgtm, we can ship the current version though it is slightly more complicated than I would expect.
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.
Summary
RingBuffer.SkipRead, which is a raw pointer move. The resulting step discontinuity produces a click/pop train under sustained tones.SkipCooldownCallbacks = 10) so we never skip back-to-back — that is what turns a single correction into an audible artifact train.AvailableRead() >= skipBytes + crossfadeBytesso we never trade a catch-up click for an underrun click.Scoped change: one file (
Runtime/Scripts/AudioStream.cs), +49/-10. No public API change, no RingBuffer/Resampler changes.Test plan
Samples~/Basicagainstlivekit-server --devwith a sustained-tone or music publisher. Before the fix: click train within seconds. After: no perceptible artifacts over a multi-minute session._buffer.AvailableRead()— fill should oscillate in the 30–160 ms band, not park at 100 ms, and should not pin at the HWM.OnApplicationPausepath unchanged).