SDL: wasapi: Reworked for new SDL3 audio API, other win32 fixes.

https://github.com/libsdl-org/SDL/commit/c58d95c3431c028d59e2af3d0d4b6e045e3c4f22

From c58d95c3431c028d59e2af3d0d4b6e045e3c4f22 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Fri, 14 Jul 2023 19:55:30 -0400
Subject: [PATCH] wasapi: Reworked for new SDL3 audio API, other win32 fixes.

The WinRT code has _also_ be updated, but it has not been
tested or compiled, yet.
---
 src/audio/SDL_audio.c                 |  44 +-
 src/audio/SDL_audiocvt.c              |   2 +-
 src/audio/SDL_sysaudio.h              |   6 +
 src/audio/wasapi/SDL_wasapi.c         | 764 +++++++++++++++-----------
 src/audio/wasapi/SDL_wasapi.h         |  25 +-
 src/audio/wasapi/SDL_wasapi_win32.c   |  58 +-
 src/audio/wasapi/SDL_wasapi_winrt.cpp | 262 ++++-----
 src/core/windows/SDL_immdevice.c      |   7 +-
 src/core/windows/SDL_immdevice.h      |   2 +-
 test/loopwave.c                       |   2 +-
 10 files changed, 635 insertions(+), 537 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 5224cb067ef1..9d770b817097 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1082,7 +1082,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec)
     return 0;
 }
 
-// this expects the device lock to be held.
+// this expects the device lock to be held.  !!! FIXME: no it doesn't...?
 static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
 {
     SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0)));
@@ -1688,3 +1688,45 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
         SDL_AudioDeviceDisconnected(current_default_device);  // Call again, now that we're not the default; this will remove from device list, send removal events, and destroy the SDL_AudioDevice.
     }
 }
