Skip to content

fix(audio): preserve compatible playback and recover failed Windows outputs - #16150

Draft
fufesou wants to merge 15 commits into
rustdesk:masterfrom
fufesou:fix/audio-playback-recovery
Draft

fix(audio): preserve compatible playback and recover failed Windows outputs#16150
fufesou wants to merge 15 commits into
rustdesk:masterfrom
fufesou:fix/audio-playback-recovery

Conversation

@fufesou

@fufesou fufesou commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

When remote capture restarts, a failed playback replacement can discard a usable output. On Windows, a terminal WASAPI error can also leave playback silent until reconnecting. Preserve compatible playback through replacement startup and recover terminally failed outputs on the audio owner thread.

Changes

  • Build non-Linux replacement playback with independent state. On compatible synchronous failure, retain the old stream, PCM, resampler, channel configuration and readiness, while installing a fresh Opus decoder for the restarted remote encoder.
  • On Windows, retain the compatible output until the candidate's first playback callback. An Ok(()) from play() only acknowledges command submission. Reject already-terminal candidates and roll back errors arriving before confirmation. Keep decoding and playing incoming packets on the old output while waiting, then transfer that decoder history when promoting the candidate. Before a later valid format supersedes the pending attempt, resolve readiness and errors so a started, usable candidate can become the retained output. Preserve a terminal-failure result after draining its queue. This resolution does not reopen the old format.
  • Queue Windows callback errors for owner-thread logging, teardown and reopening of the current default output. Pace retries at one-second intervals, including during silence. Keep candidate error queues independent, preserve recovery when both outputs fail, and log nonterminal priority warnings without reopening playback. Process the next queued format before recovery and exit when receiver disconnection is observed.
  • Temporarily pin CPAL to b8ad274 fufesou/cpal@b8ad274. Dependency fix: fix(wasapi): release command events after worker exit rustdesk-org/cpal#6. Before merging this PR, switch the dependency back to rustdesk-org/cpal at its merged fix. The exact dependency range fufesou/cpal@69ad257...b8ad274 contains only WASAPI command-event ownership, worker cleanup, notification error propagation and their regressions. Retain shared event ownership until both the stream and worker release it. Follow upstream teardown: attempt termination, join independently of notification success, skip self-join, and discard termination/join errors without stderr diagnostics. Normal play/pause notification errors still propagate. No other dependency is updated.

Related: #16095 #16095 introduced the affected playback replacement and capture-worker paths. #15827 #15827 and discussion #15828 #15828 report audio returning after reconnecting; those reports do not establish either failure mechanism fixed here.

Tests

Windows 11 manual results are recorded in local test-16150.md (2026-09-13/14, head bf6f7e57f9e1f8154eb7f9fb9e5516d02aad4338); T01-T13 refer to that record. Earlier native and automated tests are identified by commit below. T12 is the main remaining manual check. Extended cases remain unchecked, and the voice-delay and USB-output observations below remain open.

  • Desktop stereo baseline: hear left, right and both channels in the correct order through the USB headset. (T01a)
  • Desktop tone baseline: stable 48 kHz playback for at least one minute; a 44.1 kHz mono file plays at the same pitch. These two checks used one working speaker and do not establish a protocol-format change. (T01b/d)
  • Voice call: speak both ways; hear both sides clearly, including 30 seconds each and 10 seconds of simultaneous speech. (T02)
  • During a call, disable/re-enable each PC's outputs in turn; voice returns without redialing. Three recoveries per receiving PC passed; this does not close the latency observation. (T04)
  • Repeat output recovery during silence; speak afterward and hear both sides without redialing. Both receiving directions tested. (T05)
  • During audio recovery, live video, mouse and keyboard still work. (T03/T04)
  • Desktop audio: invalidate the active output; sound recovers on another available default output without reconnecting. (T03)
  • Desktop audio: keep all controller outputs unavailable for about 10 seconds; retries and remote input remain responsive. Re-enable one output: sound returns without a new format announcement or reconnect. (T03)
  • Desktop silence: playback recovers during the zero-gated silent interval; the final five-second tone plays steadily without restarting capture or reconnecting. (T05)
  • Switch between USB and built-in microphones and back on each PC; the other side hears speech from each selected input. Host-side retests passed after replugging the controller's USB headset; the initial silence remains noted below. (T06)
  • End/start voice calls three times: microphone transmission stops when ended and both directions return after accepting a new call. Only the first cycle included the full 30/30/10-second speech sequence. (T06)
  • End a voice call: desktop sound returns and stays steady for about 20 seconds without reconnecting the desktop session. (T06)
  • Idle shutdown/restart: native testing on e545fa6 confirmed the parked encoder exits and the same host process resumes capture after reconnecting. The extra three idle reconnects were not repeated in the guided run and remain an optional extended check. (T06 step 6)
  • Close the remote window while all outputs are unavailable and audio recovery is retrying; the session I/O and audio loops exit normally while RustDesk returns to its main window. Restore devices and reconnect: USB headset audio is steady for 30 seconds without crackling or dropouts. (T06 step 7)
  • Switch host desktop input from System Sound to CABLE Output and back at the same logged 48 kHz/stereo format; both restarts recover stable, same-pitch sound without reconnecting. (T07)
  • Change the logged remote channel count: 48 kHz/stereo -> mono -> stereo. Mono playback has the correct pitch; the return to stereo occurred during source silence and was followed by a successful left/right/both listening check. (T07)
  • Change the logged remote format: 48 kHz/stereo -> 24 kHz/stereo -> 48 kHz/stereo. The first transition recovers after the blocked-output failure in T09; the return succeeds during source silence. After each recovery, tone is steady and unchanged in pitch for 30 seconds without reconnecting. This does not claim uninterrupted playback across the changes. (T07/T09)
  • Make B default and hold it exclusively; restart capture at the same 48 kHz/stereo format. The logged replacement failure during source silence retains A; resumed tone is steady on A for 30 seconds without reconnecting. This does not claim uninterrupted tone across the restart. (T08)
  • Release B and trigger another same-format announcement: B opens, its output callback starts, and tone is steady on B for 30 seconds without reconnecting. (T08)
  • Hold B exclusively and change the remote format from 48 kHz/stereo to 24 kHz/stereo: opening B fails and recovery retries, with no compatible-stream retention or successful output callback logged before release. Release B: same-pitch audio is steady on B for 30 seconds without changing the source, sending another format announcement or reconnecting. (T09)
  • Decoder reset: native testing on e545fa6 retained A after B failed to open. Generated silent Opus decoded exactly like a fresh decoder, unlike the old-decoder control; the checked output window contained zero PCM. Decoder regressions also passed with both resamplers on b5146c2. (T10)
  • Extended end-to-end check: while retaining A, restart actual remote capture from tone to independently verified silence; check for an old-tone burst after queued audio drains. The controlled decoder tests do not complete this remote-capture case. (T10)
  • Controlled native startup: real WASAPI streams on b5146c2, with both resamplers, verified that A keeps consuming audio while B awaits confirmation, B is promoted after confirmation, and A survives an injected candidate failure before confirmation. (T11)
  • Extended hardware check: reproduce a real delayed native startup failure after play() returns but before the first output callback; the compatible old output must survive. This hardware failure has not been reproduced. (T11)
  • Run music/voice for 30 minutes with device changes; no lasting silence, distortion or steady handle growth. (T12)
  • New Windows <-> new/old Android, macOS and Linux: cross-platform checks completed as reported by the user. (T13)

