Procházet zdrojové kódy

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.
Ryan C. Gordon před 1 rokem
rodič
revize
c58d95c343

+ 43 - 1
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;
+}
+

+ 1 - 1
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;

+ 6 - 0
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);
 

+ 447 - 317
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);
 }
 
-static void ReleaseWasapiDevice(SDL_AudioDevice *_this)
+static void WASAPI_CloseDevice(SDL_AudioDevice *device)
 {
-    if (_this->hidden->client) {
-        IAudioClient_Stop(_this->hidden->client);
-        IAudioClient_Release(_this->hidden->client);
-        _this->hidden->client = NULL;
-    }
-
-    if (_this->hidden->render) {
-        IAudioRenderClient_Release(_this->hidden->render);
-        _this->hidden->render = NULL;
+    if (device->hidden) {
+        ResetWasapiDevice(device);
+        SDL_free(device->hidden->devid);
+        SDL_free(device->hidden);
+        device->hidden = NULL;
     }
-
-    if (_this->hidden->capture) {
-        IAudioCaptureClient_Release(_this->hidden->capture);
-        _this->hidden->capture = NULL;
-    }
-
-    if (_this->hidden->waveformat) {
-        CoTaskMemFree(_this->hidden->waveformat);
-        _this->hidden->waveformat = NULL;
-    }
-
-    if (_this->hidden->capturestream) {
-        SDL_DestroyAudioStream(_this->hidden->capturestream);
-        _this->hidden->capturestream = NULL;
-    }
-
-    if (_this->hidden->activation_handler) {
-        WASAPI_PlatformDeleteActivationHandler(_this->hidden->activation_handler);
-        _this->hidden->activation_handler = NULL;
-    }
-
-    if (_this->hidden->event) {
-        CloseHandle(_this->hidden->event);
-        _this->hidden->event = NULL;
-    }
-}
-
-static void WASAPI_CloseDevice(SDL_AudioDevice *_this)
-{
-    WASAPI_UnrefDevice(_this);
-}
-
-void WASAPI_RefDevice(SDL_AudioDevice *_this)
-{
-    SDL_AtomicIncRef(&_this->hidden->refcount);
 }
 
-void WASAPI_UnrefDevice(SDL_AudioDevice *_this)
+static int mgmtthrtask_PrepDevice(void *userdata)
 {
-    if (!SDL_AtomicDecRef(&_this->hidden->refcount)) {
-        return;
-    }
-
-    /* actual closing happens here. */
+    SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
 
-    /* don't touch _this->hidden->task in here; it has to be reverted from
-       our callback thread. We do that in WASAPI_ThreadDeinit().
-       (likewise for _this->hidden->coinitialized). */
-    ReleaseWasapiDevice(_this);
-    SDL_free(_this->hidden->devid);
-    SDL_free(_this->hidden);
-}
-
-/* This is called once a device is activated, possibly asynchronously. */
-int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream)
-{
     /* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
        !!!  it will write into the kernel's audio buffer directly instead of
        !!!  shared memory that a user-mode mixer then writes to the kernel with
@@ -387,49 +526,42 @@ int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream)
        !!!  wins actually look like. Maybe add a hint to force exclusive mode at
        !!!  some point. To be sure, defaulting to shared mode is the right thing to
        !!!  do in any case. */
-    const SDL_AudioSpec oldspec = _this->spec;
     const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
-    UINT32 bufsize = 0; /* this is in sample frames, not samples, not bytes. */
-    REFERENCE_TIME default_period = 0;
-    IAudioClient *client = _this->hidden->client;
-    IAudioRenderClient *render = NULL;
-    IAudioCaptureClient *capture = NULL;
-    WAVEFORMATEX *waveformat = NULL;
-    SDL_AudioFormat test_format;
-    SDL_AudioFormat wasapi_format = 0;
-    const SDL_AudioFormat *closefmts;
-    HRESULT ret = S_OK;
-    DWORD streamflags = 0;
 
+    IAudioClient *client = device->hidden->client;
     SDL_assert(client != NULL);
 
-#if defined(__WINRT__) || defined(__GDK__) /* CreateEventEx() arrived in Vista, so we need an #ifdef for XP. */
-    _this->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
+#if defined(__WINRT__) || defined(__GDK__) // CreateEventEx() arrived in Vista, so we need an #ifdef for XP.
+    device->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
 #else
-    _this->hidden->event = CreateEventW(NULL, 0, 0, NULL);
+    device->hidden->event = CreateEventW(NULL, 0, 0, NULL);
 #endif
 
-    if (_this->hidden->event == NULL) {
+    if (device->hidden->event == NULL) {
         return WIN_SetError("WASAPI can't create an event handle");
     }
 
+    HRESULT ret;
+
+    WAVEFORMATEX *waveformat = NULL;
     ret = IAudioClient_GetMixFormat(client, &waveformat);
     if (FAILED(ret)) {
         return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
     }
-
     SDL_assert(waveformat != NULL);
-    _this->hidden->waveformat = waveformat;
+    device->hidden->waveformat = waveformat;
 
-    _this->spec.channels = (Uint8)waveformat->nChannels;
+    SDL_AudioSpec newspec;
+    newspec.channels = (Uint8)waveformat->nChannels;
 
-    /* Make sure we have a valid format that we can convert to whatever WASAPI wants. */
-    wasapi_format = WaveFormatToSDLFormat(waveformat);
+    // Make sure we have a valid format that we can convert to whatever WASAPI wants.
+    const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
 
-    closefmts = SDL_ClosestAudioFormats(_this->spec.format);
+    SDL_AudioFormat test_format;
+    const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
     while ((test_format = *(closefmts++)) != 0) {
         if (test_format == wasapi_format) {
-            _this->spec.format = test_format;
+            newspec.format = test_format;
             break;
         }
     }
@@ -438,36 +570,40 @@ int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream)
         return SDL_SetError("%s: Unsupported audio format", "wasapi");
     }
 
