From 2fd94476703002514e339fba876800b605692406 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 29 Mar 2024 00:01:49 -0400 Subject: [PATCH] coreaudio: Make sure device handles are unique. AudioDeviceID is not unique (hardware that can do both capture and output will expose both interfaces off the same AudioDeviceID!). --- src/audio/SDL_audio.c | 3 ++ src/audio/coreaudio/SDL_coreaudio.m | 64 +++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index a2468e8c2..5ca394dbe 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -594,6 +594,9 @@ static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_Audi // The audio backends call this when a new device is plugged in. SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *inspec, void *handle) { + // device handles MUST be unique! If the target reuses the same handle for hardware with both input and output interfaces, wrap it in a pointer you SDL_malloc'd! + SDL_assert(SDL_FindPhysicalAudioDeviceByHandle(handle) == NULL); + const SDL_AudioFormat default_format = iscapture ? DEFAULT_AUDIO_CAPTURE_FORMAT : DEFAULT_AUDIO_OUTPUT_FORMAT; const int default_channels = iscapture ? DEFAULT_AUDIO_CAPTURE_CHANNELS : DEFAULT_AUDIO_OUTPUT_CHANNELS; const int default_freq = iscapture ? DEFAULT_AUDIO_CAPTURE_FREQUENCY : DEFAULT_AUDIO_OUTPUT_FREQUENCY; diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index d58269639..17cfe79f6 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -42,6 +42,28 @@ #endif #ifdef MACOSX_COREAUDIO +// Apparently AudioDeviceID values might not be unique, so we wrap it in an SDL_malloc()'d pointer +// to make it so. Use FindCoreAudioDeviceByHandle to deal with this redirection, if you need to +// map from an AudioDeviceID to a SDL handle. +typedef struct SDLCoreAudioHandle +{ + AudioDeviceID devid; + SDL_bool iscapture; +} SDLCoreAudioHandle; + +static SDL_bool TestCoreAudioDeviceHandleCallback(SDL_AudioDevice *device, void *handle) +{ + const SDLCoreAudioHandle *a = (const SDLCoreAudioHandle *) device->handle; + const SDLCoreAudioHandle *b = (const SDLCoreAudioHandle *) handle; + return (a->devid == b->devid) && (!!a->iscapture == !!b->iscapture); +} + +static SDL_AudioDevice *FindCoreAudioDeviceByHandle(const AudioDeviceID devid, const SDL_bool iscapture) +{ + SDLCoreAudioHandle handle = { devid, iscapture }; + return SDL_FindPhysicalAudioDeviceByCallback(TestCoreAudioDeviceHandleCallback, &handle); +} + static const AudioObjectPropertyAddress devlist_address = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, @@ -70,7 +92,7 @@ static const AudioObjectPropertyAddress alive_address = { static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) { SDL_AudioDevice *device = (SDL_AudioDevice *)data; - SDL_assert(((AudioObjectID)(size_t)device->handle) == devid); + SDL_assert(((const SDLCoreAudioHandle *) device->handle)->devid == devid); UInt32 alive = 1; UInt32 size = sizeof(alive); @@ -95,8 +117,9 @@ static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, co static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device) { - const AudioDeviceID devid = (AudioDeviceID)(size_t)device->handle; - AudioObjectRemovePropertyListener(devid, &alive_address, DeviceAliveNotification, device); + SDLCoreAudioHandle *handle = (SDLCoreAudioHandle *) device->handle; + AudioObjectRemovePropertyListener(handle->devid, &alive_address, DeviceAliveNotification, device); + SDL_free(handle); } // This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes. @@ -117,8 +140,7 @@ static void RefreshPhysicalDevices(void) const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID)); for (UInt32 i = 0; i < total_devices; i++) { - SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devs[i])); - if (device) { + if (FindCoreAudioDeviceByHandle(devs[i], SDL_TRUE) || FindCoreAudioDeviceByHandle(devs[i], SDL_FALSE)) { devs[i] = 0; // The system and SDL both agree it's already here, don't check it again. } } @@ -206,10 +228,16 @@ static void RefreshPhysicalDevices(void) ((iscapture) ? "capture" : "output"), (int)i, name, (int)dev); #endif - - SDL_AudioDevice *device = SDL_AddAudioDevice(iscapture ? SDL_TRUE : SDL_FALSE, name, &spec, (void *)((size_t)dev)); - if (device) { - AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device); + SDLCoreAudioHandle *newhandle = (SDLCoreAudioHandle *) SDL_calloc(1, sizeof (*newhandle)); + if (newhandle) { + newhandle->devid = dev; + newhandle->iscapture = iscapture ? SDL_TRUE : SDL_FALSE; + SDL_AudioDevice *device = SDL_AddAudioDevice(newhandle->iscapture, name, &spec, newhandle); + if (device) { + AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device); + } else { + SDL_free(newhandle); + } } } SDL_free(name); // SDL_AddAudioDevice() would have copied the string. @@ -226,12 +254,12 @@ static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 nu return noErr; } -static OSStatus DefaultAudioDeviceChangedNotification(AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) +static OSStatus DefaultAudioDeviceChangedNotification(const SDL_bool iscapture, AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) { AudioDeviceID devid; UInt32 size = sizeof(devid); if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { - SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid))); + SDL_DefaultAudioDeviceChanged(FindCoreAudioDeviceByHandle(devid, iscapture)); } return noErr; } @@ -242,7 +270,7 @@ static OSStatus DefaultOutputDeviceChangedNotification(AudioObjectID inObjectID, SDL_Log("COREAUDIO: default output device changed!"); #endif SDL_assert(inNumberAddresses == 1); - return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses); + return DefaultAudioDeviceChangedNotification(SDL_FALSE, inObjectID, inAddresses); } static OSStatus DefaultInputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) @@ -251,7 +279,7 @@ static OSStatus DefaultInputDeviceChangedNotification(AudioObjectID inObjectID, SDL_Log("COREAUDIO: default input device changed!"); #endif SDL_assert(inNumberAddresses == 1); - return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses); + return DefaultAudioDeviceChangedNotification(SDL_TRUE, inObjectID, inAddresses); } static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) @@ -266,7 +294,7 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD size = sizeof(AudioDeviceID); if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_output_device_address, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, SDL_FALSE); if (device) { *default_output = device; } @@ -275,7 +303,7 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD size = sizeof(AudioDeviceID); if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_input_device_address, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, SDL_TRUE); if (device) { *default_capture = device; } @@ -631,10 +659,10 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) #ifdef MACOSX_COREAUDIO static int PrepareDevice(SDL_AudioDevice *device) { - void *handle = device->handle; - SDL_assert(handle != NULL); // this meant "system default" in SDL2, but doesn't anymore + SDL_assert(device->handle != NULL); // this meant "system default" in SDL2, but doesn't anymore - const AudioDeviceID devid = (AudioDeviceID)((size_t)handle); + const SDLCoreAudioHandle *handle = (const SDLCoreAudioHandle *) device->handle; + const AudioDeviceID devid = handle->devid; OSStatus result = noErr; UInt32 size = 0;