SDL: Add SDL_GetAudioDeviceSpec.

From 67e8522d31b875874ffd24086678ee6c861ff91c Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Sat, 27 Feb 2021 17:37:25 -0500
Subject: [PATCH] Add SDL_GetAudioDeviceSpec.

This API is supported by pipewire, pulseaudio, coreaudio, wasapi, and disk.
---
 include/SDL_audio.h                     | 19 ++++++++
 src/audio/SDL_audio.c                   | 61 +++++++++++++++++++++----
 src/audio/SDL_audiodev.c                |  8 +++-
 src/audio/SDL_sysaudio.h                |  3 +-
 src/audio/alsa/SDL_alsa_audio.c         |  6 ++-
 src/audio/coreaudio/SDL_coreaudio.m     | 33 ++++++++-----
 src/audio/directsound/SDL_directsound.c |  7 ++-
 src/audio/disk/SDL_diskaudio.c          |  4 +-
 src/audio/os2/SDL_os2audio.c            |  4 +-
 src/audio/pipewire/SDL_pipewire.c       |  4 +-
 src/audio/pulseaudio/SDL_pulseaudio.c   | 47 ++++++++++++++++++-
 src/audio/qsa/SDL_qsa_audio.c           | 12 ++++-
 src/audio/wasapi/SDL_wasapi.c           | 49 ++++++++++++--------
 src/audio/wasapi/SDL_wasapi.h           |  2 +-
 src/audio/wasapi/SDL_wasapi_win32.c     | 26 +++++++----
 src/audio/wasapi/SDL_wasapi_winrt.cpp   | 27 +++++++++--
 src/audio/winmm/SDL_winmm.c             |  9 +++-
 src/dynapi/SDL_dynapi_overrides.h       |  1 +
 src/dynapi/SDL_dynapi_procs.h           |  1 +
 19 files changed, 256 insertions(+), 67 deletions(-)

diff --git a/include/SDL_audio.h b/include/SDL_audio.h
index 46f03164e..46fefe2e2 100644
--- a/include/SDL_audio.h
+++ b/include/SDL_audio.h
@@ -359,6 +359,25 @@ extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture);
 extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index,
                                                            int iscapture);
 
+/**
+ *  Get the audio format of a specific audio device.
+ *  Must be a value between 0 and (number of audio devices-1).
+ *  Only valid after a successfully initializing the audio subsystem.
+ *  The values returned by this function reflect the latest call to
+ *  SDL_GetNumAudioDevices(); recall that function to redetect available
+ *  hardware.
+ *
+ *  The spec will be filled with the sample rate, sample format, and channel
+ *  count. All other values in the structure are filled with 0. When the
+ *  supported struct members are 0, SDL was unable to get the property from the
+ *  backend.
+ *
+ *  \return 0 on success, nonzero on error
+ */
+extern DECLSPEC int SDLCALL SDL_GetAudioDeviceSpec(int index,
+                                                   int iscapture,
+                                                   SDL_AudioSpec *spec);
+
 
 /**
  *  Open a specific audio device. Passing in a device name of NULL requests
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index e0d6d450a..725e169ce 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -223,9 +223,9 @@ SDL_AudioDetectDevices_Default(void)
     SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice);
     SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport);
 
-    SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, (void *) ((size_t) 0x1));
+    SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *) ((size_t) 0x1));
     if (current_audio.impl.HasCaptureSupport) {
-        SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, (void *) ((size_t) 0x2));
+        SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *) ((size_t) 0x2));
     }
 }
 
@@ -377,7 +377,7 @@ finish_audio_entry_points_init(void)
 /* device hotplug support... */
 
 static int
