From 2fd94476703002514e339fba876800b605692406 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
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 a2468e8c2c2f3..5ca394dbec14a 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 d58269639d22d..17cfe79f6707b 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 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;