From 9bec57309c2b624c7010ebbcea245528296fac44 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 26 Oct 2023 22:59:02 -0400 Subject: [PATCH] wasapi: Proxy default device change handling to management thread. This does a ton of work that can deadlock, because several crucial WASAPI things that we want to do in response to this will block until the notification callback has returned, so we can't call them from the handler directly, or we'll be waiting until the thing that called us returns. --- src/audio/directsound/SDL_directsound.c | 2 +- src/audio/wasapi/SDL_wasapi_win32.c | 13 ++++++++++++- src/core/windows/SDL_immdevice.c | 12 ++++++++++-- src/core/windows/SDL_immdevice.h | 4 +++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c index 432e740f8..74fc522be 100644 --- a/src/audio/directsound/SDL_directsound.c +++ b/src/audio/directsound/SDL_directsound.c @@ -647,7 +647,7 @@ static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl) } #ifdef HAVE_MMDEVICEAPI_H - SupportsIMMDevice = !(SDL_IMMDevice_Init() < 0); + SupportsIMMDevice = !(SDL_IMMDevice_Init(NULL) < 0); #endif impl->DetectDevices = DSOUND_DetectDevices; diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c index 19dfbd4d9..b69515451 100644 --- a/src/audio/wasapi/SDL_wasapi_win32.c +++ b/src/audio/wasapi/SDL_wasapi_win32.c @@ -48,11 +48,22 @@ static SDL_bool immdevice_initialized = SDL_FALSE; // Some GUIDs we need to know without linking to libraries that aren't available before Vista. static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; +static int mgmtthrtask_DefaultAudioDeviceChanged(void *userdata) +{ + SDL_DefaultAudioDeviceChanged((SDL_AudioDevice *) userdata); + return 0; +} + +static void WASAPI_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) +{ + WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, new_default_device, NULL); // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock. +} + int WASAPI_PlatformInit(void) { if (FAILED(WIN_CoInitialize())) { return SDL_SetError("CoInitialize() failed"); - } else if (SDL_IMMDevice_Init() < 0) { + } else if (SDL_IMMDevice_Init(WASAPI_DefaultAudioDeviceChanged) < 0) { return -1; // Error string is set by SDL_IMMDevice_Init } diff --git a/src/core/windows/SDL_immdevice.c b/src/core/windows/SDL_immdevice.c index 780eff40d..583c71ff2 100644 --- a/src/core/windows/SDL_immdevice.c +++ b/src/core/windows/SDL_immdevice.c @@ -37,6 +37,7 @@ static const ERole SDL_IMMDevice_role = eConsole; /* !!! FIXME: should this be e /* This is global to the WASAPI target, to handle hotplug and default device lookup. */ static IMMDeviceEnumerator *enumerator = NULL; +static SDL_IMMDevice_DefaultAudioDeviceChanged devchangecallback = NULL; /* PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency. */ #ifdef PropVariantInit @@ -204,7 +205,9 @@ static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationCl static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *iclient, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { if (role == SDL_IMMDevice_role) { - SDL_DefaultAudioDeviceChanged(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); + if (devchangecallback) { + devchangecallback(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); + } } return S_OK; } @@ -273,7 +276,7 @@ static const IMMNotificationClientVtbl notification_client_vtbl = { static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } }; -int SDL_IMMDevice_Init(void) +int SDL_IMMDevice_Init(SDL_IMMDevice_DefaultAudioDeviceChanged devchanged) { HRESULT ret; @@ -291,6 +294,9 @@ int SDL_IMMDevice_Init(void) WIN_CoUninitialize(); return WIN_SetErrorFromHRESULT("IMMDevice CoCreateInstance(MMDeviceEnumerator)", ret); } + + devchangecallback = devchanged ? devchanged : SDL_DefaultAudioDeviceChanged; + return 0; } @@ -302,6 +308,8 @@ void SDL_IMMDevice_Quit(void) enumerator = NULL; } + devchangecallback = NULL; + WIN_CoUninitialize(); } diff --git a/src/core/windows/SDL_immdevice.h b/src/core/windows/SDL_immdevice.h index a190a7cf8..0b64d2f55 100644 --- a/src/core/windows/SDL_immdevice.h +++ b/src/core/windows/SDL_immdevice.h @@ -28,7 +28,9 @@ typedef struct SDL_AudioDevice SDL_AudioDevice; // this is defined in src/audio/SDL_sysaudio.h -int SDL_IMMDevice_Init(void); +typedef void (*SDL_IMMDevice_DefaultAudioDeviceChanged)(SDL_AudioDevice *new_default_device); + +int SDL_IMMDevice_Init(SDL_IMMDevice_DefaultAudioDeviceChanged devchanged); void SDL_IMMDevice_Quit(void); int SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, SDL_bool iscapture); void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);