feat(media): preroll move-paired sounds to remove playbin warmup skew - #1332
Draft
tfrere wants to merge 1 commit into
Draft
feat(media): preroll move-paired sounds to remove playbin warmup skew#1332tfrere wants to merge 1 commit into
tfrere wants to merge 1 commit into
Conversation
audio_lead_ms (PR #1143) was partly compensating the GStreamer playbin warmup: set_state(PLAYING) lazily opens the sink, decodes and fills the ring buffer, and that delay is variable (device state, file, CPU load), so no constant lead could ever cancel it - the protocol doc suggests +0..100 ms while the JS SDK default ended up at -100 ms. Preroll instead: drive the playbin to PAUSED (the slow part) before the motion clock starts, then flip it to PLAYING at t0 so the first sample hits the speaker essentially immediately. audio_lead_ms becomes a pure artistic offset with a stable meaning, and lead=0 means genuinely in sync. play_move offloads the blocking preroll to an executor and falls back to the legacy start if the preroll fails. A cancelled negative-lead sound now also releases the prerolled pipeline via stop_sound. scripts/measure_playbin_warmup.py rebuilds the exact daemon pipeline (playbin + tee sink bin, sync=true appsink as a render-time probe) and measures cold vs prerolled start latency in isolation on the robot.
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
Preroll move-paired sounds so audio starts deterministically (~1 ms) instead of a variable 15-50 ms after the motion clock.
audio_lead_ms = 0then truly means "audio and motion start together".Problem
Every
play_sound()rebuilds its audio player from scratch - detect the format, spin up the decoder, open the ALSA device. That takes 15-50 ms and is never the same twice (spikes past 100 ms under CPU load), while the motion starts instantly: the gesture always leads the sound by an amount no fixedaudio_lead_mscan cancel.Solution
Build the player before the go signal:
prepare_sound()prerolls the playbin toPAUSED(the slow part: sink open + decode + buffer fill)start_prepared_sound()flips it toPLAYINGat t0 (~1 ms, deterministic, load-independent)Backend._play_moveprerolls off the event loop (executor) before the motion clock starts, then starts the prepared sound at the requested leadBenchmarks
On the robot (wireless CM4, daemon running,
wake_up.wav, 20 runs - time from thePLAYINGrequest to the first buffer reaching the daemon's own ALSA sink):play_sound()at t0With 3 CPU cores saturated, cold median rises to ~48 ms with spikes >100 ms (measured with the fuller probe in
scripts/measure_playbin_warmup.py, which rebuilds the daemon's complete tee sink bin); primed stays at 1-4 ms regardless of load.How to reproduce
Standalone script, no reachy_mini import:
python3 warmup_minimal.py /path/to/wake_up.wav 20warmup_minimal.pyLimitations / open questions (why this is a draft)
play_movepath. Motor chain latency is out of scope._play_moveis also needed for the synchronousplay_sound()callers (wake_up/goto_sleep)play_soundduring the lead window swaps it under the moveRelated
audio_lead_msas an empirical knob; this PR gives it back a single meaning (artistic offset)audioLeadMs = -100(ts/lib/reachy-mini.ts:1645) - that constant should not ship in this week's frozen SDK release, regardless of this PR's fatemedia_server.pyconflict for whichever lands secondMade with Cursor