Add SDL_GetAudioDeviceSpec.

This API is supported by pipewire, pulseaudio, coreaudio, wasapi, and disk.
This commit is contained in:
Ethan Lee 2021-02-27 17:37:25 -05:00 committed by Sam Lantinga
parent 00fabdde18
commit 67e8522d31
19 changed files with 256 additions and 67 deletions

View file

@ -359,6 +359,25 @@ extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture);
extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index,
int iscapture); 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 * Open a specific audio device. Passing in a device name of NULL requests

View file

@ -223,9 +223,9 @@ SDL_AudioDetectDevices_Default(void)
SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice); SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice);
SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport); 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) { 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... */ /* device hotplug support... */
static int 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; int retval = -1;
SDL_AudioDeviceItem *item; SDL_AudioDeviceItem *item;
@ -400,6 +400,11 @@ add_audio_device(const char *name, void *handle, SDL_AudioDeviceItem **devices,
item->dupenum = 0; item->dupenum = 0;
item->name = item->original_name; item->name = item->original_name;
if (spec != NULL) {
SDL_memcpy(&item->spec, spec, sizeof(SDL_AudioSpec));
} else {
SDL_zero(item->spec);
}
item->handle = handle; item->handle = handle;
SDL_LockMutex(current_audio.detectionLock); SDL_LockMutex(current_audio.detectionLock);
@ -437,16 +442,16 @@ add_audio_device(const char *name, void *handle, SDL_AudioDeviceItem **devices,
} }
static SDL_INLINE int 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); 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 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 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. */ /* The audio backends call this when a new device is plugged in. */
void 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) { if (device_index != -1) {
/* Post the event, if desired */ /* Post the event, if desired */
if (SDL_GetEventState(SDL_AUDIODEVICEADDED) == SDL_ENABLE) { 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 static void
close_audio_device(SDL_AudioDevice * device) close_audio_device(SDL_AudioDevice * device)
{ {

View file

@ -59,7 +59,13 @@ test_device(const int iscapture, const char *fname, int flags, int (*test) (int
static size_t dummyhandle = 0; static size_t dummyhandle = 0;
dummyhandle++; dummyhandle++;
SDL_assert(dummyhandle != 0); 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);
} }
} }
} }

View file

@ -39,7 +39,7 @@ typedef struct SDL_AudioDevice SDL_AudioDevice;
/* Audio targets should call this as devices are added to the system (such as /* 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 a USB headset being plugged in), and should also be called for
for every device found during DetectDevices(). */ 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 /* Audio targets should call this as devices are removed, so SDL can update
its list of available devices. */ its list of available devices. */
@ -99,6 +99,7 @@ typedef struct SDL_AudioDeviceItem
void *handle; void *handle;
char *name; char *name;
char *original_name; char *original_name;
SDL_AudioSpec spec;
int dupenum; int dupenum;
struct SDL_AudioDeviceItem *next; struct SDL_AudioDeviceItem *next;
} SDL_AudioDeviceItem; } SDL_AudioDeviceItem;

View file

@ -765,7 +765,11 @@ add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSee
return; 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) if (hint)
free(desc); free(desc);
dev->name = handle; dev->name = handle;

View file