+    REFERENCE_TIME default_period = 0;
     ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
     if (FAILED(ret)) {
         return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
     }
 
+    DWORD streamflags = 0;
+
     /* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
        it fixes some other WASAPI-specific quirks we haven't quite tracked down.
        Refer to bug #6326 for the immediate concern. */
-#if 0
-    _this->spec.freq = waveformat->nSamplesPerSec;  /* force sampling rate so our resampler kicks in, if necessary. */
-#else
-    /* favor WASAPI's resampler over our own */
-    if ((DWORD)_this->spec.freq != waveformat->nSamplesPerSec) {
+#if 1
+    // favor WASAPI's resampler over our own
+    if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
         streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
-        waveformat->nSamplesPerSec = _this->spec.freq;
+        waveformat->nSamplesPerSec = device->spec.freq;
         waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
     }
 #endif
 
+    newspec.freq = waveformat->nSamplesPerSec;
+
     streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
     ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
     if (FAILED(ret)) {
         return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
     }
 
-    ret = IAudioClient_SetEventHandle(client, _this->hidden->event);
+    ret = IAudioClient_SetEventHandle(client, device->hidden->event);
     if (FAILED(ret)) {
         return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
     }
 
+    UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
     ret = IAudioClient_GetBufferSize(client, &bufsize);
     if (FAILED(ret)) {
         return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
@@ -475,116 +611,108 @@ int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream)
 
     /* Match the callback size to the period size to cut down on the number of
        interrupts waited for in each call to WaitDevice */
-    {
-        const float period_millis = default_period / 10000.0f;
-        const float period_frames = period_millis * _this->spec.freq / 1000.0f;
-        _this->spec.samples = (Uint16)SDL_ceilf(period_frames);
-    }
-
-    /* Update the fragment size as size in bytes */
-    SDL_CalculateAudioSpec(&_this->spec);
+    const float period_millis = default_period / 10000.0f;
+    const float period_frames = period_millis * newspec.freq / 1000.0f;
+    const int new_sample_frames = (int) SDL_ceilf(period_frames);
 
-    _this->hidden->framesize = (SDL_AUDIO_BITSIZE(_this->spec.format) / 8) * _this->spec.channels;
+    // Update the fragment size as size in bytes
+    if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) {
+        return -1;
+    }
 