+
+int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
+{
+    SDL_bool kill_device = SDL_FALSE;
+
+    const int orig_buffer_size = device->buffer_size;
+    const SDL_bool iscapture = device->iscapture;
+
+    if ((device->spec.format != newspec->format) || (device->spec.channels != newspec->channels) || (device->spec.freq != newspec->freq)) {
+        SDL_memcpy(&device->spec, newspec, sizeof (newspec));
+        for (SDL_LogicalAudioDevice *logdev = device->logical_devices; !kill_device && (logdev != NULL); logdev = logdev->next) {
+            for (SDL_AudioStream *stream = logdev->bound_streams; !kill_device && (stream != NULL); stream = stream->next_binding) {
+                if (SDL_SetAudioStreamFormat(stream, iscapture ? &device->spec : NULL, iscapture ? NULL : &device->spec) == -1) {
+                    kill_device = SDL_TRUE;
+                }
+            }
+        }
+    }
+
+    if (!kill_device) {
+        device->sample_frames = new_sample_frames;
+        SDL_UpdatedAudioDeviceFormat(device);
+        if (device->work_buffer && (device->buffer_size > orig_buffer_size)) {
+            SDL_aligned_free(device->work_buffer);
+            device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->buffer_size);
+            if (!device->work_buffer) {
+                kill_device = SDL_TRUE;
+            }
+        }
+    }
+
+    return kill_device ? -1 : 0;
+}
+
+int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
+{
+    SDL_LockMutex(device->lock);
+    const int retval = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames);
+    SDL_UnlockMutex(device->lock);
+    return retval;
+}
+
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index effc3af25504..d7e2c54f412e 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -879,7 +879,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
             // if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame.
             //  however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up.
             if (stream->flushed) {
-                SDL_assert(((input_frames * src_sample_frame_size) + future_buffer_filled_frames) <= stream->future_buffer_allocation);
+                SDL_assert(((size_t) ((input_frames * src_sample_frame_size) + future_buffer_filled_frames)) <= stream->future_buffer_allocation);
                 // leave input_frames alone; this will just shuffle what's available from the future buffer and pad with silence as appropriate, below.
             } else {
                 return 0;
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 2a034b5c85ec..7e170330a234 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -85,6 +85,12 @@ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
 // Backends should call this if the system default device changes.
 extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
 
+// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
+extern int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
+
+// Same as above, but assume the device is already locked.
+extern int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
+
 // Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
 extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
 
diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c
index d91d4239997a..2f2ad8ff3a1b 100644
--- a/src/audio/wasapi/SDL_wasapi.c
+++ b/src/audio/wasapi/SDL_wasapi.c
@@ -24,6 +24,7 @@
 
 #include "../../core/windows/SDL_windows.h"
 #include "../../core/windows/SDL_immdevice.h"
+#include "../../thread/SDL_systhread.h"
 #include "../SDL_audio_c.h"
 #include "../SDL_sysaudio.h"
 
@@ -32,7 +33,7 @@
 
 #include "SDL_wasapi.h"
 
-/* These constants aren't available in older SDKs */
+// These constants aren't available in older SDKs
 #ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
 #define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
 #endif
@@ -43,140 +44,367 @@
 #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
 #endif
 
-/* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */
+// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
 static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
 static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
 
 
-static void WASAPI_DetectDevices(void)
+// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
+//  so we proxy various stuff to a single background thread to manage.
+
+typedef struct ManagementThreadPendingTask
+{
+    ManagementThreadTask fn;
+    void *userdata;
+    int result;
+    SDL_Semaphore *task_complete_sem;
+    char *errorstr;
+    struct ManagementThreadPendingTask *next;
+} ManagementThreadPendingTask;
+
+static SDL_Thread *ManagementThread = NULL;
+static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
+static SDL_Mutex *ManagementThreadLock = NULL;
+static SDL_Condition *ManagementThreadCondition = NULL;
+static SDL_AtomicInt ManagementThreadShutdown;
+
+static void ManagementThreadMainloop(void)
 {
-    WASAPI_EnumerateEndpoints();
+    SDL_LockMutex(ManagementThreadLock);
+    ManagementThreadPendingTask *task;
+    while (((task = SDL_AtomicGetPtr(&ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) {
+        if (!task) {
+            SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
+        } else {
+            SDL_AtomicSetPtr(&ManagementThreadPendingTasks, task->next); // take task off the pending list.
+            SDL_UnlockMutex(ManagementThreadLock);                       // let other things add to the list while we chew on this task.
+            task->result = task->fn(task->userdata);                     // run this task.
+            if (task->task_complete_sem) {                               // something waiting on result?
+                task->errorstr = SDL_strdup(SDL_GetError());
+                SDL_PostSemaphore(task->task_complete_sem);
+            } else { // nothing waiting, we're done, free it.
+                SDL_free(task);
+            }
+            SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
+        }
+    }
+    SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
 }
 
-static SDL_INLINE SDL_bool WasapiFailed(SDL_AudioDevice *_this, const HRESULT err)
+int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result)
 {
-    if (err == S_OK) {
-        return SDL_FALSE;
+    // We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
+    if ((wait_on_result != NULL) && (SDL_ThreadID() == SDL_GetThreadID(ManagementThread))) {
+        *wait_on_result = task(userdata);
+        return 0;  // completed!
     }
 
-    if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
-        _this->hidden->device_lost = SDL_TRUE;
-    } else if (SDL_AtomicGet(&_this->enabled)) {
-        IAudioClient_Stop(_this->hidden->client);
-        SDL_OpenedAudioDeviceDisconnected(_this);
-        SDL_assert(!SDL_AtomicGet(&_this->enabled));
+    if (SDL_AtomicGet(&ManagementThreadShutdown)) {
+        return SDL_SetError("Can't add task, we're shutting down");
     }
 
-    return SDL_TRUE;
-}
+    ManagementThreadPendingTask *pending = SDL_calloc(1, sizeof(ManagementThreadPendingTask));
+    if (!pending) {
+        return SDL_OutOfMemory();
+    }
 
-static int UpdateAudioStream(SDL_AudioDevice *_this, const SDL_AudioSpec *oldspec)
-{
-    /* Since WASAPI requires us to handle all audio conversion, and our
-       device format might have changed, we might have to add/remove/change
-       the audio stream that the higher level uses to convert data, so
-       SDL keeps firing the callback as if nothing happened here. */
-
-    if ((_this->callbackspec.channels == _this->spec.channels) &&
-        (_this->callbackspec.format == _this->spec.format) &&
-        (_this->callbackspec.freq == _this->spec.freq) &&
-        (_this->callbackspec.samples == _this->spec.samples)) {
-        /* no need to buffer/convert in an AudioStream! */
-        SDL_DestroyAudioStream(_this->stream);
-        _this->stream = NULL;
-    } else if ((oldspec->channels == _this->spec.channels) &&
-               (oldspec->format == _this->spec.format) &&
-               (oldspec->freq == _this->spec.freq)) {
-        /* The existing audio stream is okay to keep using. */
-    } else {
-        /* replace the audiostream for new format */
-        SDL_DestroyAudioStream(_this->stream);
-        if (_this->iscapture) {
-            _this->stream = SDL_CreateAudioStream(_this->spec.format,
-                                              _this->spec.channels, _this->spec.freq,
-                                              _this->callbackspec.format,
-                                              _this->callbackspec.channels,
-                                              _this->callbackspec.freq);
-        } else {
-            _this->stream = SDL_CreateAudioStream(_this->callbackspec.format,
-                                              _this->callbackspec.channels,
-                                              _this->callbackspec.freq, _this->spec.format,
-                                              _this->spec.channels, _this->spec.freq);
-        }
+    pending->fn = task;
+    pending->userdata = userdata;
 
-        if (!_this->stream) {
-            return -1; /* SDL_CreateAudioStream should have called SDL_SetError. */
+    if (wait_on_result) {
+        pending->task_complete_sem = SDL_CreateSemaphore(0);
+        if (!pending->task_complete_sem) {
+            SDL_free(pending);
+            return -1;
         }
     }
 
-    /* make sure our scratch buffer can cover the new device spec. */
-    if (_this->spec.size > _this->work_buffer_len) {
-        Uint8 *ptr = (Uint8 *)SDL_realloc(_this->work_buffer, _this->spec.size);
-        if (ptr == NULL) {
-            return SDL_OutOfMemory();
+    pending->next = NULL;
+
+    SDL_LockMutex(ManagementThreadLock);
+
+    // add to end of task list.
+    ManagementThreadPendingTask *prev = NULL;
+    for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr(&ManagementThreadPendingTasks); i != NULL; i = i->next) {
+        prev = i;
+    }
+
+    if (prev != NULL) {
+        prev->next = pending;
+    } else {
+        SDL_AtomicSetPtr(&ManagementThreadPendingTasks, pending);
+    }
+
+    // task is added to the end of the pending list, let management thread rip!
+    SDL_SignalCondition(ManagementThreadCondition);
+    SDL_UnlockMutex(ManagementThreadLock);
+
+    if (wait_on_result) {
+        SDL_WaitSemaphore(pending->task_complete_sem);
+        SDL_DestroySemaphore(pending->task_complete_sem);
+        *wait_on_result = pending->result;
+        if (pending->errorstr) {
+            SDL_SetError("%s", pending->errorstr);
+            SDL_free(pending->errorstr);
         }
-        _this->work_buffer = ptr;
-        _this->work_buffer_len = _this->spec.size;
+        SDL_free(pending);
+    }
+
+    return 0; // successfully added (and possibly executed)!
+}
+
+static int ManagementThreadPrepare(void)
+{
+    if (WASAPI_PlatformInit() == -1) {
+        return -1;
+    }
+
+    ManagementThreadLock = SDL_CreateMutex();
+    if (!ManagementThreadLock) {
+        WASAPI_PlatformDeinit();
+        return -1;
+    }
+
+    ManagementThreadCondition = SDL_CreateCondition();
+    if (!ManagementThreadCondition) {
+        SDL_DestroyMutex(ManagementThreadLock);
+        ManagementThreadLock = NULL;
+        WASAPI_PlatformDeinit();
+        return -1;
     }
 
     return 0;
 }
 
-static void ReleaseWasapiDevice(SDL_AudioDevice *_this);
+typedef struct
+{
+    char *errorstr;
+    SDL_Semaphore *ready_sem;
+} ManagementThreadEntryData;
+
+static int ManagementThreadEntry(void *userdata)
+{
+    ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
+
+    if (ManagementThreadPrepare() < 0) {
+        data->errorstr = SDL_strdup(SDL_GetError());
+        SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
+        return 0;
+    }
+
+    SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
+    ManagementThreadMainloop();
+
+    WASAPI_PlatformDeinit();
+    return 0;
+}
 
-static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *_this)
+static int InitManagementThread(void)
 {
-    ReleaseWasapiDevice(_this); /* dump the lost device's handles. */
+    ManagementThreadEntryData mgmtdata;
+    SDL_zero(mgmtdata);
+    mgmtdata.ready_sem = SDL_CreateSemaphore(0);
+    if (!mgmtdata.ready_sem) {
+        return -1;
+    }
+
+    SDL_AtomicSetPtr(&ManagementThreadPendingTasks, NULL);
+    SDL_AtomicSet(&ManagementThreadShutdown, 0);
+    ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
+    if (!ManagementThread) {
+        return -1;
+    }
+
+    SDL_WaitSemaphore(mgmtdata.ready_sem);
+    SDL_DestroySemaphore(mgmtdata.ready_sem);
 
-    if (_this->hidden->default_device_generation) {
-        _this->hidden->default_device_generation = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration);
+    if (mgmtdata.errorstr) {
+        SDL_WaitThread(ManagementThread, NULL);
+        ManagementThread = NULL;
+        SDL_SetError("%s", mgmtdata.errorstr);
+        SDL_free(mgmtdata.errorstr);
+        return -1;
     }
 
-    /* this can fail for lots of reasons, but the most likely is we had a
-       non-default device that was disconnected, so we can't recover. Default
-       devices try to reinitialize whatever the new default is, so it's more
-       likely to carry on here, but this handles a non-default device that
-       simply had its format changed in the Windows Control Panel. */
-    if (WASAPI_ActivateDevice(_this, SDL_TRUE) == -1) {
-        SDL_OpenedAudioDeviceDisconnected(_this);
+    return 0;
+}
+
+static void DeinitManagementThread(void)
+{
+    if (ManagementThread) {
+        SDL_AtomicSet(&ManagementThreadShutdown, 1);
+        SDL_LockMutex(ManagementThreadLock);
+        SDL_SignalCondition(ManagementThreadCondition);
+        SDL_UnlockMutex(ManagementThreadLock);
+        SDL_WaitThread(ManagementThread, NULL);
+        ManagementThread = NULL;
+    }
+
+    SDL_assert(SDL_AtomicGetPtr(&ManagementThreadPendingTasks) == NULL);
+
+    SDL_DestroyCondition(ManagementThreadCondition);
+    SDL_DestroyMutex(ManagementThreadLock);
+    ManagementThreadCondition = NULL;
+    ManagementThreadLock = NULL;
+    SDL_AtomicSet(&ManagementThreadShutdown, 0);
+}
+
+typedef struct
+{
+    SDL_AudioDevice **default_output;
+    SDL_AudioDevice **default_capture;
+} mgmtthrtask_DetectDevicesData;
+
+static int mgmtthrtask_DetectDevices(void *userdata)
+{
+    mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
+    WASAPI_EnumerateEndpoints(data->default_output, data->default_capture);
+    return 0;
+}
+
+static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
+{
+    int rc;
+    mgmtthrtask_DetectDevicesData data = { default_output, default_capture };
+    WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
+}
+
+static int mgmtthrtask_DisconnectDevice(void *userdata)
+{
+    SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata);
+    return 0;
+}
+
+void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
+{
+    // this runs async, so it can hold the device lock from the management thread.
+    WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
+}
+
+static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
+{
+    if (err == S_OK) {
         return SDL_FALSE;
     }
 
-    _this->hidden->device_lost = SDL_FALSE;
+    if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
+        device->hidden->device_lost = SDL_TRUE;
+    } else {
+        IAudioClient_Stop(device->hidden->client);
+        WASAPI_DisconnectDevice(device);
+    }
+
+    return SDL_TRUE;
+}
+
+static int mgmtthrtask_ReleaseCaptureClient(void *userdata)
+{
+    IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
+    return 0;
+}
 
-    return SDL_TRUE; /* okay, carry on with new device details! */
+static int mgmtthrtask_ReleaseRenderClient(void *userdata)
+{
+    IAudioRenderClient_Release((IAudioRenderClient *)userdata);
+    return 0;
 }
 
-static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *_this)
+static int mgmtthrtask_ResetWasapiDevice(void *userdata)
 {
-    const int generation = _this->hidden->default_device_generation;
-    SDL_bool lost = _this->hidden->device_lost;
+    SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
 
-    if (!SDL_AtomicGet(&_this->enabled)) {
-        return SDL_FALSE; /* already failed. */
+    if (!device || !device->hidden) {
+        return 0;
     }
 
-    if (!_this->hidden->client) {
-        return SDL_TRUE; /* still waiting for activation. */
+    if (device->hidden->client) {
+        IAudioClient_Stop(device->hidden->client);
+        IAudioClient_Release(device->hidden->client);
+        device->hidden->client = NULL;
     }
 
-    if (!lost && (generation > 0)) { /* is a default device? */
-        const int newgen = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration);
-        if (generation != newgen) { /* the desired default device was changed, jump over to it. */
-            lost = SDL_TRUE;
-        }
+    if (device->hidden->render) {
+        // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so
+        //  proxy this to the management thread to be released later.
+        WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, device->hidden->render, NULL);
+        device->hidden->render = NULL;
     }
 
-    return lost ? RecoverWasapiDevice(_this) : SDL_TRUE;
+    if (device->hidden->capture) {
+        // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so
+        //  proxy this to the management thread to be released later.
+        WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, device->hidden->capture, NULL);
+        device->hidden->capture = NULL;
+    }
+
+    if (device->hidden->waveformat) {
+        CoTaskMemFree(device->hidden->waveformat);
+        device->hidden->waveformat = NULL;
+    }
+
+    if (device->hidden->activation_handler) {
+        WASAPI_PlatformDeleteActivationHandler(device->hidden->activation_handler);
+        device->hidden->activation_handler = NULL;
+    }
+
+    if (device->hidden->event) {
+        CloseHandle(device->hidden->event);
+        device->hidden->event = NULL;
+    }
+
+    return 0;
+}
+
+static void ResetWasapiDevice(SDL_AudioDevice *device)
+{
+    int rc;
+    WASAPI_ProxyToManagementThread(mgmtthrtask_ResetWasapiDevice, device, &rc);
 }
 
-static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *_this)
+static int mgmtthrtask_ActivateDevice(void *userdata)
 {
-    /* get an endpoint buffer from WASAPI. */
+    return WASAPI_ActivateDevice((SDL_AudioDevice *)userdata);
+}
+
+static int ActivateWasapiDevice(SDL_AudioDevice *device)
+{
+    int rc;
+    return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0;
+}
+
+static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device)
+{
+    ResetWasapiDevice(device); // dump the lost device's handles.
+
+    // This handles a non-default device that simply had its format changed in the Windows Control Panel.
+    if (ActivateWasapiDevice(device) == -1) {
+        WASAPI_DisconnectDevice(device);
+        return SDL_FALSE;
+    }
+
+    device->hidden->device_lost = SDL_FALSE;
+
+    return SDL_TRUE; // okay, carry on with new device details!
+}
+
+static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device)
+{
+    if (SDL_AtomicGet(&device->shutdown)) {
+        return SDL_FALSE; // already failed.
+    } else if (!device->hidden->client) {
+        return SDL_TRUE; // still waiting for activation.
+    }
+
+    return device->hidden->device_lost ? RecoverWasapiDevice(device) : SDL_TRUE;
+}
+
+static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
+{
+    // get an endpoint buffer from WASAPI.
     BYTE *buffer = NULL;
 
-    while (RecoverWasapiIfLost(_this) && _this->hidden->render) {
-        if (!WasapiFailed(_this, IAudioRenderClient_GetBuffer(_this->hidden->render, _this->spec.samples, &buffer))) {
+    while (RecoverWasapiIfLost(device) && device->hidden->render) {
+        if (!WasapiFailed(device, IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer))) {
             return (Uint8 *)buffer;
         }
         SDL_assert(buffer == NULL);
@@ -185,197 +413,108 @@ static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *_this)
     return (Uint8 *)buffer;
 }
 
-static void WASAPI_PlayDevice(SDL_AudioDevice *_this)
+static void WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
-    if (_this->hidden->render != NULL) { /* definitely activated? */
-        /* WasapiFailed() will mark the device for reacquisition or removal elsewhere. */
-        WasapiFailed(_this, IAudioRenderClient_ReleaseBuffer(_this->hidden->render, _this->spec.samples, 0));
+    if (device->hidden->render != NULL) { // definitely activated?
+        // WasapiFailed() will mark the device for reacquisition or removal elsewhere.
+        WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
     }
 }
 
-static void WASAPI_WaitDevice(SDL_AudioDevice *_this)
+static void WASAPI_WaitDevice(SDL_AudioDevice *device)
 {
-    while (RecoverWasapiIfLost(_this) && _this->hidden->client && _this->hidden->event) {
-        DWORD waitResult = WaitForSingleObjectEx(_this->hidden->event, 200, FALSE);
+    while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
+        DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
         if (waitResult == WAIT_OBJECT_0) {
-            const UINT32 maxpadding = _this->spec.samples;
+            const UINT32 maxpadding = device->sample_frames;
             UINT32 padding = 0;
-            if (!WasapiFailed(_this, IAudioClient_GetCurrentPadding(_this->hidden->client, &padding))) {
-                /*SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
-                if (_this->iscapture) {
-                    if (padding > 0) {
-                        break;
-                    }
-                } else {
-                    if (padding <= maxpadding) {
-                        break;
-                    }
+            if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
+                //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
+                if (device->iscapture && (padding > 0)) {
+                    break;
+                } else if (!device->iscapture && (padding <= maxpadding)) {
+                    break;
                 }
             }
         } else if (waitResult != WAIT_TIMEOUT) {
-            /*SDL_Log("WASAPI FAILED EVENT!");*/
-            IAudioClient_Stop(_this->hidden->client);
-            SDL_OpenedAudioDeviceDisconnected(_this);
+            //SDL_Log("WASAPI FAILED EVENT!");*/
+            IAudioClient_Stop(device->hidden->client);
+            WASAPI_DisconnectDevice(device);
         }
     }
 }
 
-static int WASAPI_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen)
+static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
 {
-    SDL_AudioStream *stream = _this->hidden->capturestream;
-    const int avail = SDL_GetAudioStreamAvailable(stream);
-    if (avail > 0) {
-        const int cpy = SDL_min(buflen, avail);
-        SDL_GetAudioStreamData(stream, buffer, cpy);
-        return cpy;
-    }
-
-    while (RecoverWasapiIfLost(_this)) {
-        HRESULT ret;
-        BYTE *ptr = NULL;
-        UINT32 frames = 0;
-        DWORD flags = 0;
+    BYTE *ptr = NULL;
+    UINT32 frames = 0;
+    DWORD flags = 0;
 
-        /* uhoh, client isn't activated yet, just return silence. */
-        if (!_this->hidden->capture) {
-            /* Delay so we run at about the speed that audio would be arriving. */
-            SDL_Delay(((_this->spec.samples * 1000) / _this->spec.freq));
-            SDL_memset(buffer, _this->spec.silence, buflen);
-            return buflen;
+    while (RecoverWasapiIfLost(device) && device->hidden->capture) {
+        HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
+        if (ret == AUDCLNT_S_BUFFER_EMPTY) {
+            return 0;  // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL
         }
 
-        ret = IAudioCaptureClient_GetBuffer(_this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
-        if (ret != AUDCLNT_S_BUFFER_EMPTY) {
-            WasapiFailed(_this, ret); /* mark device lost/failed if necessary. */
-        }
+        WasapiFailed(device, ret); // mark device lost/failed if necessary.
 
-        if ((ret == AUDCLNT_S_BUFFER_EMPTY) || !frames) {
-            WASAPI_WaitDevice(_this);
-        } else if (ret == S_OK) {
-            const int total = ((int)frames) * _this->hidden->framesize;
+        if (ret == S_OK) {
+            const int total = ((int)frames) * device->hidden->framesize;
             const int cpy = SDL_min(buflen, total);
             const int leftover = total - cpy;
             const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
 
+            SDL_assert(leftover == 0);  // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
+
             if (silent) {
-                SDL_memset(buffer, _this->spec.silence, cpy);
+                SDL_memset(buffer, device->silence_value, cpy);
             } else {
                 SDL_memcpy(buffer, ptr, cpy);
             }
 
-            if (leftover > 0) {
-                ptr += cpy;
-                if (silent) {
-                    SDL_memset(ptr, _this->spec.silence, leftover); /* I guess this is safe? */
-                }
-
-                if (SDL_PutAudioStreamData(stream, ptr, leftover) == -1) {
-                    return -1; /* uhoh, out of memory, etc. Kill device.  :( */
-                }
-            }
-
-            ret = IAudioCaptureClient_ReleaseBuffer(_this->hidden->capture, frames);
-            WasapiFailed(_this, ret); /* mark device lost/failed if necessary. */
+            ret = IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames);
+            WasapiFailed(device, ret); // mark device lost/failed if necessary.
 
             return cpy;
         }
     }
 
-    return -1; /* unrecoverable error. */
+    return -1; // unrecoverable error.
 }
 
-static void WASAPI_FlushCapture(SDL_AudioDevice *_this)
+static void WASAPI_FlushCapture(SDL_AudioDevice *device)
 {
     BYTE *ptr = NULL;
     UINT32 frames = 0;
     DWORD flags = 0;
 
-    if (!_this->hidden->capture) {
-        return; /* not activated yet? */
-    }
-
-    /* just read until we stop getting packets, throwing them away. */
-    while (SDL_TRUE) {
-        const HRESULT ret = IAudioCaptureClient_GetBuffer(_this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
+    // just read until we stop getting packets, throwing them away.
+    while (!SDL_AtomicGet(&device->shutdown) && device->hidden->capture) {
+        const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
         if (ret == AUDCLNT_S_BUFFER_EMPTY) {
-            break; /* no more buffered data; we're done. */
-        } else if (WasapiFailed(_this, ret)) {
-            break; /* failed for some other reason, abort. */
-        } else if (WasapiFailed(_this, IAudioCaptureClient_ReleaseBuffer(_this->hidden->capture, frames))) {
-            break; /* something broke. */
+            break; // no more buffered data; we're done.
+        } else if (WasapiFailed(device, ret)) {
+            break; // failed for some other reason, abort.
+        } else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
+            break; // something broke.
         }
     }
-    SDL_ClearAudioStream(_this->hidden->capturestream);
 }
 

(Patch may be truncated, please check the link at the top of this post.)