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!).
main
Ryan C. Gordon 2024-03-29 00:01:49 -04:00 committed by Sam Lantinga
parent 87235e0f6d
commit 2fd9447670
2 changed files with 49 additions and 18 deletions

View File

@ -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;

View File

@ -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;