From 61a0220c1a45312f1a13d41074232b77356ae587 Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:09:37 -0700 Subject: [PATCH] audio: retain WASAPI clients only after initialization (#807) ## Link to GitHub Issue or related Pull Request, if one exists Fixes #782 ## Description of change This addresses an issue only seen on Linux + WINE. Defer retaining WASAPI clients until `Initialize` succeeds. Before this PR, temporary clients created during DirectShow device capability probing were retained as active audio clients, causing Wine/Linux crashes in DDR and popn. Also covers IAudioClient3, dummy, ASIO, WaveOut, and null-device initialization paths, for consistency. ## Testing WIP --- src/spice2x/hooks/audio/audio.cpp | 46 +++++++++++++++++-- src/spice2x/hooks/audio/audio_private.h | 2 + .../hooks/audio/backends/mmdevice/device.cpp | 8 ---- .../audio/backends/mmdevice/null_device.cpp | 10 ---- .../audio/backends/wasapi/audio_client.cpp | 2 + .../backends/wasapi/dummy_audio_client.cpp | 8 +++- 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/spice2x/hooks/audio/audio.cpp b/src/spice2x/hooks/audio/audio.cpp index e1d007a..dbdec25 100644 --- a/src/spice2x/hooks/audio/audio.cpp +++ b/src/spice2x/hooks/audio/audio.cpp @@ -53,9 +53,39 @@ namespace hooks::audio { std::string ASIO_DRIVER_NAME = ""; bool ASIO_FORCE_UNLOAD_ON_STOP = false; - // private globals - IAudioClient *CLIENT = nullptr; std::mutex INITIALIZE_LOCK; // for asio + + IAudioClient *CLIENT = nullptr; + static std::mutex CLIENT_LOCK; + static bool CLIENT_STOPPING = false; + + void set_active_client(IAudioClient *client, const char *source) { + IAudioClient *previous_client = nullptr; + { + std::lock_guard lock(CLIENT_LOCK); + + if (CLIENT_STOPPING || CLIENT == client) { + return; + } + if (client) { + client->AddRef(); + } + + previous_client = CLIENT; + CLIENT = client; + } + + if (previous_client) { + previous_client->Release(); + } + + if (client) { + log_info( + "audio", + "active client selected by {} after successful initialization", + source); + } + } } static HRESULT STDAPICALLTYPE CoCreateInstance_hook( @@ -133,11 +163,17 @@ namespace hooks::audio { void stop() { log_info("audio", "stopping"); - if (CLIENT) { - CLIENT->Stop(); - CLIENT->Release(); + IAudioClient *client = nullptr; + { + std::lock_guard lock(CLIENT_LOCK); + CLIENT_STOPPING = true; + client = CLIENT; CLIENT = nullptr; } + if (client) { + client->Stop(); + client->Release(); + } stop_low_latency(); // release the ASIO drivers we kept pinned for the process lifetime now that we are diff --git a/src/spice2x/hooks/audio/audio_private.h b/src/spice2x/hooks/audio/audio_private.h index 1ea2e4e..6201134 100644 --- a/src/spice2x/hooks/audio/audio_private.h +++ b/src/spice2x/hooks/audio/audio_private.h @@ -14,4 +14,6 @@ namespace hooks::audio { extern std::mutex INITIALIZE_LOCK; extern bool VOLUME_HOOK_ENABLED; extern LowLatencyAudioClient *LOW_LATENCY_CLIENT; + + void set_active_client(IAudioClient *client, const char *source); } diff --git a/src/spice2x/hooks/audio/backends/mmdevice/device.cpp b/src/spice2x/hooks/audio/backends/mmdevice/device.cpp index aeafe18..b92bc1a 100644 --- a/src/spice2x/hooks/audio/backends/mmdevice/device.cpp +++ b/src/spice2x/hooks/audio/backends/mmdevice/device.cpp @@ -95,11 +95,6 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate( } std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock); - // release old audio client if initialized - if (hooks::audio::CLIENT) { - hooks::audio::CLIENT->Release(); - } - IAudioClient *client = nullptr; if (iid == IID_IAudioClient) { client = wrap_audio_client(reinterpret_cast(*ppInterface)); @@ -107,9 +102,6 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate( client = wrap_audio_client3(reinterpret_cast(*ppInterface)); } *ppInterface = client; - // persist the audio client - hooks::audio::CLIENT = client; - hooks::audio::CLIENT->AddRef(); } else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) { *ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast(*ppInterface)); diff --git a/src/spice2x/hooks/audio/backends/mmdevice/null_device.cpp b/src/spice2x/hooks/audio/backends/mmdevice/null_device.cpp index a0b3966..424e612 100644 --- a/src/spice2x/hooks/audio/backends/mmdevice/null_device.cpp +++ b/src/spice2x/hooks/audio/backends/mmdevice/null_device.cpp @@ -153,19 +153,9 @@ HRESULT STDMETHODCALLTYPE NullMMDevice::Activate( log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid)); if (iid == IID_IAudioClient) { - - // release any previously persisted client - if (hooks::audio::CLIENT != nullptr) { - hooks::audio::CLIENT->Release(); - } - auto *client = static_cast(new DummyIAudioClient(new NullDiscardBackend())); *ppInterface = client; - // persist the audio client - hooks::audio::CLIENT = client; - hooks::audio::CLIENT->AddRef(); - return S_OK; } diff --git a/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp b/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp index 73a7067..734d466 100644 --- a/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp +++ b/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp @@ -302,6 +302,7 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize( device_format->nChannels * (device_format->wBitsPerSample / 8)); } + hooks::audio::set_active_client(this, "WrappedIAudioClient::Initialize"); return ret; } HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) { @@ -651,5 +652,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream( log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret)); copy_wave_format(&hooks::audio::FORMAT, pFormat); copy_wave_format(&this->device_format, pFormat); + hooks::audio::set_active_client(this, "WrappedIAudioClient::InitializeSharedAudioStream"); return ret; } diff --git a/src/spice2x/hooks/audio/backends/wasapi/dummy_audio_client.cpp b/src/spice2x/hooks/audio/backends/wasapi/dummy_audio_client.cpp index 2e193b1..9c678f1 100644 --- a/src/spice2x/hooks/audio/backends/wasapi/dummy_audio_client.cpp +++ b/src/spice2x/hooks/audio/backends/wasapi/dummy_audio_client.cpp @@ -75,13 +75,17 @@ HRESULT STDMETHODCALLTYPE DummyIAudioClient::Initialize( log_info("audio::wasapi", "IAudioClient::Initialize hook hit"); print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat); - CHECK_RESULT(this->backend->on_initialize( + HRESULT ret = this->backend->on_initialize( &ShareMode, &StreamFlags, &hnsBufferDuration, &hnsPeriodicity, pFormat, - AudioSessionGuid)); + AudioSessionGuid); + if (SUCCEEDED(ret)) { + hooks::audio::set_active_client(this, "DummyIAudioClient::Initialize"); + } + CHECK_RESULT(ret); } HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) { static std::once_flag printed;