SDL: audio: Split Deinitialize into two stages.

From 7a52f7b3fdbd6f26e68015ee8248f19d855b2df1 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 18 Oct 2023 10:41:20 -0400
Subject: [PATCH] audio: Split Deinitialize into two stages.

First stage happens before we destroy objects, and is generally used to
shut down hotplug. The second stage is the usual deinit, which cleans up
the lowlevel API, unloads shared libraries, etc.
---
 src/audio/SDL_audio.c                   |  4 ++++
 src/audio/SDL_sysaudio.h                |  1 +
 src/audio/alsa/SDL_alsa_audio.c         |  6 +++++-
 src/audio/android/SDL_androidaudio.c    |  2 +-
 src/audio/coreaudio/SDL_coreaudio.m     |  4 ++--
 src/audio/directsound/SDL_directsound.c | 11 ++++++++--
 src/audio/pipewire/SDL_pipewire.c       |  9 ++++++++-
 src/audio/pulseaudio/SDL_pulseaudio.c   |  6 +++++-
 src/audio/wasapi/SDL_wasapi.c           | 13 ++++++++++++
 src/audio/wasapi/SDL_wasapi.h           |  1 +
 src/audio/wasapi/SDL_wasapi_win32.c     | 27 ++++++++++++++++++++++---
 src/audio/wasapi/SDL_wasapi_winrt.cpp   | 13 +++++++++++-
 12 files changed, 85 insertions(+), 12 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 331e57c7ee8b..a5dac4b673ae 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -570,6 +570,7 @@ static int SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buf
 static int SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { return 0; /* no-op. */ }
 static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
+static void SDL_AudioDeinitializeStart_Default(void) { /* no-op. */ }
 static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ }
 static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ }
 
@@ -622,6 +623,7 @@ static void CompleteAudioEntryPoints(void)
     FILL_STUB(FlushCapture);
     FILL_STUB(CloseDevice);
     FILL_STUB(FreeDeviceHandle);
+    FILL_STUB(DeinitializeStart);
     FILL_STUB(Deinitialize);
     #undef FILL_STUB
 }
@@ -808,6 +810,8 @@ void SDL_QuitAudio(void)
         return;
     }
 
+    current_audio.impl.DeinitializeStart();
+
     // Destroy any audio streams that still exist...
     while (current_audio.existing_streams != NULL) {
         SDL_DestroyAudioStream(current_audio.existing_streams);
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index c33ac9ce08dc..0d3ec6305a5b 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -141,6 +141,7 @@ typedef struct SDL_AudioDriverImpl
     void (*FlushCapture)(SDL_AudioDevice *device);
     void (*CloseDevice)(SDL_AudioDevice *device);
     void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
+    void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
     void (*Deinitialize)(void);
 
     // Some flags to push duplicate code into the core and reduce #ifdefs.
diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index c6f86d846020..ebc2b604c58c 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -923,7 +923,7 @@ static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice
 #endif
 }
 
-static void ALSA_Deinitialize(void)
+static void ALSA_DeinitializeStart(void)
 {
     ALSA_Device *dev;
     ALSA_Device *next;
@@ -944,7 +944,10 @@ static void ALSA_Deinitialize(void)
         SDL_free(dev);
     }
     hotplug_devices = NULL;
+}
 
+static void ALSA_Deinitialize(void)
+{
     UnloadALSALibrary();
 }
 
@@ -960,6 +963,7 @@ static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl)
     impl->GetDeviceBuf = ALSA_GetDeviceBuf;
     impl->PlayDevice = ALSA_PlayDevice;
     impl->CloseDevice = ALSA_CloseDevice;
+    impl->DeinitializeStart = ALSA_DeinitializeStart;
     impl->Deinitialize = ALSA_Deinitialize;
     impl->WaitCaptureDevice = ALSA_WaitDevice;
     impl->CaptureFromDevice = ALSA_CaptureFromDevice;
diff --git a/src/audio/android/SDL_androidaudio.c b/src/audio/android/SDL_androidaudio.c
index c0dade8acb6e..9d49ce9b8cb7 100644
--- a/src/audio/android/SDL_androidaudio.c
+++ b/src/audio/android/SDL_androidaudio.c
@@ -172,7 +172,7 @@ static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl)
     // !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+.
     impl->ThreadInit = Android_AudioThreadInit;
     impl->DetectDevices = Android_StartAudioHotplug;
-    impl->Deinitialize = Android_StopAudioHotplug;
+    impl->DeinitializeStart = Android_StopAudioHotplug;
     impl->OpenDevice = ANDROIDAUDIO_OpenDevice;
     impl->PlayDevice = ANDROIDAUDIO_PlayDevice;
     impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf;
diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index bbe37161bf13..998bda154f03 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -940,7 +940,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device)
     return (device->hidden->thread != NULL) ? 0 : -1;
 }
 
-static void COREAUDIO_Deinitialize(void)
+static void COREAUDIO_DeinitializeStart(void)
 {
 #ifdef MACOSX_COREAUDIO
     AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
@@ -958,7 +958,7 @@ static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl)
     impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice;
     impl->FlushCapture = COREAUDIO_FlushCapture;
     impl->CloseDevice = COREAUDIO_CloseDevice;
-    impl->Deinitialize = COREAUDIO_Deinitialize;
+    impl->DeinitializeStart = COREAUDIO_DeinitializeStart;
 
 #ifdef MACOSX_COREAUDIO
     impl->DetectDevices = COREAUDIO_DetectDevices;
diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c
index ad0d9db375bb..2a6bb47e7ceb 100644
--- a/src/audio/directsound/SDL_directsound.c
+++ b/src/audio/directsound/SDL_directsound.c
@@ -623,15 +623,21 @@ static int DSOUND_OpenDevice(SDL_AudioDevice *device)
     return 0; // good to go.
 }
 
-static void DSOUND_Deinitialize(void)
+static void DSOUND_DeinitializeStart(void)
 {
 #ifdef HAVE_MMDEVICEAPI_H
     if (SupportsIMMDevice) {
         SDL_IMMDevice_Quit();
-        SupportsIMMDevice = SDL_FALSE;
     }
 #endif
+}
+
+static void DSOUND_Deinitialize(void)
+{
     DSOUND_Unload();
+#ifdef HAVE_MMDEVICEAPI_H
+    SupportsIMMDevice = SDL_FALSE;
+#endif
 }
 
 static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl)
@@ -654,6 +660,7 @@ static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl)
     impl->FlushCapture = DSOUND_FlushCapture;
     impl->CloseDevice = DSOUND_CloseDevice;
     impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
+    impl->DeinitializeStart = DSOUND_DeinitializeStart;
     impl->Deinitialize = DSOUND_Deinitialize;
 
     impl->HasCaptureSupport = SDL_TRUE;
diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
index 0dda0d8caec9..b5cbbe74c1af 100644
--- a/src/audio/pipewire/SDL_pipewire.c
+++ b/src/audio/pipewire/SDL_pipewire.c
@@ -1234,10 +1234,16 @@ static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device)
     SDL_AudioThreadFinalize(device);
 }
 
-static void PIPEWIRE_Deinitialize(void)
+static void PIPEWIRE_DeinitializeStart(void)
 {
     if (pipewire_initialized) {
         hotplug_loop_destroy();
+    }
+}
+
+static void PIPEWIRE_Deinitialize(void)
+{
+    if (pipewire_initialized) {
         deinit_pipewire_library();
         pipewire_initialized = SDL_FALSE;
     }
@@ -1261,6 +1267,7 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl)
     /* Set the function pointers */
     impl->DetectDevices = PIPEWIRE_DetectDevices;
     impl->OpenDevice = PIPEWIRE_OpenDevice;
+    impl->DeinitializeStart = PIPEWIRE_DeinitializeStart;
     impl->Deinitialize = PIPEWIRE_Deinitialize;
     impl->PlayDevice = PIPEWIRE_PlayDevice;
     impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf;
diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c
index 1ab099baa020..0703fc7401e6 100644
--- a/src/audio/pulseaudio/SDL_pulseaudio.c
+++ b/src/audio/pulseaudio/SDL_pulseaudio.c
@@ -968,7 +968,7 @@ static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_Audio
     SDL_DestroySemaphore(ready_sem);
 }
 
-static void PULSEAUDIO_Deinitialize(void)
+static void PULSEAUDIO_DeinitializeStart(void)
 {
     if (pulseaudio_hotplug_thread) {
         PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
@@ -978,7 +978,10 @@ static void PULSEAUDIO_Deinitialize(void)
         SDL_WaitThread(pulseaudio_hotplug_thread, NULL);
         pulseaudio_hotplug_thread = NULL;
     }
+}
 
+static void PULSEAUDIO_Deinitialize(void)
+{
     DisconnectFromPulseServer();
 
     SDL_free(default_sink_path);
@@ -1010,6 +1013,7 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl)
     impl->WaitDevice = PULSEAUDIO_WaitDevice;
     impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
     impl->CloseDevice = PULSEAUDIO_CloseDevice;
