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
This commit is contained in:
bicarus
2026-07-18 03:09:37 -07:00
committed by GitHub
parent c080bbe301
commit 61a0220c1a
6 changed files with 51 additions and 25 deletions
+41 -5
View File
@@ -53,9 +53,39 @@ namespace hooks::audio {
std::string ASIO_DRIVER_NAME = ""; std::string ASIO_DRIVER_NAME = "";
bool ASIO_FORCE_UNLOAD_ON_STOP = false; bool ASIO_FORCE_UNLOAD_ON_STOP = false;
// private globals
IAudioClient *CLIENT = nullptr;
std::mutex INITIALIZE_LOCK; // for asio 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( static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
@@ -133,11 +163,17 @@ namespace hooks::audio {
void stop() { void stop() {
log_info("audio", "stopping"); log_info("audio", "stopping");
if (CLIENT) { IAudioClient *client = nullptr;
CLIENT->Stop(); {
CLIENT->Release(); std::lock_guard lock(CLIENT_LOCK);
CLIENT_STOPPING = true;
client = CLIENT;
CLIENT = nullptr; CLIENT = nullptr;
} }
if (client) {
client->Stop();
client->Release();
}
stop_low_latency(); stop_low_latency();
// release the ASIO drivers we kept pinned for the process lifetime now that we are // release the ASIO drivers we kept pinned for the process lifetime now that we are
+2
View File
@@ -14,4 +14,6 @@ namespace hooks::audio {
extern std::mutex INITIALIZE_LOCK; extern std::mutex INITIALIZE_LOCK;
extern bool VOLUME_HOOK_ENABLED; extern bool VOLUME_HOOK_ENABLED;
extern LowLatencyAudioClient *LOW_LATENCY_CLIENT; extern LowLatencyAudioClient *LOW_LATENCY_CLIENT;
void set_active_client(IAudioClient *client, const char *source);
} }
@@ -95,11 +95,6 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
} }
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock); 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; IAudioClient *client = nullptr;
if (iid == IID_IAudioClient) { if (iid == IID_IAudioClient) {
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface)); client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
@@ -107,9 +102,6 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface)); client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
} }
*ppInterface = client; *ppInterface = client;
// persist the audio client
hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef();
} else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) { } else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) {
*ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast<IAudioEndpointVolume *>(*ppInterface)); *ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast<IAudioEndpointVolume *>(*ppInterface));
@@ -153,19 +153,9 @@ HRESULT STDMETHODCALLTYPE NullMMDevice::Activate(
log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid)); log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid));
if (iid == IID_IAudioClient) { if (iid == IID_IAudioClient) {
// release any previously persisted client
if (hooks::audio::CLIENT != nullptr) {
hooks::audio::CLIENT->Release();
}
auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend())); auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend()));
*ppInterface = client; *ppInterface = client;
// persist the audio client
hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef();
return S_OK; return S_OK;
} }
@@ -302,6 +302,7 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
device_format->nChannels * (device_format->wBitsPerSample / 8)); device_format->nChannels * (device_format->wBitsPerSample / 8));
} }
hooks::audio::set_active_client(this, "WrappedIAudioClient::Initialize");
return ret; return ret;
} }
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) { 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)); log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
copy_wave_format(&hooks::audio::FORMAT, pFormat); copy_wave_format(&hooks::audio::FORMAT, pFormat);
copy_wave_format(&this->device_format, pFormat); copy_wave_format(&this->device_format, pFormat);
hooks::audio::set_active_client(this, "WrappedIAudioClient::InitializeSharedAudioStream");
return ret; return ret;
} }
@@ -75,13 +75,17 @@ HRESULT STDMETHODCALLTYPE DummyIAudioClient::Initialize(
log_info("audio::wasapi", "IAudioClient::Initialize hook hit"); log_info("audio::wasapi", "IAudioClient::Initialize hook hit");
print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat); print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat);
CHECK_RESULT(this->backend->on_initialize( HRESULT ret = this->backend->on_initialize(
&ShareMode, &ShareMode,
&StreamFlags, &StreamFlags,
&hnsBufferDuration, &hnsBufferDuration,
&hnsPeriodicity, &hnsPeriodicity,
pFormat, pFormat,
AudioSessionGuid)); AudioSessionGuid);
if (SUCCEEDED(ret)) {
hooks::audio::set_active_client(this, "DummyIAudioClient::Initialize");
}
CHECK_RESULT(ret);
} }
HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) { HRESULT STDMETHODCALLTYPE DummyIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
static std::once_flag printed; static std::once_flag printed;