-    if (_this->iscapture) {
-        _this->hidden->capturestream = SDL_CreateAudioStream(_this->spec.format, _this->spec.channels, _this->spec.freq, _this->spec.format, _this->spec.channels, _this->spec.freq);
-        if (!_this->hidden->capturestream) {
-            return -1; /* already set SDL_Error */
-        }
+    device->hidden->framesize = (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels;
 
+    if (device->iscapture) {
+        IAudioCaptureClient *capture = NULL;
         ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
         if (FAILED(ret)) {
             return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
         }
 
         SDL_assert(capture != NULL);
-        _this->hidden->capture = capture;
+        device->hidden->capture = capture;
         ret = IAudioClient_Start(client);
         if (FAILED(ret)) {
             return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
         }
 
-        WASAPI_FlushCapture(_this); /* MSDN says you should flush capture endpoint right after startup. */
+        WASAPI_FlushCapture(device); // MSDN says you should flush capture endpoint right after startup.
     } else {
+        IAudioRenderClient *render = NULL;
         ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
         if (FAILED(ret)) {
             return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
         }
 
         SDL_assert(render != NULL);
-        _this->hidden->render = render;
+        device->hidden->render = render;
         ret = IAudioClient_Start(client);
         if (FAILED(ret)) {
             return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
         }
     }
 
-    if (updatestream) {
-        return UpdateAudioStream(_this, &oldspec);
-    }
-
-    return 0; /* good to go. */
+    return 0; // good to go.
 }
 
-static int WASAPI_OpenDevice(SDL_AudioDevice *_this, const char *devname)
+// This is called once a device is activated, possibly asynchronously.
+int WASAPI_PrepDevice(SDL_AudioDevice *device)
 {
-    LPCWSTR devid = (LPCWSTR)_this->handle;
+    int rc;
+    return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) < 0) ? -1 : rc;
+}
 
-    /* Initialize all variables that we clean on shutdown */
-    _this->hidden = (struct SDL_PrivateAudioData *) SDL_malloc(sizeof(*_this->hidden));
-    if (_this->hidden == NULL) {
+static int WASAPI_OpenDevice(SDL_AudioDevice *device)
+{
+    // Initialize all variables that we clean on shutdown
+    device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
+    if (device->hidden == NULL) {
         return SDL_OutOfMemory();
-    }
-    SDL_zerop(_this->hidden);
-
-    WASAPI_RefDevice(_this); /* so CloseDevice() will unref to zero. */
-
-    if (!devid) { /* is default device? */
-        _this->hidden->default_device_generation = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration);
-    } else {
-        _this->hidden->devid = SDL_wcsdup(devid);
-        if (!_this->hidden->devid) {
-            return SDL_OutOfMemory();
-        }
-    }
-
-    if (WASAPI_ActivateDevice(_this, SDL_FALSE) == -1) {
-        return -1; /* already set error. */
+    } else if (ActivateWasapiDevice(device) < 0) {
+        return -1; // already set error.
     }
 
-    /* Ready, but waiting for async device activation.
+    /* Ready, but possibly waiting for async device activation.
        Until activation is successful, we will report silence from capture
-       devices and ignore data on playback devices.
-       Also, since we don't know the _actual_ device format until after
-       activation, we let the app have whatever it asks for. We set up
-       an SDL_AudioStream to convert, if necessary, once the activation
-       completes. */
+       devices and ignore data on playback devices. Upon activation, we'll make
+       sure any bound audio streams are adjusted for the final device format. */
 
     return 0;
 }
 
-static void WASAPI_ThreadInit(SDL_AudioDevice *_this)
+static void WASAPI_ThreadInit(SDL_AudioDevice *device)
+{
+    WASAPI_PlatformThreadInit(device);
+}
+
+static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
+{
+    WASAPI_PlatformThreadDeinit(device);
+}
+
+static int mgmtthrtask_FreeDeviceHandle(void *userdata)
 {
-    WASAPI_PlatformThreadInit(_this);
+    WASAPI_PlatformFreeDeviceHandle((SDL_AudioDevice *)userdata);
+    return 0;
 }
 
-static void WASAPI_ThreadDeinit(SDL_AudioDevice *_this)
+static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
 {
-    WASAPI_PlatformThreadDeinit(_this);
+    int rc;
+    WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
 }
 
 static void WASAPI_Deinitialize(void)
 {
-    WASAPI_PlatformDeinit();
+    DeinitManagementThread();
 }
 
 static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl)
 {
-    if (WASAPI_PlatformInit() == -1) {
+    if (InitManagementThread() < 0) {
         return SDL_FALSE;
     }
 
-    /* Set the function pointers */
     impl->DetectDevices = WASAPI_DetectDevices;
     impl->ThreadInit = WASAPI_ThreadInit;
     impl->ThreadDeinit = WASAPI_ThreadDeinit;
@@ -592,18 +720,20 @@ static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl)
     impl->PlayDevice = WASAPI_PlayDevice;
     impl->WaitDevice = WASAPI_WaitDevice;
     impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
+    impl->WaitCaptureDevice = WASAPI_WaitDevice;
     impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
     impl->FlushCapture = WASAPI_FlushCapture;
     impl->CloseDevice = WASAPI_CloseDevice;
     impl->Deinitialize = WASAPI_Deinitialize;
-    impl->GetDefaultAudioInfo = WASAPI_GetDefaultAudioInfo;
+    impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
+
     impl->HasCaptureSupport = SDL_TRUE;
 
-    return SDL_TRUE; /* this audio target is available. */
+    return SDL_TRUE;
 }
 
 AudioBootStrap WASAPI_bootstrap = {
     "wasapi", "WASAPI", WASAPI_Init, SDL_FALSE
 };
 
