Skip to content

Commit

Permalink
coreaudio: Make sure device handles are unique.
Browse files Browse the repository at this point in the history
AudioDeviceID is not unique (hardware that can do both capture and output
will expose both interfaces off the same AudioDeviceID!).
  • Loading branch information
icculus authored and slouken committed Mar 29, 2024
1 parent 87235e0 commit 2fd9447
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 18 deletions.
3 changes: 3 additions & 0 deletions src/audio/SDL_audio.c
Expand Up @@ -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;
Expand Down
64 changes: 46 additions & 18 deletions src/audio/coreaudio/SDL_coreaudio.m
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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.
Expand All @@ -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.
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 2fd9447

Please sign in to comment.