-add_audio_device(const char *name, void *handle, SDL_AudioDeviceItem **devices, int *devCount)
+add_audio_device(const char *name, SDL_AudioSpec *spec, void *handle, SDL_AudioDeviceItem **devices, int *devCount)
 {
     int retval = -1;
     SDL_AudioDeviceItem *item;
@@ -400,6 +400,11 @@ add_audio_device(const char *name, void *handle, SDL_AudioDeviceItem **devices,
 
     item->dupenum = 0;
     item->name = item->original_name;
+    if (spec != NULL) {
+        SDL_memcpy(&item->spec, spec, sizeof(SDL_AudioSpec));
+    } else {
+        SDL_zero(item->spec);
+    }
     item->handle = handle;
 
     SDL_LockMutex(current_audio.detectionLock);
@@ -437,16 +442,16 @@ add_audio_device(const char *name, void *handle, SDL_AudioDeviceItem **devices,
 }
 
 static SDL_INLINE int
-add_capture_device(const char *name, void *handle)
+add_capture_device(const char *name, SDL_AudioSpec *spec, void *handle)
 {
     SDL_assert(current_audio.impl.HasCaptureSupport);
-    return add_audio_device(name, handle, &current_audio.inputDevices, &current_audio.inputDeviceCount);
+    return add_audio_device(name, spec, handle, &current_audio.inputDevices, &current_audio.inputDeviceCount);
 }
 
 static SDL_INLINE int
-add_output_device(const char *name, void *handle)
+add_output_device(const char *name, SDL_AudioSpec *spec, void *handle)
 {
-    return add_audio_device(name, handle, &current_audio.outputDevices, &current_audio.outputDeviceCount);
+    return add_audio_device(name, spec, handle, &current_audio.outputDevices, &current_audio.outputDeviceCount);
 }
 
 static void
@@ -472,9 +477,9 @@ free_device_list(SDL_AudioDeviceItem **devices, int *devCount)
 
 /* The audio backends call this when a new device is plugged in. */
 void
-SDL_AddAudioDevice(const int iscapture, const char *name, void *handle)
+SDL_AddAudioDevice(const int iscapture, const char *name, SDL_AudioSpec *spec, void *handle)
 {
-    const int device_index = iscapture ? add_capture_device(name, handle) : add_output_device(name, handle);
+    const int device_index = iscapture ? add_capture_device(name, spec, handle) : add_output_device(name, spec, handle);
     if (device_index != -1) {
         /* Post the event, if desired */
         if (SDL_GetEventState(SDL_AUDIODEVICEADDED) == SDL_ENABLE) {
@@ -1112,6 +1117,44 @@ SDL_GetAudioDeviceName(int index, int iscapture)
 }
 
 
+int
+SDL_GetAudioDeviceSpec(int index, int iscapture, SDL_AudioSpec *spec)
+{
+    if (spec == NULL) {
+        return SDL_InvalidParamError("spec");
+    }
+
+    SDL_zerop(spec);
+
+    if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+        return SDL_SetError("Audio subsystem is not initialized");
+    }
+
+    if (iscapture && !current_audio.impl.HasCaptureSupport) {
+        return SDL_SetError("No capture support");
+    }
+
+    if (index >= 0) {
+        SDL_AudioDeviceItem *item;
+        int i;
+
+        SDL_LockMutex(current_audio.detectionLock);
+        item = iscapture ? current_audio.inputDevices : current_audio.outputDevices;
+        i = iscapture ? current_audio.inputDeviceCount : current_audio.outputDeviceCount;
+        if (index < i) {
+            for (i--; i > index; i--, item = item->next) {
+                SDL_assert(item != NULL);
+            }
+            SDL_assert(item != NULL);
+            SDL_memcpy(spec, &item->spec, sizeof(SDL_AudioSpec));
+        }
+        SDL_UnlockMutex(current_audio.detectionLock);
+    }
+
+    return 0;
+}
+
+
 static void
 close_audio_device(SDL_AudioDevice * device)
 {
diff --git a/src/audio/SDL_audiodev.c b/src/audio/SDL_audiodev.c
index 78c85b55c..42364027c 100644
--- a/src/audio/SDL_audiodev.c
+++ b/src/audio/SDL_audiodev.c
@@ -59,7 +59,13 @@ test_device(const int iscapture, const char *fname, int flags, int (*test) (int
                 static size_t dummyhandle = 0;
                 dummyhandle++;
                 SDL_assert(dummyhandle != 0);
-                SDL_AddAudioDevice(iscapture, fname, (void *) dummyhandle);
+
+                /* Note that spec is NULL; while we are opening the device
+                 * endpoint here, the endpoint does not provide any mix format
+                 * information,  making this information inaccessible at
+                 * enumeration time
+                 */
+                SDL_AddAudioDevice(iscapture, fname, NULL, (void *) dummyhandle);
             }
         }
     }
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index ad9a164a9..0c66ec657 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -39,7 +39,7 @@ typedef struct SDL_AudioDevice SDL_AudioDevice;
 /* Audio targets should call this as devices are added to the system (such as
    a USB headset being plugged in), and should also be called for
    for every device found during DetectDevices(). */
-extern void SDL_AddAudioDevice(const int iscapture, const char *name, void *handle);
+extern void SDL_AddAudioDevice(const int iscapture, const char *name, SDL_AudioSpec *spec, void *handle);
 
 /* Audio targets should call this as devices are removed, so SDL can update
    its list of available devices. */
@@ -99,6 +99,7 @@ typedef struct SDL_AudioDeviceItem
     void *handle;
     char *name;
     char *original_name;
+    SDL_AudioSpec spec;
     int dupenum;
     struct SDL_AudioDeviceItem *next;
 } SDL_AudioDeviceItem;
diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index 547855b75..7a6b7344b 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -765,7 +765,11 @@ add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSee
         return;
     }
 
-    SDL_AddAudioDevice(iscapture, desc, handle);
+    /* Note that spec is NULL, because we are required to open the device before
+     * acquiring the mix format, making this information inaccessible at
+     * enumeration time
+     */
+    SDL_AddAudioDevice(iscapture, desc, NULL, handle);
     if (hint)
         free(desc);
     dev->name = handle;
diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index f438fcda3..f84a1c558 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -56,7 +56,7 @@
     kAudioObjectPropertyElementMaster
 };
 
-typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
+typedef void (*addDevFn)(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data);
 
 typedef struct AudioDeviceList
 {
@@ -88,10 +88,10 @@
 }
 
 static void
-addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
+addToDevList(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data)
 {
     if (add_to_internal_dev_list(iscapture, devId)) {
-        SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
+        SDL_AddAudioDevice(iscapture, name, spec, (void *) ((size_t) devId));
     }
 }
 
@@ -126,17 +126,23 @@
         AudioBufferList *buflist = NULL;
         int usable = 0;
         CFIndex len = 0;
+        double sampleRate = 0;
+        SDL_AudioSpec spec;
         const AudioObjectPropertyAddress addr = {
             kAudioDevicePropertyStreamConfiguration,
             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
             kAudioObjectPropertyElementMaster
         };
-
         const AudioObjectPropertyAddress nameaddr = {
             kAudioObjectPropertyName,
             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
             kAudioObjectPropertyElementMaster
         };
+        const AudioObjectPropertyAddress freqaddr = {
+            kAudioDevicePropertyNominalSampleRate,
+            iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+            kAudioObjectPropertyElementMaster
+        };
 
         result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
         if (result != noErr)
@@ -149,21 +155,24 @@
         result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
                                             &size, buflist);
 
+        SDL_zero(spec);
         if (result == noErr) {
             UInt32 j;
             for (j = 0; j < buflist->mNumberBuffers; j++) {
-                if (buflist->mBuffers[j].mNumberChannels > 0) {
-                    usable = 1;
-                    break;
-                }
+                spec.channels += buflist->mBuffers[j].mNumberChannels;
             }
         }
 
         SDL_free(buflist);
 
-        if (!usable)
+        if (spec.channels == 0)
             continue;
 
+        size = sizeof (sampleRate);
+        result = AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate);
+        if (result == noErr) {
+            spec.freq = (int) sampleRate;
+        }
 
         size = sizeof (CFStringRef);
         result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
@@ -197,7 +206,7 @@
                    ((iscapture) ? "capture" : "output"),
                    (int) i, ptr, (int) dev);
 #endif