-#endif /* SDL_AUDIO_DRIVER_WASAPI */
+#endif // SDL_AUDIO_DRIVER_WASAPI

+ 13 - 12
src/audio/wasapi/SDL_wasapi.h

@@ -31,37 +31,38 @@ extern "C" {
 
 struct SDL_PrivateAudioData
 {
-    SDL_AtomicInt refcount;
     WCHAR *devid;
     WAVEFORMATEX *waveformat;
     IAudioClient *client;
     IAudioRenderClient *render;
     IAudioCaptureClient *capture;
-    SDL_AudioStream *capturestream;
     HANDLE event;
     HANDLE task;
     SDL_bool coinitialized;
     int framesize;
-    int default_device_generation;
     SDL_bool device_lost;
     void *activation_handler;
-    SDL_AtomicInt just_activated;
 };
 
 /* win32 and winrt implementations call into these. */
-int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream);
-void WASAPI_RefDevice(SDL_AudioDevice *_this);
-void WASAPI_UnrefDevice(SDL_AudioDevice *_this);
+int WASAPI_PrepDevice(SDL_AudioDevice *device);
+void WASAPI_DisconnectDevice(SDL_AudioDevice *device);
+
+
+// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
+typedef int (*ManagementThreadTask)(void *userdata);
+int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_until_complete);
 
 /* These are functions that are implemented differently for Windows vs WinRT. */
+// UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD.
 int WASAPI_PlatformInit(void);
 void WASAPI_PlatformDeinit(void);
-void WASAPI_EnumerateEndpoints(void);
-int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture);
-int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery);
-void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this);
-void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this);
+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.
+void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device);  // this happens on the audio device thread, not the management thread.
 void WASAPI_PlatformDeleteActivationHandler(void *handler);
+void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device);
 
 #ifdef __cplusplus
 }

+ 28 - 30
src/audio/wasapi/SDL_wasapi_win32.c

@@ -49,7 +49,7 @@ static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x
 
 int WASAPI_PlatformInit(void)
 {
-    if (SDL_IMMDevice_Init() < 0) {
+    if (SDL_IMMDevice_Init() < 0) {   // this will call WIN_CoInitialize for us!
         return -1; /* This is set by SDL_IMMDevice_Init */
     }
 
@@ -72,72 +72,65 @@ void WASAPI_PlatformDeinit(void)
     pAvSetMmThreadCharacteristicsW = NULL;
     pAvRevertMmThreadCharacteristics = NULL;
 
-    SDL_IMMDevice_Quit();
+    SDL_IMMDevice_Quit();  // This will call WIN_CoUninitialize for us!
 }
 
-void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this)
+void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
 {
     /* this thread uses COM. */
     if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */
-        _this->hidden->coinitialized = SDL_TRUE;
+        device->hidden->coinitialized = SDL_TRUE;
     }
 
     /* Set this thread to very high "Pro Audio" priority. */
     if (pAvSetMmThreadCharacteristicsW) {
         DWORD idx = 0;
-        _this->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
+        device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
     }
 }
 
