fix(desktop): keep the huddle playout tick on schedule on Windows - #5973
fix(desktop): keep the huddle playout tick on schedule on Windows#5973kaalph wants to merge 2 commits into
Conversation
The 10 ms playout tick uses MissedTickBehavior::Delay, which never shortens the ticks that follow a missed one. Windows timers default to a 15.6 ms resolution and tokio intervals fire ~14.6 ms late there on average (tokio-rs/tokio#5021), so the loop settles at ~62 of the 100 pulls/s the pipeline needs. The per-peer rodio queues run dry (audible gaps, dropped words) while NetEq stays full and time-compresses playback (metallic, sped-up voices) - on every peer, regardless of network quality. Matches the choppy-audio reports in block#2652; block#4281 changed the drop threshold but not the tick rate. Measured on Windows 11 with a standalone reproduction of this loop: 62.4 ticks/s with Delay at default resolution, 100.2 ticks/s with timeBeginPeriod(1) raised for the loop lifetime and Burst making up missed ticks. Catch-up bursts are absorbed by the existing queue recovery (hysteresis 10-4, emergency trim at 30). Signed-off-by: kaalph <alexsanker@hotmail.com>
themiguelamador
left a comment
There was a problem hiding this comment.
Two blocking findings:
- The new WinMM calls introduce production
unsafeblocks, which violates this repository's no-new-unsafe gate. They also ignoretimeBeginPeriodfailure and unconditionally calltimeEndPeriod, so the begin/end contract is not reliably balanced. MissedTickBehavior::Burstleaves catch-up unbounded. After suspend or a long runtime stall, the biased select loop can replay a very large 10 ms backlog and monopolize playout; the player queue high-water mark bounds queued audio, not timer iterations.
I fixed both in Complear/buzz@abbb15ce5 (branch review/pr-5973-fix): Windows now uses a safe per-timer high-resolution waitable timer, and stale catch-up is reset after 300 ms while short catch-up remains intact. Added focused deadline tests. Verification: full Tauri suite passed before the reset-only refinement (2442 passed, 15 ignored); strict Clippy and formatting pass after it; the Windows timer API path cross-compiles for x86_64-pc-windows-msvc. A final focused relink was blocked by the Sherpa prebuilt archive download timing out after its local cache disappeared.
…h-up Review follow-up. timeEndPeriod is now only paired with a timeBeginPeriod that actually succeeded (TIMERR_NOERROR), instead of firing unconditionally from the drop guard. And Burst catch-up is bounded: a tick gap beyond 300 ms (suspend, long runtime stall) resets the interval and drops the backlog instead of replaying it - NetEq holds at most 200 ms of audio, so a backlog older than that only replays silence and stalls the select loop. Signed-off-by: kaalph <alexsanker@hotmail.com>
|
Thanks for the look. Both points are addressed in 15a1b23. On the begin/end contract: fair catch. The guard now only gets created when On the unbounded catch-up: agreed, that was a real gap. A tick arriving more than 300 ms after the previous one now resets the interval instead of replaying the backlog. NetEq holds at most 200 ms of audio, so anything older than that would only have drained silence while hogging the biased select — dropping it and realigning the cadence is strictly better. Short catch-up (the case the fix is actually for) is untouched. On the One thing I couldn't do: your Complear/buzz@abbb15ce5 link 404s for me, so I wasn't able to compare against your branch. If it's public somewhere else, point me at it. Verified on my side: the touched paths type-check for |
Huddle audio on my Windows 11 box was constantly choppy — metallic voices, dropped words, every call, every peer. Sounded exactly like the reports in #2652. Network was my first suspect and it was a dead end: I measured the incoming frame stream over ten minutes and got p99 inter-frame gap of 22 ms with zero losses. So the frames arrive fine and something eats them locally.
Root cause is the 10 ms playout tick in
run_playout_recv_loop. It usesMissedTickBehavior::Delay, andDelaynever shortens the ticks that follow a missed one. Windows timers default to a 15.6 ms resolution, and tokio intervals fire ~14.6 ms late there on average (tokio-rs/tokio#5021). Put those together and the loop settles at ~62 pulls per second instead of 100. The per-peer rodio queues run dry and the mixer plays silence gaps, while NetEq sits at its 200 ms cap and time-compresses playback forever — that's the robot voice. #4281 later moved the drop threshold to speed recovery, but the tick rate itself never changed, which would be why the reports kept coming.To verify I wrote a small standalone program with exactly this interval setup and ran it on Windows 11 (IoT LTSC 2024): 62.4 ticks/s with
Delayat default resolution, 100.2 ticks/s withtimeBeginPeriod(1)active andBurst. The machine dependence falls out of this too — any other process can raise the global timer resolution, so some machines never show the bug.The fix does two things:
timeEndPeriod). Gated behind#[cfg(windows)], so other platforms are untouched.Burst, so missed ticks are made up instead of silently lost. The short catch-up bursts stay bounded by the queue recovery that's already there (hysteresis 10→4, emergency trim at 30).We've been running this in real calls since yesterday and the audio is clean — no gaps, no acceleration, and I couldn't hear any regression. I haven't exercised macOS or Linux beyond the fact that the resolution guard doesn't compile there and
Burstonly changes behavior when ticks are actually missed.The
unsafeblocks are the twowinmmFFI calls; there was no way around them that I could find, and the desktop crate already does OS-level FFI the same way inmouse_nav.rsandshutdown.rs.