-            addfn(ptr, iscapture, dev, addfndata);
+            addfn(ptr, &spec, iscapture, dev, addfndata);
         }
         SDL_free(ptr);  /* addfn() would have copied the string. */
     }
@@ -223,7 +232,7 @@
 }
 
 static void
-build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
+build_device_change_list(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data)
 {
     AudioDeviceList **list = (AudioDeviceList **) data;
     AudioDeviceList *item;
@@ -235,7 +244,7 @@
     }
 
     add_to_internal_dev_list(iscapture, devId);  /* new device, add it. */
-    SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
+    SDL_AddAudioDevice(iscapture, name, spec, (void *) ((size_t) devId));
 }
 
 static void
diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c
index 008565297..700a40084 100644
--- a/src/audio/directsound/SDL_directsound.c
+++ b/src/audio/directsound/SDL_directsound.c
@@ -163,7 +163,12 @@ FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data)
         if (str != NULL) {
             LPGUID cpyguid = (LPGUID) SDL_malloc(sizeof (GUID));
             SDL_memcpy(cpyguid, guid, sizeof (GUID));
-            SDL_AddAudioDevice(iscapture, str, cpyguid);
+
+            /* Note that spec is NULL, because we are required to connect to the
+             * device before getting the channel mask and output format, making
+             * this information inaccessible at enumeration time
+             */
+            SDL_AddAudioDevice(iscapture, str, NULL, cpyguid);
             SDL_free(str);  /* addfn() makes a copy of this string. */
         }
     }
diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c
index 8a2ef491f..5b2fa145f 100644
--- a/src/audio/disk/SDL_diskaudio.c
+++ b/src/audio/disk/SDL_diskaudio.c
@@ -173,8 +173,8 @@ DISKAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
 static void
 DISKAUDIO_DetectDevices(void)
 {
-    SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, (void *) 0x1);
-    SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, (void *) 0x2);
+    SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *) 0x1);
+    SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *) 0x2);
 }
 
 static int
diff --git a/src/audio/os2/SDL_os2audio.c b/src/audio/os2/SDL_os2audio.c
index abcbc1ca5..9369eaf2d 100644
--- a/src/audio/os2/SDL_os2audio.c
+++ b/src/audio/os2/SDL_os2audio.c
@@ -160,9 +160,9 @@ static void OS2_DetectDevices(void)
         }
 
         ulHandle++;
-        SDL_AddAudioDevice(0, stLogDevice.szProductInfo, (void *)(ulHandle));
+        SDL_AddAudioDevice(0, stLogDevice.szProductInfo, NULL, (void *)(ulHandle));
         ulHandle++;
-        SDL_AddAudioDevice(1, stLogDevice.szProductInfo, (void *)(ulHandle));
+        SDL_AddAudioDevice(1, stLogDevice.szProductInfo, NULL, (void *)(ulHandle));
     }
 }
 
diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
index 637b02607..a073f8f1f 100644
--- a/src/audio/pipewire/SDL_pipewire.c
+++ b/src/audio/pipewire/SDL_pipewire.c
@@ -266,7 +266,7 @@ io_list_check_add(struct io_node *node)
     spa_list_append(&hotplug_io_list, &node->link);
 
     if (SDL_AtomicGet(&hotplug_events_enabled)) {
-        SDL_AddAudioDevice(node->is_capture, node->name, PW_ID_TO_HANDLE(node->id));
+        SDL_AddAudioDevice(node->is_capture, node->name, &node->spec, PW_ID_TO_HANDLE(node->id));
     }
 
 dup_found:
@@ -768,7 +768,7 @@ PIPEWIRE_DetectDevices()
     io_list_sort();
 
     spa_list_for_each (io, &hotplug_io_list, link) {
-        SDL_AddAudioDevice(io->is_capture, io->name, PW_ID_TO_HANDLE(io->id));
+        SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id));
     }
 
     SDL_AtomicSet(&hotplug_events_enabled, 1);
diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c
index 25dba67d3..bb5997ed1 100644
--- a/src/audio/pulseaudio/SDL_pulseaudio.c
+++ b/src/audio/pulseaudio/SDL_pulseaudio.c
@@ -696,12 +696,45 @@ static SDL_Thread *hotplug_thread = NULL;
 
 /* device handles are device index + 1, cast to void*, so we never pass a NULL. */
 
+static SDL_AudioFormat
+PulseFormatToSDLFormat(pa_sample_format_t format)
+{
+    switch (format) {
+    case PA_SAMPLE_U8:
+        return AUDIO_U8;
+    case PA_SAMPLE_S16LE:
+        return AUDIO_S16LSB;
+    case PA_SAMPLE_S16BE:
+        return AUDIO_S16MSB;
+    case PA_SAMPLE_S32LE:
+        return AUDIO_S32LSB;
+    case PA_SAMPLE_S32BE:
+        return AUDIO_S32MSB;
+    case PA_SAMPLE_FLOAT32LE:
+        return AUDIO_F32LSB;
+    case PA_SAMPLE_FLOAT32BE:
+        return AUDIO_F32MSB;
+    default:
+        return 0;
+    }
+}
+
 /* This is called when PulseAudio adds an output ("sink") device. */
 static void
 SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
 {
+    SDL_AudioSpec spec;
     if (i) {
-        SDL_AddAudioDevice(SDL_FALSE, i->description, (void *) ((size_t) i->index+1));
+        spec.freq = i->sample_spec.rate;
+        spec.channels = i->sample_spec.channels;
+        spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
+        spec.silence = 0;
+        spec.samples = 0;
+        spec.size = 0;
+        spec.callback = NULL;
+        spec.userdata = NULL;
+
+        SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *) ((size_t) i->index+1));
     }
 }
 
@@ -709,10 +742,20 @@ SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
 static void
 SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
 {
+    SDL_AudioSpec spec;
     if (i) {
         /* Skip "monitor" sources. These are just output from other sinks. */
         if (i->monitor_of_sink == PA_INVALID_INDEX) {
-            SDL_AddAudioDevice(SDL_TRUE, i->description, (void *) ((size_t) i->index+1));
+            spec.freq = i->sample_spec.rate;
+            spec.channels = i->sample_spec.channels;
+            spec.format = PulseFormatToSDLFormat(i->sample_spec.format);
+            spec.silence = 0;
+            spec.samples = 0;
+            spec.size = 0;
+            spec.callback = NULL;
+            spec.userdata = NULL;
+
+            SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *) ((size_t) i->index+1));
         }
     }
 }
diff --git a/src/audio/qsa/SDL_qsa_audio.c b/src/audio/qsa/SDL_qsa_audio.c
index b50bfd700..cb95551f9 100644
--- a/src/audio/qsa/SDL_qsa_audio.c
+++ b/src/audio/qsa/SDL_qsa_audio.c
@@ -528,7 +528,11 @@ QSA_DetectDevices(void)
                             devices;
                         status = snd_pcm_close(handle);
                         if (status == EOK) {
-                            SDL_AddAudioDevice(SDL_FALSE, qsa_playback_device[qsa_playback_devices].name, &qsa_playback_device[qsa_playback_devices]);
+                            /* Note that spec is NULL, because we are required to open the device before
+                             * acquiring the mix format, making this information inaccessible at
+                             * enumeration time
+                             */
+                            SDL_AddAudioDevice(SDL_FALSE, qsa_playback_device[qsa_playback_devices].name, NULL, &qsa_playback_device[qsa_playback_devices]);
                             qsa_playback_devices++;
                         }
                     } else {
@@ -586,7 +590,11 @@ QSA_DetectDevices(void)
                             devices;
                         status = snd_pcm_close(handle);
                         if (status == EOK) {
-                            SDL_AddAudioDevice(SDL_TRUE, qsa_capture_device[qsa_capture_devices].name, &qsa_capture_device[qsa_capture_devices]);
+                            /* Note that spec is NULL, because we are required to open the device before
+                             * acquiring the mix format, making this information inaccessible at
+                             * enumeration time
+                             */
+                            SDL_AddAudioDevice(SDL_TRUE, qsa_capture_device[qsa_capture_devices].name, NULL, &qsa_capture_device[qsa_capture_devices]);
                             qsa_capture_devices++;
                         }
                     } else {
diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c
index 8f7f7374c..3f86c801b 100644
--- a/src/audio/wasapi/SDL_wasapi.c
+++ b/src/audio/wasapi/SDL_wasapi.c
@@ -117,10 +117,34 @@ WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
     }
 }
 