-void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this)
+void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
 {
     /* Set this thread back to normal priority. */
-    if (_this->hidden->task && pAvRevertMmThreadCharacteristics) {
-        pAvRevertMmThreadCharacteristics(_this->hidden->task);
-        _this->hidden->task = NULL;
+    if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
+        pAvRevertMmThreadCharacteristics(device->hidden->task);
+        device->hidden->task = NULL;
     }
 
-    if (_this->hidden->coinitialized) {
+    if (device->hidden->coinitialized) {
         WIN_CoUninitialize();
-        _this->hidden->coinitialized = SDL_FALSE;
+        device->hidden->coinitialized = SDL_FALSE;
     }
 }
 
-int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
+int WASAPI_ActivateDevice(SDL_AudioDevice *device)
 {
-    IMMDevice *device = NULL;
-    HRESULT ret;
-
-    if (SDL_IMMDevice_Get(_this->hidden->devid, &device, _this->iscapture) < 0) {
-        _this->hidden->client = NULL;
+    IMMDevice *immdevice = NULL;
+    if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) {
+        device->hidden->client = NULL;
         return -1; /* This is already set by SDL_IMMDevice_Get */
     }
 
-    /* this is not async in standard win32, yay! */
-    ret = IMMDevice_Activate(device, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&_this->hidden->client);
-    IMMDevice_Release(device);
+    /* this is _not_ async in standard win32, yay! */
+    HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
+    IMMDevice_Release(immdevice);
 
     if (FAILED(ret)) {
-        SDL_assert(_this->hidden->client == NULL);
+        SDL_assert(device->hidden->client == NULL);
         return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
     }
 
-    SDL_assert(_this->hidden->client != NULL);
-    if (WASAPI_PrepDevice(_this, isrecovery) == -1) { /* not async, fire it right away. */
+    SDL_assert(device->hidden->client != NULL);
+    if (WASAPI_PrepDevice(device) == -1) { /* not async, fire it right away. */
         return -1;
     }
 
     return 0; /* good to go. */
 }
 
-void WASAPI_EnumerateEndpoints(void)
-{
-    SDL_IMMDevice_EnumerateEndpoints(SDL_FALSE);
-}
-
-int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture)
+void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
 {
-    return SDL_IMMDevice_GetDefaultAudioInfo(name, spec, iscapture);
+    SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
 }
 
 void WASAPI_PlatformDeleteActivationHandler(void *handler)
@@ -146,4 +139,9 @@ void WASAPI_PlatformDeleteActivationHandler(void *handler)
     SDL_assert(!"This function should have only been called on WinRT.");
 }
 
+void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
+{
+    SDL_IMMDevice_FreeDeviceHandle(device);
+}
+
 #endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */

+ 92 - 170
src/audio/wasapi/SDL_wasapi_winrt.cpp

@@ -53,21 +53,16 @@ using namespace Microsoft::WRL;
 
 static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
 
-static void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid);
-static void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid);
-extern "C" {
-SDL_AtomicInt SDL_IMMDevice_DefaultPlaybackGeneration;
-SDL_AtomicInt SDL_IMMDevice_DefaultCaptureGeneration;
-}
 
-/* This is a list of device id strings we have inflight, so we have consistent pointers to the same device. */
-typedef struct DevIdList
+static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata)
 {
-    WCHAR *str;
-    struct DevIdList *next;
-} DevIdList;
+    return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0) ? SDL_TRUE : SDL_FALSE;
+}
 
-static DevIdList *deviceid_list = NULL;
+static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid)
+{
+    return SDL_FindPhysicalAudioDeviceByCallback(FindWinRTAudioDeviceCallback, (void *) devid);
+}
 
 class SDL_WasapiDeviceEventHandler
 {
@@ -80,9 +75,10 @@ class SDL_WasapiDeviceEventHandler
     void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args);
     void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args);
     void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args);
-    SDL_Semaphore *completed;
+    void WaitForCompletion();
 
   private:
+    SDL_Semaphore *completed_semaphore;
     const SDL_bool iscapture;
     DeviceWatcher ^ watcher;
     Windows::Foundation::EventRegistrationToken added_handler;
@@ -93,10 +89,11 @@ class SDL_WasapiDeviceEventHandler
 };
 
 SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
-    : iscapture(_iscapture), completed(SDL_CreateSemaphore(0))
+    : iscapture(_iscapture), completed_semaphore(SDL_CreateSemaphore(0))
 {
-    if (!completed)
+    if (!completed_semaphore) {
         return; // uhoh.
+    }
 
     Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector();
     Platform::Collections::Vector<Platform::String ^> properties;
@@ -128,9 +125,10 @@ SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
         watcher->Stop();
         watcher = nullptr;
     }
