From 388d39ce4a04f5676fdca28788c4501fe90ec3d3 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Thu, 12 Mar 2026 00:13:22 +1300 Subject: [PATCH 1/2] wasapi: Trigger device reconfig if the audio session is disconnected. Listen for IAudioSessionEvents::OnSessionDisconnected and direct disconnection notifications to the reconfigure_event to ensure streams reconfigure rather than freezing in WFMO when the audio session is disconnected. --- src/cubeb_wasapi.cpp | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp index 36bad4bf..7ae1c412 100644 --- a/src/cubeb_wasapi.cpp +++ b/src/cubeb_wasapi.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -317,6 +318,7 @@ struct cubeb { }; class wasapi_endpoint_notification_client; +class wasapi_session_notification_client; /* We have three possible callbacks we can use with a stream: * - input only @@ -415,6 +417,10 @@ struct cubeb_stream { audio device changes and route the audio to the new default audio output device */ com_ptr notification_client; + /* Session notification client, to be notified when the audio session is + disconnected (e.g. when an audio device is removed from the system). */ + com_ptr session_control; + com_ptr session_notification_client; /* Main andle to the WASAPI capture stream. */ com_ptr input_client; /* Interface to use the event driven capture interface */ @@ -834,6 +840,89 @@ class wasapi_endpoint_notification_client : public IMMNotificationClient { DWORD last_device_change; }; +class wasapi_session_notification_client : public IAudioSessionEvents { +public: + ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); } + + ULONG STDMETHODCALLTYPE Release() + { + ULONG ulRef = InterlockedDecrement(&ref_count); + if (0 == ulRef) { + delete this; + } + return ulRef; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface) + { + if (__uuidof(IUnknown) == riid) { + AddRef(); + *ppvInterface = (IUnknown *)this; + } else if (__uuidof(IAudioSessionEvents) == riid) { + AddRef(); + *ppvInterface = (IAudioSessionEvents *)this; + } else { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + wasapi_session_notification_client(HANDLE event) + : ref_count(1), reconfigure_event(event) + { + } + + virtual ~wasapi_session_notification_client() {} + + HRESULT STDMETHODCALLTYPE + OnSessionDisconnected(AudioSessionDisconnectReason reason) + { + LOG("session: Audio session disconnected, reason: %d", reason); + BOOL ok = SetEvent(reconfigure_event); + if (!ok) { + LOG("session: SetEvent on reconfigure_event failed: %lx", GetLastError()); + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR value, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR value, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float volume, BOOL mute, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD channel_count, + float volumes[], + DWORD changed_channel, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID grouping_param, + LPCGUID event_context) + { + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState state) + { + return S_OK; + } + +private: + LONG ref_count; + HANDLE reconfigure_event; +}; + namespace { long @@ -2643,6 +2732,22 @@ setup_wasapi_stream(cubeb_stream * stm) return CUBEB_ERROR; } + hr = stm->output_client->GetService(__uuidof(IAudioSessionControl), + stm->session_control.receive_vpp()); + if (SUCCEEDED(hr)) { + stm->session_notification_client.reset( + new wasapi_session_notification_client(stm->reconfigure_event)); + hr = stm->session_control->RegisterAudioSessionNotification( + stm->session_notification_client.get()); + if (FAILED(hr)) { + LOG("Could not register session notification client: %lx", hr); + stm->session_notification_client = nullptr; + stm->session_control = nullptr; + } + } else { + LOG("Could not get the IAudioSessionControl: %lx", hr); + } + #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME /* Restore the stream volume over a device change. */ if (stream_set_volume(stm, stm->volume) != CUBEB_OK) { @@ -2906,6 +3011,13 @@ close_wasapi_stream(cubeb_stream * stm) stm->stream_reset_lock.assert_current_thread_owns(); + if (stm->session_control && stm->session_notification_client) { + stm->session_control->UnregisterAudioSessionNotification( + stm->session_notification_client.get()); + stm->session_notification_client = nullptr; + stm->session_control = nullptr; + } + #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME stm->audio_stream_volume = nullptr; #endif From afe35a493d85e7f5ab5c012838ee743f8679b03b Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Thu, 12 Mar 2026 20:19:40 +1300 Subject: [PATCH 2/2] wasapi: Remove incorrect was_running test when reconfiguring stream. Stop() on the stream we're switching away from can return S_FALSE in some situations, resulting in a failure to start the stream we're switching to. The was_running check was made obsolete by the introduction of the stm->active check anyway, which better represent the running state of the stream from the API caller's perspective. --- src/cubeb_wasapi.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cubeb_wasapi.cpp b/src/cubeb_wasapi.cpp index 7ae1c412..a6606cdd 100644 --- a/src/cubeb_wasapi.cpp +++ b/src/cubeb_wasapi.cpp @@ -1549,13 +1549,12 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) XASSERT(stm->output_client || stm->input_client); LOG("Reconfiguring the stream"); /* Close the stream */ - bool was_running = false; if (stm->output_client) { - was_running = stm->output_client->Stop() == S_OK; + stm->output_client->Stop(); LOG("Output stopped."); } if (stm->input_client) { - was_running = stm->input_client->Stop() == S_OK; + stm->input_client->Stop(); LOG("Input stopped."); } close_wasapi_stream(stm); @@ -1573,7 +1572,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) } LOG("Stream setup successfuly."); XASSERT(stm->output_client || stm->input_client); - if (was_running && stm->output_client) { + if (stm->output_client) { hr = stm->output_client->Start(); if (FAILED(hr)) { LOG("Error starting output after reconfigure, error: %lx", hr); @@ -1582,7 +1581,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream) } LOG("Output started after reconfigure."); } - if (was_running && stm->input_client) { + if (stm->input_client) { hr = stm->input_client->Start(); if (FAILED(hr)) { LOG("Error starting input after reconfiguring, error: %lx", hr);