@ -56,7 +56,7 @@ static const AudioObjectPropertyAddress devlist_address = {
kAudioObjectPropertyElementMaster 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 typedef struct AudioDeviceList
{ {
@ -88,10 +88,10 @@ add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
} }
static void 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)) { 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; AudioBufferList *buflist = NULL;
int usable = 0; int usable = 0;
CFIndex len = 0; CFIndex len = 0;
double sampleRate = 0;
SDL_AudioSpec spec;
const AudioObjectPropertyAddress addr = { const AudioObjectPropertyAddress addr = {
kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamConfiguration,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster kAudioObjectPropertyElementMaster
}; };
const AudioObjectPropertyAddress nameaddr = { const AudioObjectPropertyAddress nameaddr = {
kAudioObjectPropertyName, kAudioObjectPropertyName,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster kAudioObjectPropertyElementMaster
}; };
const AudioObjectPropertyAddress freqaddr = {
kAudioDevicePropertyNominalSampleRate,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
if (result != noErr) if (result != noErr)
@ -149,21 +155,24 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
&size, buflist); &size, buflist);
SDL_zero(spec);
if (result == noErr) { if (result == noErr) {
UInt32 j; UInt32 j;
for (j = 0; j < buflist->mNumberBuffers; j++) { for (j = 0; j < buflist->mNumberBuffers; j++) {
if (buflist->mBuffers[j].mNumberChannels > 0) { spec.channels += buflist->mBuffers[j].mNumberChannels;
usable = 1;
break;
}
} }
} }
SDL_free(buflist); SDL_free(buflist);
if (!usable) if (spec.channels == 0)
continue; continue;
size = sizeof (sampleRate);
result = AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate);
if (result == noErr) {
spec.freq = (int) sampleRate;
}
size = sizeof (CFStringRef); size = sizeof (CFStringRef);
result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr); result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
@ -197,7 +206,7 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
((iscapture) ? "capture" : "output"), ((iscapture) ? "capture" : "output"),
(int) i, ptr, (int) dev); (int) i, ptr, (int) dev);
#endif #endif
addfn(ptr, iscapture, dev, addfndata); addfn(ptr, &spec, iscapture, dev, addfndata);
} }
SDL_free(ptr); /* addfn() would have copied the string. */ SDL_free(ptr); /* addfn() would have copied the string. */
} }
@ -223,7 +232,7 @@ COREAUDIO_DetectDevices(void)
} }
static 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 **list = (AudioDeviceList **) data;
AudioDeviceList *item; 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. */ 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 static void

View file

@ -163,7 +163,12 @@ FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data)
if (str != NULL) { if (str != NULL) {
LPGUID cpyguid = (LPGUID) SDL_malloc(sizeof (GUID)); LPGUID cpyguid = (LPGUID) SDL_malloc(sizeof (GUID));
SDL_memcpy(cpyguid, guid, 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. */ SDL_free(str); /* addfn() makes a copy of this string. */
} }
} }

View file

@ -173,8 +173,8 @@ DISKAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
static void static void
DISKAUDIO_DetectDevices(void) DISKAUDIO_DetectDevices(void)
{ {
SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, (void *) 0x1); SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *) 0x1);
SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, (void *) 0x2); SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *) 0x2);
} }
static int static int

View file

@ -160,9 +160,9 @@ static void OS2_DetectDevices(void)
} }
ulHandle++; ulHandle++;
SDL_AddAudioDevice(0, stLogDevice.szProductInfo, (void *)(ulHandle)); SDL_AddAudioDevice(0, stLogDevice.szProductInfo, NULL, (void *)(ulHandle));
ulHandle++; ulHandle++;
SDL_AddAudioDevice(1, stLogDevice.szProductInfo, (void *)(ulHandle)); SDL_AddAudioDevice(1, stLogDevice.szProductInfo, NULL, (void *)(ulHandle));
} }
} }

View file

@ -266,7 +266,7 @@ io_list_check_add(struct io_node *node)
spa_list_append(&hotplug_io_list, &node->link); spa_list_append(&hotplug_io_list, &node->link);
if (SDL_AtomicGet(&hotplug_events_enabled)) { 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: dup_found:
@ -768,7 +768,7 @@ PIPEWIRE_DetectDevices()
io_list_sort(); io_list_sort();
spa_list_for_each (io, &hotplug_io_list, link) { 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); SDL_AtomicSet(&hotplug_events_enabled, 1);

View file

@ -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. */ /* 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. */ /* This is called when PulseAudio adds an output ("sink") device. */
static void static void
SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data)
{ {
SDL_AudioSpec spec;
if (i) { 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 static void
SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data)
{ {
SDL_AudioSpec spec;
if (i) { if (i) {
/* Skip "monitor" sources. These are just output from other sinks. */ /* Skip "monitor" sources. These are just output from other sinks. */
if (i->monitor_of_sink == PA_INVALID_INDEX) { 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));
} }
} }
} }