-    if (completed) {
-        SDL_DestroySemaphore(completed);
-        completed = nullptr;
+
+    if (completed_semaphore) {
+        SDL_DestroySemaphore(completed_semaphore);
+        completed_semaphore = nullptr;
     }
 
     if (iscapture) {
@@ -142,21 +140,34 @@ SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
 
 void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ info)
 {
+    /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
+       In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
+       phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
+       available and switch automatically. (!!! FIXME...?) */
+
     SDL_assert(sender == this->watcher);
     char *utf8dev = WIN_StringToUTF8(info->Name->Data());
     if (utf8dev) {
-        WAVEFORMATEXTENSIBLE fmt;
+        SDL_AudioSpec spec;
+        SDL_zero(spec);
+
         Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat);
         if (obj) {
             IPropertyValue ^ property = (IPropertyValue ^) obj;
             Platform::Array<unsigned char> ^ data;
             property->GetUInt8Array(&data);
-            SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE)));
-        } else {
+            WAVEFORMATEXTENSIBLE fmt;
             SDL_zero(fmt);
+            SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE)));
+            spec.channels = (Uint8)fmt->Format.nChannels;
+            spec.freq = fmt->Format.nSamplesPerSec;
+            spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt);
         }
 
-        WASAPI_AddDevice(this->iscapture, utf8dev, &fmt, info->Id->Data());
+        LPWSTR devid = SDL_wcsdup(devid);
+        if (devid) {
+            SDL_AddAudioDevice(this->iscapture, utf8dev, spec.channels ? &spec : NULL, devid);
+        }
         SDL_free(utf8dev);
     }
 }
@@ -164,7 +175,7 @@ void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceI
 void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info)
 {
     SDL_assert(sender == this->watcher);
-    WASAPI_RemoveDevice(this->iscapture, info->Id->Data());
+    WASAPI_DisconnectDevice(FindWinRTAudioDevice(info->Id->Data()));
 }
 
 void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args)
@@ -175,19 +186,30 @@ void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, Devic
 void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args)
 {
     SDL_assert(sender == this->watcher);
-    SDL_PostSemaphore(this->completed);
+    if (this->completed_semaphore) {
+        SDL_PostSemaphore(this->completed_semaphore);
+    }
 }
 
 void SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args)
 {
     SDL_assert(!this->iscapture);
-    SDL_AtomicAdd(&SDL_IMMDevice_DefaultPlaybackGeneration, 1);
+    SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
 }
 
 void SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args)
 {
     SDL_assert(this->iscapture);
-    SDL_AtomicAdd(&SDL_IMMDevice_DefaultCaptureGeneration, 1);
+    SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
+}
+
+void SDL_WasapiDeviceEventHandler::WaitForCompletion()
+{
+    if (this->completed_semaphore) {
+        SDL_WaitSemaphore(this->completed_semaphore);
+        SDL_DestroySemaphore(this->completed_semaphore);
+        this->completed_semaphore = nullptr;
+    }
 }
 
 static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
@@ -195,54 +217,63 @@ static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
 
 int WASAPI_PlatformInit(void)
 {
-    SDL_AtomicSet(&SDL_IMMDevice_DefaultPlaybackGeneration, 1);
-    SDL_AtomicSet(&SDL_IMMDevice_DefaultCaptureGeneration, 1);
     return 0;
 }
 
 void WASAPI_PlatformDeinit(void)
 {
-    DevIdList *devidlist;
-    DevIdList *next;
-
     delete playback_device_event_handler;
     playback_device_event_handler = nullptr;
     delete capture_device_event_handler;
     capture_device_event_handler = nullptr;
-
-    for (devidlist = deviceid_list; devidlist; devidlist = next) {
-        next = devidlist->next;
-        SDL_free(devidlist->str);
-        SDL_free(devidlist);
-    }
-    deviceid_list = NULL;
 }
 
-void WASAPI_EnumerateEndpoints(void)
+void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
 {
+    Platform::String ^ defdevid;
+
     // DeviceWatchers will fire an Added event for each existing device at
     //  startup, so we don't need to enumerate them separately before
     //  listening for updates.
     playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE);
+    playback_device_event_handler->WaitForCompletion();
+    defdevid = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
+    if (defdevid) {
+        *default_output = FindWinRTAudioDevice(defdevid->Data());
+    }
+
     capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE);
-    SDL_WaitSemaphore(playback_device_event_handler->completed);
-    SDL_WaitSemaphore(capture_device_event_handler->completed);
+    capture_device_event_handler->WaitForCompletion();
+    defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default)
+    if (defdevid) {
+        *default_capture = FindWinRTAudioDevice(defdevid->Data());
+    }
 }
 