+static SDL_AudioFormat
+WaveFormatToSDLFormat(WAVEFORMATEX *waveformat)
+{
+    if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
+        return AUDIO_F32SYS;
+    } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
+        return AUDIO_S16SYS;
+    } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
+        return 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 AUDIO_F32SYS;
+        } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
+            return AUDIO_S16SYS;
+        } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
+            return AUDIO_S32SYS;
+        }
+    }
+    SDL_assert(0 && "Unrecognized wFormatTag!");
+    return 0;
+}
+
 void
-WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid)
+WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid)
 {
     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
@@ -149,7 +173,11 @@ WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid)
     devidlist->next = deviceid_list;
     deviceid_list = devidlist;
 
-    SDL_AddAudioDevice(iscapture, devname, (void *) devid);
+    SDL_zero(spec);
+    spec.channels = fmt->Format.nChannels;
+    spec.freq = fmt->Format.nSamplesPerSec;
+    spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *) fmt);
+    SDL_AddAudioDevice(iscapture, devname, &spec, (void *) devid);
 }
 
 static void
@@ -539,22 +567,7 @@ WASAPI_PrepDevice(_THIS, const SDL_bool updatestream)
     this->spec.channels = (Uint8) waveformat->nChannels;
 
     /* Make sure we have a valid format that we can convert to whatever WASAPI wants. */
-    if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
-        wasapi_format = AUDIO_F32SYS;
-    } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
-        wasapi_format = AUDIO_S16SYS;
-    } else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
-        wasapi_format = 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)) {
-            wasapi_format = AUDIO_F32SYS;
-        } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
-            wasapi_format = AUDIO_S16SYS;
-        } else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof (GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
-            wasapi_format = AUDIO_S32SYS;
-        }
-    }
+    wasapi_format = WaveFormatToSDLFormat(waveformat);
 
     while ((!valid_format) && (test_format)) {
         if (test_format == wasapi_format) {
diff --git a/src/audio/wasapi/SDL_wasapi.h b/src/audio/wasapi/SDL_wasapi.h
index eb6ddc0f6..9ee26157b 100644
--- a/src/audio/wasapi/SDL_wasapi.h
+++ b/src/audio/wasapi/SDL_wasapi.h
@@ -63,7 +63,7 @@ extern SDL_atomic_t WASAPI_DefaultCaptureGeneration;
 int WASAPI_PrepDevice(_THIS, const SDL_bool updatestream);
 void WASAPI_RefDevice(_THIS);
 void WASAPI_UnrefDevice(_THIS);
-void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid);
+void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid);
 void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid);
 
 /* These are functions that are implemented differently for Windows vs WinRT. */
diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c
index 23b48ac3d..173ab00eb 100644
--- a/src/audio/wasapi/SDL_wasapi_win32.c
+++ b/src/audio/wasapi/SDL_wasapi_win32.c
@@ -65,26 +65,31 @@ static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85,{
 static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } };
 static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,{ 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
 static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 };
+static const PROPERTYKEY SDL_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27,{ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, } }, 0 };
 
 
-static char *
-GetWasapiDeviceName(IMMDevice *device)
+static void
+GetWasapiDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt)
 {
     /* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be
        "SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in
        its own UIs, like Volume Control, etc. */
-    char *utf8dev = NULL;
     IPropertyStore *props = NULL;
+    *utf8dev = NULL;
+    SDL_zerop(fmt);
     if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
         PROPVARIANT var;
         PropVariantInit(&var);
         if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) {
-            utf8dev = WIN_StringToUTF8W(var.pwszVal);
+            *utf8dev = WIN_StringToUTF8W(var.pwszVal);
+        }
+        PropVariantClear(&var);
+        if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEngine_DeviceFormat, &var))) {
+            SDL_memcpy(fmt, var.blob.pBlobData, SDL_min(var.blob.cbSize, sizeof(WAVEFORMATEXTENSIBLE)));
         }
         PropVariantClear(&var);
         IPropertyStore_Release(props);
     }
-    return utf8dev;
 }
 
 
@@ -194,9 +199,11 @@ SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *ithis, LPCWS
             if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) {
                 const SDL_bool iscapture = (flow == eCapture);
                 if (dwNewState == DEVICE_STATE_ACTIVE) {
-                    char *utf8dev = GetWasapiDeviceName(device);
+                    char *utf8dev;
+                    WAVEFORMATEXTENSIBLE fmt;
+                    GetWasapiDeviceInfo(device, &utf8dev, &fmt);
                     if (utf8dev) {
-                        WASAPI_AddDevice(iscapture, utf8dev, pwstrDeviceId);
+                        WASAPI_AddDevice(iscapture, utf8dev, &fmt, pwstrDeviceId);
                         SDL_free(utf8dev);
                     }
                 } else {
@@ -352,6 +359,7 @@ typedef struct
 {
     LPWSTR devid;
     char *devname;
+    WAVEFORMATEXTENSIBLE fmt;
 } EndpointItem;
 
 static int sort_endpoints(const void *_a, const void *_b)
@@ -408,7 +416,7 @@ WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture)
         IMMDevice *device = NULL;
         if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) {
             if (SUCCEEDED(IMMDevice_GetId(device, &item->devid))) {
-                item->devname = GetWasapiDeviceName(device);
+                GetWasapiDeviceInfo(device, &item->devname, &item->fmt);
             }
             IMMDevice_Release(device);
         }
@@ -421,7 +429,7 @@ WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture)
     for (i = 0; i < total; i++) {
         EndpointItem *item = items + i;
         if ((item->devid) && (item->devname)) {
-            WASAPI_AddDevice(iscapture, item->devname, item->devid);
+            WASAPI_AddDevice(iscapture, item->devname, &item->fmt, item->devid);
         }
         SDL_free(item->devname);
         CoTaskMemFree(item->devid);
diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp
index c70b865d8..b8c5adc45 100644
--- a/src/audio/wasapi/SDL_wasapi_winrt.cpp
+++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp
@@ -32,6 +32,7 @@
 #include <windows.devices.enumeration.h>
 #include <windows.media.devices.h>
 #include <wrl/implements.h>
+#include <collection.h>
 
 extern "C" {
 #include "../../core/windows/SDL_windows.h"
@@ -52,6 +53,8 @@ using namespace Windows::Media::Devices;
 using namespace Windows::Foundation;
 using namespace Microsoft::WRL;
 
+static Platform::String^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
+
 class SDL_WasapiDeviceEventHandler
 {
 public:
@@ -78,9 +81,16 @@ class SDL_WasapiDeviceEventHandler
 SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
     : iscapture(_iscapture)
     , completed(SDL_CreateSemaphore(0))
-    , watcher(DeviceInformation::CreateWatcher(_iscapture ? DeviceClass::AudioCapture : DeviceClass::AudioRender))
 {
-    if (!watcher || !completed)
+    if (!completed)
+        return;  // uhoh.
+
+    Platform::String^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() :
+                                              MediaDevice::GetAudioRenderSelector();
+    Platform::Collections::Vector<Platform::String^> properties;
+    properties.Append(SDL_PKEY_AudioEngine_DeviceFormat);
+    watcher = DeviceInformation::CreateWatcher(selector, properties.GetView());
+    if (!watcher)
         return;  // uhoh.
 
     // !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan.
@@ -124,7 +134,18 @@ SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher^ sender, DeviceInforma
     SDL_assert(sender == this->watcher);
     char *utf8dev = WIN_StringTo

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