+    impl->DeinitializeStart = PULSEAUDIO_DeinitializeStart;
     impl->Deinitialize = PULSEAUDIO_Deinitialize;
     impl->WaitCaptureDevice = PULSEAUDIO_WaitCaptureDevice;
     impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice;
diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c
index 42aa8ef57716..86f504387bcc 100644
--- a/src/audio/wasapi/SDL_wasapi.c
+++ b/src/audio/wasapi/SDL_wasapi.c
@@ -714,6 +714,18 @@ static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
     WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
 }
 
+static int mgmtthrtask_DeinitializeStart(void *userdata)
+{
+    WASAPI_PlatformDeinitializeStart(void);
+    return 0;
+}
+
+static void WASAPI_DeinitializeStart(void)
+{
+    int rc;
+    WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
+}
+
 static void WASAPI_Deinitialize(void)
 {
     DeinitManagementThread();
@@ -736,6 +748,7 @@ static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl)
     impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
     impl->FlushCapture = WASAPI_FlushCapture;
     impl->CloseDevice = WASAPI_CloseDevice;
+    impl->DeinitializeStart = WASAPI_DeinitializeStart;
     impl->Deinitialize = WASAPI_Deinitialize;
     impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
 
diff --git a/src/audio/wasapi/SDL_wasapi.h b/src/audio/wasapi/SDL_wasapi.h
index ba2870801b2b..00f80a08a302 100644
--- a/src/audio/wasapi/SDL_wasapi.h
+++ b/src/audio/wasapi/SDL_wasapi.h
@@ -58,6 +58,7 @@ int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, in
 // UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD.
 int WASAPI_PlatformInit(void);
 void WASAPI_PlatformDeinit(void);
+void WASAPI_PlatformDeinitializeStart(void);
 void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
 int WASAPI_ActivateDevice(SDL_AudioDevice *device);
 void WASAPI_PlatformThreadInit(SDL_AudioDevice *device);  // this happens on the audio device thread, not the management thread.
diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c
index 719ac94a3bec..b43e07941ca1 100644
--- a/src/audio/wasapi/SDL_wasapi_win32.c
+++ b/src/audio/wasapi/SDL_wasapi_win32.c
@@ -44,15 +44,21 @@ typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
 static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
 static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
 
+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 } };
 
 int WASAPI_PlatformInit(void)
 {
-    if (SDL_IMMDevice_Init() < 0) {   // this will call WIN_CoInitialize for us!
-        return -1; /* This is set by SDL_IMMDevice_Init */
+    if (FAILED(WIN_CoInitialize())) {
+        return SDL_SetError("CoInitialize() failed");
+    } else if (SDL_IMMDevice_Init() < 0) {
+        return -1; // Error string is set by SDL_IMMDevice_Init
     }
 
+    immdevice_initialized = SDL_TRUE;
+
     libavrt = LoadLibrary(TEXT("avrt.dll")); /* this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! */
     if (libavrt) {
         pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
@@ -62,6 +68,14 @@ int WASAPI_PlatformInit(void)
     return 0;
 }
 
+static void StopWasapiHotplug(void)
+{
+    if (immdevice_initialized) {
+        SDL_IMMDevice_Quit();
+        immdevice_initialized = SDL_FALSE;
+    }
+}
+
 void WASAPI_PlatformDeinit(void)
 {
     if (libavrt) {
@@ -72,7 +86,14 @@ void WASAPI_PlatformDeinit(void)
     pAvSetMmThreadCharacteristicsW = NULL;
     pAvRevertMmThreadCharacteristics = NULL;
 
-    SDL_IMMDevice_Quit();  // This will call WIN_CoUninitialize for us!
+    StopWasapiHotplug();
+
+    WIN_CoUninitialize();
+}
+
+void WASAPI_PlatformDeinitializeStart(void)
+{
+    StopWasapiHotplug();
 }
 
 void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp
index a4030f39c2dc..c3701a8a5f14 100644
--- a/src/audio/wasapi/SDL_wasapi_winrt.cpp
+++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp
@@ -220,7 +220,7 @@ int WASAPI_PlatformInit(void)
     return 0;
 }
 
-void WASAPI_PlatformDeinit(void)
+static void StopWasapiHotplug(void)
 {
     delete playback_device_event_handler;
     playback_device_event_handler = nullptr;
@@ -228,6 +228,17 @@ void WASAPI_PlatformDeinit(void)
     capture_device_event_handler = nullptr;
 }
 
+void WASAPI_PlatformDeinit(void)
+{
+    StopWasapiHotplug();
+}
+
+void WASAPI_PlatformDeinitializeStart(void)
+{
+    StopWasapiHotplug();
+}
+
+
 void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
 {
     Platform::String ^ defdevid;