-struct SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler>
+class SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler>
 {
-    SDL_WasapiActivationHandler() : device(nullptr) {}
-    STDMETHOD(ActivateCompleted)
-    (IActivateAudioInterfaceAsyncOperation *operation);
-    SDL_AudioDevice *device;
+public:
+    SDL_WasapiActivationHandler() : completion_semaphore(SDL_CreateSemaphore(0)) { SDL_assert(completion_semaphore != NULL); }
+    STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
+    void WaitForCompletion();
+private:
+    SDL_Semaphore *completion_semaphore;
 };
 
+void SDL_WasapiActivationHandler::WaitForCompletion()
+{
+    if (completion_semaphore) {
+        SDL_WaitSemaphore(completion_semaphore);
+        SDL_DestroySemaphore(completion_semaphore);
+        completion_semaphore = NULL;
+    }
+}
+
 HRESULT
 SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async)
 {
     // Just set a flag, since we're probably in a different thread. We'll pick it up and init everything on our own thread to prevent races.
-    SDL_AtomicSet(&device->hidden->just_activated, 1);
-    WASAPI_UnrefDevice(device);
+    SDL_PostSemaphore(completion_semaphore);
     return S_OK;
 }
 
@@ -251,24 +282,10 @@ void WASAPI_PlatformDeleteActivationHandler(void *handler)
     ((SDL_WasapiActivationHandler *)handler)->Release();
 }
 
-int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture)
+int WASAPI_ActivateDevice(SDL_AudioDevice *device)
 {
-    return SDL_Unsupported();
-}
-
-int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
-{
-    LPCWSTR devid = _this->hidden->devid;
-    Platform::String ^ defdevid;
-
-    if (devid == nullptr) {
-        defdevid = _this->iscapture ? MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) : MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
-        if (defdevid) {
-            devid = defdevid->Data();
-        }
-    }
-
-    SDL_AtomicSet(&_this->hidden->just_activated, 0);
+    LPCWSTR devid = (LPCWSTR) device->handle;
+    SDL_assert(devid != NULL);
 
     ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
     if (handler == nullptr) {
@@ -276,10 +293,8 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
     }
 
     handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
-    handler.Get()->device = _this;
-    _this->hidden->activation_handler = handler.Get();
+    device->hidden->activation_handler = handler.Get();
 
-    WASAPI_RefDevice(_this); /* completion handler will unref it. */
     IActivateAudioInterfaceAsyncOperation *async = nullptr;
     const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
 
@@ -288,21 +303,11 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
             async->Release();
         }
         handler.Get()->Release();
-        WASAPI_UnrefDevice(_this);
         return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
     }
 
-    /* Spin until the async operation is complete.
-     * If we don't PrepDevice before leaving this function, the bug list gets LONG:
-     * - device.spec is not filled with the correct information
-     * - The 'obtained' spec will be wrong for ALLOW_CHANGE properties
-     * - SDL_AudioStreams will/will not be allocated at the right time
-     * - SDL_assert(device->callbackspec.size == device->spec.size) will fail
-     * - When the assert is ignored, skipping or a buffer overflow will occur
-     */
-    while (!SDL_AtomicCAS(&_this->hidden->just_activated, 1, 0)) {
-        SDL_Delay(1);
-    }
+    // !!! FIXME: the problems in SDL2 that needed this to be synchronous are _probably_ solved by SDL3, and this can block indefinitely if a user prompt is shown to get permission to use a microphone.
+    handler.WaitForCompletion();  // block here until we have an answer, so this is synchronous to us after all.
 
     HRESULT activateRes = S_OK;
     IUnknown *iunknown = nullptr;
@@ -314,114 +319,31 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
         return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes);
     }
 
-    iunknown->QueryInterface(IID_PPV_ARGS(&_this->hidden->client));
-    if (!_this->hidden->client) {
+    iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client));
+    if (!device->hidden->client) {
         return SDL_SetError("Failed to query WASAPI client interface");
     }
 
-    if (WASAPI_PrepDevice(_this, isrecovery) == -1) {
+    if (WASAPI_PrepDevice(device) == -1) {
         return -1;
     }
 
     return 0;
 }
 
-void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this)
+void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
 {
     // !!! FIXME: set this thread to "Pro Audio" priority.
 }
 
-void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this)
+void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
 {
     // !!! FIXME: set this thread to "Pro Audio" priority.
 }
 