Scope and limitations

The application runtime changes are confined to non-Linux format replacement and Windows playback recovery/confirmation, including the incoming voice-call owner. The dependency fix changes WASAPI input/output stream notification errors and destruction. Linux playback, non-Windows CPAL backends, capture queue policy, codec settings and the wire format are unchanged. A healthy compatible output retained after replacement failure stays active; a subsequent format announcement or terminal failure can trigger another replacement. Callback confirmation does not prove audible output.

Windows 7 system
loopback remains unsupported by the pinned event-driven WASAPI capture path: Microsoft's loopback documentation https://learn.microsoft.com/en-us/windows/win32/coreaudio/loopback-recording describes the missing capture events before Windows 10 1703. This predates #16095. Microphone/input-device capture remains enabled. #16095 moved encoding/message submission into the worker and introduced the callback-to-worker unpark() dependency. With Rust 1.75 on Windows 7 https://github.com/rust-lang/rust/blob/1.75.0/library/std/src/sys/windows/thread_parking.rs, notification can wait between publication of PARKED and entry into NtWaitForKeyedEvent; park_timeout() does not bound that callback wait. The previously accepted Win7 scheduling risk remains unmeasured and is outside these two playback/cleanup fixes.

Earlier hardware runs do not complete the unchecked cases. The guided Windows 11 checks above cover basic audibility and recovery; sustained voice quality and 30-minute endurance/handle trends remain unverified. T04 still records a subjectively estimated ~1 s voice delay, also observed in a new call before another device fault. Its cause and whether it is a regression remain undetermined; callback timings are not end-to-end latency measurements. During T06, the controller's USB output became silent even for local playback. Microphone-switch retests passed after replugging the headset, but the initial silence remains unexplained. Earlier recordings with gaps and PCM buffer discards remain part of the evidence.

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fufesou fufesou changed the title fix(audio): recover Windows playback after output device failure fix(audio): preserve playback, recover failed outputs, and fix Windows 7 wakeups Sep 10, 2026
@fufesou

fufesou commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@fufesou fufesou changed the title fix(audio): preserve playback, recover failed outputs, and fix Windows 7 wakeups fix(audio): preserve compatible playback and recover failed Windows outputs Sep 12, 2026
Keep a compatible active output until the replacement callback confirms startup, preserving decoder progress on promotion or rollback. Handle superseding formats and simultaneous output failures without losing recovery state. Pin the scoped CPAL WASAPI event-ownership fix and add deterministic and native regressions.
Pin the upstream-aligned WASAPI cleanup with shared event lifetime, self-join prevention and fallible destructor diagnostics. Preserve the existing dependency graph and audio implementation.
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