View file

@ -528,7 +528,11 @@ QSA_DetectDevices(void)
devices; devices;
status = snd_pcm_close(handle); status = snd_pcm_close(handle);
if (status == EOK) { 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++; qsa_playback_devices++;
} }
} else { } else {
@ -586,7 +590,11 @@ QSA_DetectDevices(void)
devices; devices;
status = snd_pcm_close(handle); status = snd_pcm_close(handle);
if (status == EOK) { 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++; qsa_capture_devices++;
} }
} else { } else {

View file

@ -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 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; DevIdList *devidlist;
SDL_AudioSpec spec;
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever). /* 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 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; devidlist->next = deviceid_list;
deviceid_list = devidlist; 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 static void
@ -539,22 +567,7 @@ WASAPI_PrepDevice(_THIS, const SDL_bool updatestream)
this->spec.channels = (Uint8) waveformat->nChannels; this->spec.channels = (Uint8) waveformat->nChannels;
/* Make sure we have a valid format that we can convert to whatever WASAPI wants. */ /* 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 = WaveFormatToSDLFormat(waveformat);
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;
}
}
while ((!valid_format) && (test_format)) { while ((!valid_format) && (test_format)) {
if (test_format == wasapi_format) { if (test_format == wasapi_format) {

View file

@ -63,7 +63,7 @@ extern SDL_atomic_t WASAPI_DefaultCaptureGeneration;
int WASAPI_PrepDevice(_THIS, const SDL_bool updatestream); int WASAPI_PrepDevice(_THIS, const SDL_bool updatestream);
void WASAPI_RefDevice(_THIS); void WASAPI_RefDevice(_THIS);
void WASAPI_UnrefDevice(_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); void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid);
/* These are functions that are implemented differently for Windows vs WinRT. */ /* These are functions that are implemented differently for Windows vs WinRT. */

View file

@ -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_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 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_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 * static void
GetWasapiDeviceName(IMMDevice *device) GetWasapiDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt)
{ {
/* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be /* 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 "SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in
its own UIs, like Volume Control, etc. */ its own UIs, like Volume Control, etc. */
char *utf8dev = NULL;
IPropertyStore *props = NULL; IPropertyStore *props = NULL;
*utf8dev = NULL;
SDL_zerop(fmt);
if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) { if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
PROPVARIANT var; PROPVARIANT var;
PropVariantInit(&var); PropVariantInit(&var);
if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &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); PropVariantClear(&var);
IPropertyStore_Release(props); IPropertyStore_Release(props);
} }
return utf8dev;
} }
@ -194,9 +199,11 @@ SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *ithis, LPCWS
if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) { if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) {
const SDL_bool iscapture = (flow == eCapture); const SDL_bool iscapture = (flow == eCapture);
if (dwNewState == DEVICE_STATE_ACTIVE) { if (dwNewState == DEVICE_STATE_ACTIVE) {
char *utf8dev = GetWasapiDeviceName(device); char *utf8dev;
WAVEFORMATEXTENSIBLE fmt;
GetWasapiDeviceInfo(device, &utf8dev, &fmt);
if (utf8dev) { if (utf8dev) {
WASAPI_AddDevice(iscapture, utf8dev, pwstrDeviceId); WASAPI_AddDevice(iscapture, utf8dev, &fmt, pwstrDeviceId);
SDL_free(utf8dev); SDL_free(utf8dev);
} }
} else { } else {
@ -352,6 +359,7 @@ typedef struct
{ {
LPWSTR devid; LPWSTR devid;
char *devname; char *devname;
WAVEFORMATEXTENSIBLE fmt;
} EndpointItem; } EndpointItem;
static int sort_endpoints(const void *_a, const void *_b) static int sort_endpoints(const void *_a, const void *_b)
@ -408,7 +416,7 @@ WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture)
IMMDevice *device = NULL; IMMDevice *device = NULL;
if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) { if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) {
if (SUCCEEDED(IMMDevice_GetId(device, &item->devid))) { if (SUCCEEDED(IMMDevice_GetId(device, &item->devid))) {
item->devname = GetWasapiDeviceName(device); GetWasapiDeviceInfo(device, &item->devname, &item->fmt);
} }
IMMDevice_Release(device); IMMDevice_Release(device);
} }
@ -421,7 +429,7 @@ WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture)
for (i = 0; i < total; i++) { for (i = 0; i < total; i++) {
EndpointItem *item = items + i; EndpointItem *item = items + i;
if ((item->devid) && (item->devname)) { 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); SDL_free(item->devname);
CoTaskMemFree(item->devid); CoTaskMemFree(item->devid);

View file

@ -32,6 +32,7 @@
#include <windows.devices.enumeration.h> #include <windows.devices.enumeration.h>
#include <windows.media.devices.h> #include <windows.media.devices.h>
#include <wrl/implements.h> #include <wrl/implements.h>
#include <collection.h>
extern "C" { extern "C" {
#include "../../core/windows/SDL_windows.h" #include "../../core/windows/SDL_windows.h"
@ -52,6 +53,8 @@ using namespace Windows::Media::Devices;
using namespace Windows::Foundation; using namespace Windows::Foundation;
using namespace Microsoft::WRL; using namespace Microsoft::WRL;
static Platform::String^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
class SDL_WasapiDeviceEventHandler class SDL_WasapiDeviceEventHandler
{ {
public: public:
@ -78,9 +81,16 @@ private:
SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture) SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
: iscapture(_iscapture) : iscapture(_iscapture)
, completed(SDL_CreateSemaphore(0)) , 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. return; // uhoh.
// !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan. // !!! 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); SDL_assert(sender == this->watcher);
char *utf8dev = WIN_StringToUTF8(info->Name->Data()); char *utf8dev = WIN_StringToUTF8(info->Name->Data());
if (utf8dev) { 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<unsigned char>^ 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); SDL_free(utf8dev);
} }
} }

View file

@ -75,12 +75,19 @@ static void DetectWave##typ##Devs(void) { \
const UINT iscapture = iscap ? 1 : 0; \ const UINT iscapture = iscap ? 1 : 0; \
const UINT devcount = wave##typ##GetNumDevs(); \ const UINT devcount = wave##typ##GetNumDevs(); \
capstyp##2W caps; \ capstyp##2W caps; \
SDL_AudioSpec spec; \
UINT i; \ UINT i; \
SDL_zero(spec); \
for (i = 0; i < devcount; i++) { \ for (i = 0; i < devcount; i++) { \
if (wave##typ##GetDevCaps(i,(LP##capstyp##W)&caps,sizeof(caps))==MMSYSERR_NOERROR) { \ if (wave##typ##GetDevCaps(i,(LP##capstyp##W)&caps,sizeof(caps))==MMSYSERR_NOERROR) { \
char *name = WIN_LookupAudioDeviceName(caps.szPname,&caps.NameGuid); \ char *name = WIN_LookupAudioDeviceName(caps.szPname,&caps.NameGuid); \
if (name != NULL) { \ 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); \ SDL_free(name); \
} \ } \
} \ } \

View file

@ -808,3 +808,4 @@
#define SDL_isprint SDL_isprint_REAL #define SDL_isprint SDL_isprint_REAL
#define SDL_isgraph SDL_isgraph_REAL #define SDL_isgraph SDL_isgraph_REAL
#define SDL_AndroidShowToast SDL_AndroidShowToast_REAL #define SDL_AndroidShowToast SDL_AndroidShowToast_REAL
#define SDL_GetAudioDeviceSpec SDL_GetAudioDeviceSpec_REAL

View file

@ -873,3 +873,4 @@ SDL_DYNAPI_PROC(int,SDL_isgraph,(int a),(a),return)
#ifdef __ANDROID__ #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) SDL_DYNAPI_PROC(int,SDL_AndroidShowToast,(const char *a, int b, int c, int d, int e),(a,b,c,d,e),return)
#endif #endif
SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL_AudioSpec *c),(a,b,c),return)