-/* Everything below was copied from SDL_wasapi.c, before it got moved to SDL_immdevice.c! */
-
-static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
-static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
-
-extern "C" SDL_AudioFormat
-WaveFormatToSDLFormat(WAVEFORMATEX *waveformat)
-{
-    if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
-        return SDL_AUDIO_F32SYS;
-    } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
-        return SDL_AUDIO_S16SYS;
-    } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
-        return SDL_AUDIO_S32SYS;
-    } else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
-        const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat;
-        if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
-            return SDL_AUDIO_F32SYS;
-        } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
-            return SDL_AUDIO_S16SYS;
-        } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
-            return SDL_AUDIO_S32SYS;
-        }
-    }
-    return 0;
-}
-
-static void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
-{
-    DevIdList *i;
-    DevIdList *next;
-    DevIdList *prev = NULL;
-    for (i = deviceid_list; i; i = next) {
-        next = i->next;
-        if (SDL_wcscmp(i->str, devid) == 0) {
-            if (prev) {
-                prev->next = next;
-            } else {
-                deviceid_list = next;
-            }
-            SDL_RemoveAudioDevice(iscapture, i->str);
-            SDL_free(i->str);
-            SDL_free(i);
-        } else {
-            prev = i;
-        }
-    }
-}
-
-static void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid)
+void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
 {
-    DevIdList *devidlist;
-    SDL_AudioSpec spec;
-
-    /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
-       In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
-       phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
-       available and switch automatically. (!!! FIXME...?) */
-
-    /* see if we already have this one. */
-    for (devidlist = deviceid_list; devidlist; devidlist = devidlist->next) {
-        if (SDL_wcscmp(devidlist->str, devid) == 0) {
-            return; /* we already have this. */
-        }
-    }
-
-    devidlist = (DevIdList *)SDL_malloc(sizeof(*devidlist));
-    if (devidlist == NULL) {
-        return; /* oh well. */
-    }
-
-    devid = SDL_wcsdup(devid);
-    if (!devid) {
-        SDL_free(devidlist);
-        return; /* oh well. */
-    }
-
-    devidlist->str = (WCHAR *)devid;
-    devidlist->next = deviceid_list;
-    deviceid_list = devidlist;
-
-    SDL_zero(spec);
-    spec.channels = (Uint8)fmt->Format.nChannels;
-    spec.freq = fmt->Format.nSamplesPerSec;
-    spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *)fmt);
-    SDL_AddAudioDevice(iscapture, devname, &spec, (void *)devid);
+    SDL_free(device->handle);
 }
 
 #endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)

+ 3 - 4
src/core/windows/SDL_immdevice.c

@@ -147,7 +147,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const SDL_bool iscapture, const char *
         SDL_zero(spec);
         spec.channels = (Uint8)fmt->Format.nChannels;
         spec.freq = fmt->Format.nSamplesPerSec;
-        spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *)fmt);
+        spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt);
 
         device = SDL_AddAudioDevice(iscapture, devname, &spec, handle);
     }
@@ -164,7 +164,6 @@ typedef struct SDLMMNotificationClient
 {
     const IMMNotificationClientVtbl *lpVtbl;
     SDL_AtomicInt refcount;
-    SDL_bool useguid;
 } SDLMMNotificationClient;
 
 static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv)
@@ -370,7 +369,7 @@ static void EnumerateEndpointsForFlow(const SDL_bool iscapture, SDL_AudioDevice
                 GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid);
                 if (devname) {
                     SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(iscapture, devname, &fmt, devid, &dsoundguid);
-                    if (default_device && default_devid && SDL_wcscmp(default_devid, devid)) {
+                    if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) {
                         *default_device = sdldevice;
                     }
                     SDL_free(devname);
@@ -395,7 +394,7 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi
     IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)&notification_client);
 }
 
-SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat)
+SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat)
 {
     if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
         return SDL_AUDIO_F32SYS;

+ 1 - 1
src/core/windows/SDL_immdevice.h

@@ -35,6 +35,6 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi
 LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device);
 LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device);
 void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device);
-SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat);
+SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);
 
 #endif /* SDL_IMMDEVICE_H */

+ 1 - 1
test/loopwave.c

@@ -39,7 +39,7 @@ static SDL_AudioStream *stream;
 
 static void fillerup(void)
 {
-    if (SDL_GetAudioStreamAvailable(stream) < (wave.soundlen / 2)) {
+    if (SDL_GetAudioStreamAvailable(stream) < (int) ((wave.soundlen / 2))) {
         SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
     }
 }