fix: recover from WASAPI device invalidation instead of going silent#55
Merged
Conversation
AUDCLNT_E_DEVICE_INVALIDATED (0x88890004) fires when the output device is pulled out from under WASAPI - the default device changes, a device is disabled, unplugged or reformatted, or another app grabs it in exclusive mode. The player dropped to a terminal Error state and stayed silent until the app was restarted (the buffer just filled and overran with nobody reading it). Detect that HResult in OnPlaybackStopped and re-initialize the output on the current device, resuming from the buffered audio. Recovery reuses the device-switch rebuild path (preserves the buffer, re-anchors timing) via a new forceResume flag, retries up to 5 times with a short backoff so Windows can settle a new default device, and only falls back to Error if all attempts fail. The previous output's Stop() is now guarded, since a dead device throws on Stop().
…ured, not guessed GetActualOutputLatency() queried IAudioClient.StreamLatency before WasapiOut.Init() ran, so every call threw AUDCLNT_E_NOT_INITIALIZED (0x88890001) and fell back to a fixed 115ms estimate - wrong for any DAC whose real latency differs, which is what forces a per-device manual static-delay offset. Move the query to after Init() (SetSampleSource and the device-switch path), where the AudioClient is initialized and StreamLatency returns the real device latency. InitializeAsync keeps a preliminary estimate only for its own init log. Promotes the shared latency constants to class scope. Note: making this drive auto-compensation still needs SDK-side work - the pipeline reads OutputLatencyMs before SetSampleSource (so it gets the estimate) and treats OutputLatencyMicroseconds as informational rather than subtracting it from the schedule.
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.
The bug (from a live log)
Playback went permanently silent. The log showed:
0x88890004isAUDCLNT_E_DEVICE_INVALIDATED— the output device was pulled out from under WASAPI (default device changed, device disabled/unplugged/reformatted, or grabbed in exclusive mode). The player dropped to a terminalErrorstate and never recovered; the network kept feeding the buffer, which pinned at capacity and just churned overruns with nobody reading. Silent until app restart.Fix
Detect that HResult in
OnPlaybackStoppedand re-initialize the output on the current device, resuming from the buffered audio, instead of dying:forceResumeflag.Errorif every attempt fails.Stop()is now guarded — a dead/invalidated device throws onStop().Net effect: a device change that used to silence playback forever becomes a brief blip that self-recovers.
Scope / risk
App-only, independent of the SDK PRs. No change to the normal
Clear()/play paths; the only edit to the device-switch path is the additiveforceResumeparameter and the guardedStop().Verify
Manual (it's a hardware/OS event): play audio, then in Windows Sound settings change the default output device (or disable the playing device). Expect a ~½–1s gap, then audio resumes and the log shows
Recovered audio output after device invalidation. Build green, 99/99 tests pass.