Fix audio crackle at the start of every synthesized utterance#137
Conversation
TtsService called callback.start() before any audio had been generated, so the system AudioTrack began draining an empty buffer while the model computed its first inference chunk (slower than steady-state, since onnxruntime warms up its execution graph on first use). The race caused an audible crackle/underrun at the start of every utterance played through the system TTS API (confirmed via AudioTrack "disabled due to previous underrun, restarting" in logcat). Likely the same root cause as woheller69#90, which reports it as a clipped first word instead of a crackle. Buffer ~300ms of audio before calling callback.start(), giving the AudioTrack a pre-roll cushion so inference no longer races playback. Short utterances that finish generating before the threshold is reached flush whatever was buffered once generation completes, so added latency stays proportional to utterance length rather than a flat per-utterance cost. Tested on a Pixel 7a: before the fix, every utterance played through the system TTS API produced an audible crackle and an AudioTrack underrun warning in logcat; after the fix neither reproduces across repeated test utterances in different languages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
if there is an issue, which I never noticed, it should be fixed in the upsteam project |
|
Thanks for taking a look. To be precise about where the race actually lives: The race: before this fix, Even a faster sherpa-onnx wouldn't remove this race, only shrink the window — any time synthesis is momentarily slower than real-time playback (cold start, background CPU contention, a slower device), the same underrun would recur unless the app buffers ahead before starting playback. That's why the fix is a small pre-roll buffer (~300ms) in Issue #90 describes the same symptom (clipped first word instead of an audible crackle) — I believe it's the same underlying race, just manifesting slightly differently depending on exactly how the underrun lands. Happy to answer questions if useful — otherwise I'll keep the fix in my fork since it resolves the issue for my install either way. |
Summary
TtsService.onSynthesizeTextcalledcallback.start()before any audio had been generated, so the systemAudioTrackbegan draining an empty buffer while the model computed its first inference chunk (slower than steady-state, since onnxruntime warms up its execution graph on first use). This race causes an audible crackle/underrun at the start of every utterance played through the system TTS API.AudioTrack: restartIfDisabled(...): disabled due to previous underrun, restartingin logcat, reliably reproducing on every utterance.callback.start(), giving theAudioTracka pre-roll cushion so inference no longer races playback. Short utterances that finish generating before the threshold is reached flush whatever was buffered once generation completes, so added latency stays proportional to utterance length rather than a flat per-utterance cost.Test plan
applicationIdSuffix) to avoid disturbing the existing production install.adb logcatthat before the fix, every utterance played through Settings → Text-to-speech output → "Play" (which exercises the realonSynthesizeTextpath) produced anAudioTrackunderrun warning; after the fix it does not, across repeated tests in different languages (including a cold model load for a not-yet-cached language)../gradlew :app:compileDebugKotlinsucceeds with no new warnings.🤖 Generated with Claude Code