Skip to content

fix(desktop): keep the huddle playout tick on schedule on Windows - #5973

Open
kaalph wants to merge 1 commit into
block:mainfrom
kaalph:fix-windows-playout-tick
Open

fix(desktop): keep the huddle playout tick on schedule on Windows#5973
kaalph wants to merge 1 commit into
block:mainfrom
kaalph:fix-windows-playout-tick

Conversation

@kaalph

@kaalph kaalph commented Aug 15, 2026

Copy link
Copy Markdown

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 uses MissedTickBehavior::Delay, and Delay 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). 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 Delay at default resolution, 100.2 ticks/s with timeBeginPeriod(1) active and Burst. 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:

  1. Raises the timer resolution to 1 ms for the lifetime of the playout loop, handed back through a drop guard (timeEndPeriod). Gated behind #[cfg(windows)], so other platforms are untouched.
  2. Switches the playout tick to 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 Burst only changes behavior when ticks are actually missed.

The unsafe blocks are the two winmm FFI calls; there was no way around them that I could find, and the desktop crate already does OS-level FFI the same way in mouse_nav.rs and shutdown.rs.

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>
@kaalph
kaalph requested a review from a team as a code owner August 15, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant