From 67e8522d31b875874ffd24086678ee6c861ff91c Mon Sep 17 00:00:00 2001 From: Ethan Lee 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, ¤t_audio.inputDevices, ¤t_audio.inputDeviceCount); + return add_audio_device(name, spec, handle, ¤t_audio.inputDevices, ¤t_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, ¤t_audio.outputDevices, ¤t_audio.outputDeviceCount); + return add_audio_device(name, spec, handle, ¤t_audio.outputDevices, ¤t_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 @@ static const AudioObjectPropertyAddress devlist_address = { 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 @@ add_to_internal_dev_list(const int iscapture, AudioDeviceID devId) } 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 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata) 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 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata) 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 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata) ((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 @@ COREAUDIO_DetectDevices(void) } 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 @@ build_device_change_list(const char *name, const int iscapture, AudioDeviceID de } 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 #include #include +#include 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 @@ private: 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 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_StringToUTF8(info->Name->Data()); if (utf8dev) { - WASAPI_AddDevice(this->iscapture, utf8dev, info->Id->Data()); + WAVEFORMATEXTENSIBLE fmt; + Platform::Object^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat); + if (obj) { + IPropertyValue^ property = (IPropertyValue^) obj; + Platform::Array^ data; + property->GetUInt8Array(&data); + SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE))); + } else { + SDL_zero(fmt); + } + + WASAPI_AddDevice(this->iscapture, utf8dev, &fmt, info->Id->Data()); SDL_free(utf8dev); } } diff --git a/src/audio/winmm/SDL_winmm.c b/src/audio/winmm/SDL_winmm.c index 791399715..5d221f665 100644 --- a/src/audio/winmm/SDL_winmm.c +++ b/src/audio/winmm/SDL_winmm.c @@ -75,12 +75,19 @@ static void DetectWave##typ##Devs(void) { \ const UINT iscapture = iscap ? 1 : 0; \ const UINT devcount = wave##typ##GetNumDevs(); \ capstyp##2W caps; \ + SDL_AudioSpec spec; \ UINT i; \ + SDL_zero(spec); \ for (i = 0; i < devcount; i++) { \ if (wave##typ##GetDevCaps(i,(LP##capstyp##W)&caps,sizeof(caps))==MMSYSERR_NOERROR) { \ char *name = WIN_LookupAudioDeviceName(caps.szPname,&caps.NameGuid); \ if (name != NULL) { \ - SDL_AddAudioDevice((int) iscapture, name, (void *) ((size_t) i+1)); \ + /* Note that freq/format are not filled in, as this information \ + * is not provided by the caps struct! At best, we get possible \ + * sample formats, but not an _active_ format. \ + */ \ + spec.channels = caps.wChannels; \ + SDL_AddAudioDevice((int) iscapture, name, &spec, (void *) ((size_t) i+1)); \ SDL_free(name); \ } \ } \ diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 1d6928171..085156e8b 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -808,3 +808,4 @@ #define SDL_isprint SDL_isprint_REAL #define SDL_isgraph SDL_isgraph_REAL #define SDL_AndroidShowToast SDL_AndroidShowToast_REAL +#define SDL_GetAudioDeviceSpec SDL_GetAudioDeviceSpec_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index ea9153e93..1cd285393 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -873,3 +873,4 @@ SDL_DYNAPI_PROC(int,SDL_isgraph,(int a),(a),return) #ifdef __ANDROID__ SDL_DYNAPI_PROC(int,SDL_AndroidShowToast,(const char *a, int b, int c, int d, int e),(a,b,c,d,e),return) #endif +SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL_AudioSpec *c),(a,b,c),return)