From c653e57768aa78ac93d58e380b98d1706dd0d355 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 22:00:58 -0400 Subject: [PATCH] coreaudio: rewritten for SDL3 audio redesign! --- src/audio/coreaudio/SDL_coreaudio.h | 5 +- src/audio/coreaudio/SDL_coreaudio.m | 1114 +++++++++------------------ 2 files changed, 383 insertions(+), 736 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h index 989ed61d2..4b7f81bf0 100644 --- a/src/audio/coreaudio/SDL_coreaudio.h +++ b/src/audio/coreaudio/SDL_coreaudio.h @@ -53,15 +53,12 @@ struct SDL_PrivateAudioData AudioQueueRef audioQueue; int numAudioBuffers; AudioQueueBufferRef *audioBuffer; - void *buffer; - UInt32 bufferOffset; - UInt32 bufferSize; + AudioQueueBufferRef current_buffer; AudioStreamBasicDescription strdesc; SDL_Semaphore *ready_semaphore; char *thread_error; #ifdef MACOSX_COREAUDIO AudioDeviceID deviceID; - SDL_AtomicInt device_change_flag; #else SDL_bool interrupted; CFTypeRef interruption_listener; diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index 20ac3bf5d..bd05c1a10 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -22,28 +22,24 @@ #ifdef SDL_AUDIO_DRIVER_COREAUDIO -/* !!! FIXME: clean out some of the macro salsa in here. */ - #include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #include "SDL_coreaudio.h" #include "../../thread/SDL_systhread.h" -#define DEBUG_COREAUDIO 0 +#define DEBUG_COREAUDIO 1 #if DEBUG_COREAUDIO -#define CHECK_RESULT(msg) \ - if (result != noErr) { \ - printf("COREAUDIO: Got error %d from '%s'!\n", (int)result, msg); \ - SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ - return 0; \ - } + #define CHECK_RESULT(msg) \ + if (result != noErr) { \ + SDL_Log("COREAUDIO: Got error %d from '%s'!\n", (int)result, msg); \ + return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ + } #else -#define CHECK_RESULT(msg) \ - if (result != noErr) { \ - SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ - return 0; \ - } + #define CHECK_RESULT(msg) \ + if (result != noErr) { \ + return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ + } #endif #ifdef MACOSX_COREAUDIO @@ -53,78 +49,84 @@ static const AudioObjectPropertyAddress devlist_address = { kAudioObjectPropertyElementMain }; -typedef void (*addDevFn)(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data); +static const AudioObjectPropertyAddress default_output_device_address = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; -typedef struct AudioDeviceList +static const AudioObjectPropertyAddress default_input_device_address = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; + +static const AudioObjectPropertyAddress alive_address = { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; + + +static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) { - AudioDeviceID devid; - SDL_bool alive; - struct AudioDeviceList *next; -} AudioDeviceList; + SDL_AudioDevice *device = (SDL_AudioDevice *)data; + SDL_assert(((AudioObjectID)(size_t)device->handle) == devid); -static AudioDeviceList *output_devs = NULL; -static AudioDeviceList *capture_devs = NULL; + UInt32 alive = 1; + UInt32 size = sizeof(alive); + const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive); -static SDL_bool add_to_internal_dev_list(const int iscapture, AudioDeviceID devId) -{ - AudioDeviceList *item = (AudioDeviceList *)SDL_malloc(sizeof(AudioDeviceList)); - if (item == NULL) { - return SDL_FALSE; - } - item->devid = devId; - item->alive = SDL_TRUE; - item->next = iscapture ? capture_devs : output_devs; - if (iscapture) { - capture_devs = item; - } else { - output_devs = item; + SDL_bool dead = SDL_FALSE; + if (error == kAudioHardwareBadDeviceError) { + dead = SDL_TRUE; /* device was unplugged. */ + } else if ((error == kAudioHardwareNoError) && (!alive)) { + dead = SDL_TRUE; /* device died in some other way. */ } - return SDL_TRUE; + if (dead) { + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: device '%s' is lost!", device->name); + #endif + SDL_AudioDeviceDisconnected(device); + } + + return noErr; } -static void addToDevList(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data) +static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device) { - if (add_to_internal_dev_list(iscapture, devId)) { - SDL_AddAudioDevice(iscapture, name, spec, (void *)((size_t)devId)); - } + const AudioDeviceID devid = (AudioDeviceID)(size_t)device->handle; + AudioObjectRemovePropertyListener(devid, &alive_address, DeviceAliveNotification, device); } -static void build_device_list(int iscapture, addDevFn addfn, void *addfndata) +// This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes. +static void RefreshPhysicalDevices(void) { - OSStatus result = noErr; UInt32 size = 0; AudioDeviceID *devs = NULL; - UInt32 i = 0; - UInt32 max = 0; + SDL_bool isstack; - result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, - &devlist_address, 0, NULL, &size); - if (result != kAudioHardwareNoError) { + if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size) != kAudioHardwareNoError) { + return; + } else if ((devs = (AudioDeviceID *) SDL_small_alloc(Uint8, size, &isstack)) == NULL) { + return; + } else if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size, devs) != kAudioHardwareNoError) { + SDL_small_free(devs, isstack); return; } - devs = (AudioDeviceID *)alloca(size); - if (devs == NULL) { - return; + const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID)); + for (UInt32 i = 0; i < total_devices; i++) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devs[i])); + if (device) { + SDL_UnlockMutex(device->lock); + devs[i] = 0; // The system and SDL both agree it's already here, don't check it again. + } } - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &devlist_address, 0, NULL, &size, devs); - if (result != kAudioHardwareNoError) { - return; - } - - max = size / sizeof(AudioDeviceID); - for (i = 0; i < max; i++) { - CFStringRef cfstr = NULL; - char *ptr = NULL; - AudioDeviceID dev = devs[i]; - AudioBufferList *buflist = NULL; - int usable = 0; - CFIndex len = 0; - double sampleRate = 0; - SDL_AudioSpec spec; + // any non-zero items remaining in `devs` are new devices to be added. + for (int iscapture = 0; iscapture < 2; iscapture++) { const AudioObjectPropertyAddress addr = { kAudioDevicePropertyStreamConfiguration, iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, @@ -141,165 +143,167 @@ static void build_device_list(int iscapture, addDevFn addfn, void *addfndata) kAudioObjectPropertyElementMain }; - result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); - if (result != noErr) { - continue; - } - - buflist = (AudioBufferList *)SDL_malloc(size); - if (buflist == NULL) { - continue; - } - - result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, - &size, buflist); - - SDL_zero(spec); - if (result == noErr) { - UInt32 j; - for (j = 0; j < buflist->mNumberBuffers; j++) { - spec.channels += buflist->mBuffers[j].mNumberChannels; + for (UInt32 i = 0; i < total_devices; i++) { + const AudioDeviceID dev = devs[i]; + if (!dev) { + continue; // already added. } - } - SDL_free(buflist); + AudioBufferList *buflist = NULL; + double sampleRate = 0; - if (spec.channels == 0) { - continue; - } - - size = sizeof(sampleRate); - result = AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate); - if (result == noErr) { - spec.freq = (int)sampleRate; - } - - size = sizeof(CFStringRef); - result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr); - if (result != kAudioHardwareNoError) { - continue; - } - - len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), - kCFStringEncodingUTF8); - - ptr = (char *)SDL_malloc(len + 1); - usable = ((ptr != NULL) && - (CFStringGetCString(cfstr, ptr, len + 1, kCFStringEncodingUTF8))); - - CFRelease(cfstr); - - if (usable) { - len = SDL_strlen(ptr); - /* Some devices have whitespace at the end...trim it. */ - while ((len > 0) && (ptr[len - 1] == ' ')) { - len--; + if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) { + continue; + } else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) { + continue; } - usable = (len > 0); - } - if (usable) { - ptr[len] = '\0'; + OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist); -#if DEBUG_COREAUDIO - printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", - ((iscapture) ? "capture" : "output"), - (int)i, ptr, (int)dev); -#endif - addfn(ptr, &spec, iscapture, dev, addfndata); - } - SDL_free(ptr); /* addfn() would have copied the string. */ - } -} - -static void free_audio_device_list(AudioDeviceList **list) -{ - AudioDeviceList *item = *list; - while (item) { - AudioDeviceList *next = item->next; - SDL_free(item); - item = next; - } - *list = NULL; -} - -static void COREAUDIO_DetectDevices(void) -{ - build_device_list(SDL_TRUE, addToDevList, NULL); - build_device_list(SDL_FALSE, addToDevList, NULL); -} - -static void build_device_change_list(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data) -{ - AudioDeviceList **list = (AudioDeviceList **)data; - AudioDeviceList *item; - for (item = *list; item != NULL; item = item->next) { - if (item->devid == devId) { - item->alive = SDL_TRUE; - return; - } - } - - add_to_internal_dev_list(iscapture, devId); /* new device, add it. */ - SDL_AddAudioDevice(iscapture, name, spec, (void *)((size_t)devId)); -} - -static void reprocess_device_list(const int iscapture, AudioDeviceList **list) -{ - AudioDeviceList *item; - AudioDeviceList *prev = NULL; - for (item = *list; item != NULL; item = item->next) { - item->alive = SDL_FALSE; - } - - build_device_list(iscapture, build_device_change_list, list); - - /* free items in the list that aren't still alive. */ - item = *list; - while (item != NULL) { - AudioDeviceList *next = item->next; - if (item->alive) { - prev = item; - } else { - SDL_RemoveAudioDevice(iscapture, (void *)((size_t)item->devid)); - if (prev) { - prev->next = item->next; - } else { - *list = item->next; + SDL_AudioSpec spec; + SDL_zero(spec); + if (result == noErr) { + for (Uint32 j = 0; j < buflist->mNumberBuffers; j++) { + spec.channels += buflist->mBuffers[j].mNumberChannels; + } } - SDL_free(item); + + SDL_free(buflist); + + if (spec.channels == 0) { + continue; + } + + size = sizeof(sampleRate); + if (AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate) == noErr) { + spec.freq = (int)sampleRate; + } + + CFStringRef cfstr = NULL; + size = sizeof(CFStringRef); + if (AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr) != kAudioHardwareNoError) { + continue; + } + + CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8); + char *name = (char *)SDL_malloc(len + 1); + SDL_bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))) ? SDL_TRUE : SDL_FALSE; + + CFRelease(cfstr); + + if (usable) { + // Some devices have whitespace at the end...trim it. + len = (CFIndex) SDL_strlen(name); + while ((len > 0) && (name[len - 1] == ' ')) { + len--; + } + usable = (len > 0); + } + + if (usable) { + name[len] = '\0'; + + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", + ((iscapture) ? "capture" : "output"), + (int)i, name, (int)dev); + #endif + + devs[i] = 0; // don't bother checking this one on the next iscapture iteration of the loop + + SDL_AudioDevice *device = SDL_AddAudioDevice(iscapture ? SDL_TRUE : SDL_FALSE, name, &spec, (void *)((size_t)dev)); + if (device) { + AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device); + } + } + SDL_free(name); // SDL_AddAudioDevice() would have copied the string. } - item = next; } + + SDL_small_free(devs, isstack); } -/* this is called when the system's list of available audio devices changes. */ -static OSStatus device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) +// this is called when the system's list of available audio devices changes. +static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) { - reprocess_device_list(SDL_TRUE, &capture_devs); - reprocess_device_list(SDL_FALSE, &output_devs); - return 0; + RefreshPhysicalDevices(); + return noErr; } -#endif -static int open_playback_devices; -static int open_capture_devices; -static int num_open_devices; -static SDL_AudioDevice **open_devices; - -#ifndef MACOSX_COREAUDIO - -static BOOL session_active = NO; - -static void pause_audio_devices(void) +static OSStatus DefaultAudioDeviceChangedNotification(AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) { - int i; + AudioDeviceID devid; + Uint32 size = sizeof(devid); + if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + if (device) { + SDL_UnlockMutex(device->lock); + SDL_DefaultAudioDeviceChanged(device); + } + } + return noErr; +} +static OSStatus DefaultOutputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) +{ + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: default output device changed!"); + #endif + SDL_assert(inNumberAddresses == 1); + return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses); +} + +static OSStatus DefaultInputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) +{ + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: default input device changed!"); + #endif + SDL_assert(inNumberAddresses == 1); + return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses); +} + +static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) +{ + RefreshPhysicalDevices(); + + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); + + /* Get the Device ID */ + UInt32 size; + AudioDeviceID devid; + + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_output_device_address, 0, NULL, &size, &devid) == noErr) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + if (device) { + SDL_UnlockMutex(device->lock); + *default_output = device; + } + } + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_output_device_address, DefaultOutputDeviceChangedNotification, NULL); + + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_input_device_address, 0, NULL, &size, &devid) == noErr) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + if (device) { + SDL_UnlockMutex(device->lock); + *default_capture = device; + } + } + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_input_device_address, DefaultInputDeviceChangedNotification, NULL); +} + +#else // iOS-specific section follows. + +static SDL_bool session_active = SDL_FALSE; + +static void PauseAudioDevices(void) // !!! FIXME: this needs to be updated, and we need a method to access SDL_audio.c's device lists. +{ if (!open_devices) { return; } - for (i = 0; i < num_open_devices; ++i) { + for (int i = 0; i < num_open_devices; ++i) { SDL_AudioDevice *device = open_devices[i]; if (device->hidden->audioQueue && !device->hidden->interrupted) { AudioQueuePause(device->hidden->audioQueue); @@ -307,15 +311,13 @@ static void pause_audio_devices(void) } } -static void resume_audio_devices(void) +static void ResumeAudioDevices(void) // !!! FIXME: this needs to be updated, and we need a method to access SDL_audio.c's device lists. { - int i; - if (!open_devices) { return; } - for (i = 0; i < num_open_devices; ++i) { + for (int i = 0; i < num_open_devices; ++i) { SDL_AudioDevice *device = open_devices[i]; if (device->hidden->audioQueue && !device->hidden->interrupted) { AudioQueueStart(device->hidden->audioQueue, NULL); @@ -323,7 +325,7 @@ static void resume_audio_devices(void) } } -static void interruption_begin(SDL_AudioDevice *device) +static void InterruptionBegin(SDL_AudioDevice *device) { if (device != NULL && device->hidden->audioQueue != NULL) { device->hidden->interrupted = SDL_TRUE; @@ -331,7 +333,7 @@ static void interruption_begin(SDL_AudioDevice *device) } } -static void interruption_end(SDL_AudioDevice *device) +static void InterruptionEnd(SDL_AudioDevice *device) { if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { device->hidden->interrupted = SDL_FALSE; @@ -351,9 +353,9 @@ static void interruption_end(SDL_AudioDevice *device) @synchronized(self) { NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { - interruption_begin(self.device); + InterruptionBegin(self.device); } else { - interruption_end(self.device); + InterruptionEnd(self.device); } } } @@ -361,13 +363,13 @@ static void interruption_end(SDL_AudioDevice *device) - (void)applicationBecameActive:(NSNotification *)note { @synchronized(self) { - interruption_end(self.device); + InterruptionEnd(self.device); } } @end -static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord) +static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord) { @autoreleasepool { AVAudioSession *session = [AVAudioSession sharedInstance]; @@ -402,11 +404,11 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo category = AVAudioSessionCategoryRecord; } -#if !TARGET_OS_TV + #if !TARGET_OS_TV if (category == AVAudioSessionCategoryPlayAndRecord) { options |= AVAudioSessionCategoryOptionDefaultToSpeaker; } -#endif + #endif if (category == AVAudioSessionCategoryRecord || category == AVAudioSessionCategoryPlayAndRecord) { /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for @@ -426,27 +428,27 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) { if (![session.category isEqualToString:category] || session.categoryOptions != options) { /* Stop the current session so we don't interrupt other application audio */ - pause_audio_devices(); + PauseAudioDevices(); [session setActive:NO error:nil]; - session_active = NO; + session_active = SDL_FALSE; if (![session setCategory:category mode:mode options:options error:&err]) { NSString *desc = err.description; SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); - return NO; + return SDL_FALSE; } } } else { if (![session.category isEqualToString:category]) { /* Stop the current session so we don't interrupt other application audio */ - pause_audio_devices(); + PauseAudioDevices(); [session setActive:NO error:nil]; - session_active = NO; + session_active = SDL_FALSE; if (![session setCategory:category error:&err]) { NSString *desc = err.description; SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); - return NO; + return SDL_FALSE; } } } @@ -455,19 +457,19 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo if (![session setActive:YES error:&err]) { if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && category == AVAudioSessionCategoryPlayAndRecord) { - return update_audio_session(device, open, SDL_FALSE); + return UpdateAudioSession(device, open, SDL_FALSE); } NSString *desc = err.description; SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); - return NO; + return SDL_FALSE; } - session_active = YES; - resume_audio_devices(); + session_active = SDL_TRUE; + ResumeAudioDevices(); } else if (!open_playback_devices && !open_capture_devices && session_active) { - pause_audio_devices(); + PauseAudioDevices(); [session setActive:NO error:nil]; - session_active = NO; + session_active = SDL_FALSE; } if (open) { @@ -504,191 +506,83 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo } } - return YES; + return SDL_TRUE; } #endif -/* The AudioQueue callback */ -static void outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) + +static void COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - - /* This flag is set before device->mixer_lock is destroyed during - shutdown, so check it before grabbing the mutex, and then check it - again _after_ in case we blocked waiting on the lock. */ - if (SDL_AtomicGet(&device->shutdown)) { - return; /* don't do anything, since we don't even want to enqueue this buffer again. */ - } - - SDL_LockMutex(device->mixer_lock); - - if (SDL_AtomicGet(&device->shutdown)) { - SDL_UnlockMutex(device->mixer_lock); - return; /* don't do anything, since we don't even want to enqueue this buffer again. */ - } - - if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) { - /* Supply silence if audio is not enabled or paused */ - SDL_memset(inBuffer->mAudioData, device->spec.silence, inBuffer->mAudioDataBytesCapacity); - } else if (device->stream) { - UInt32 remaining = inBuffer->mAudioDataBytesCapacity; - Uint8 *ptr = (Uint8 *)inBuffer->mAudioData; - - while (remaining > 0) { - if (SDL_GetAudioStreamAvailable(device->stream) == 0) { - /* Generate the data */ - (*device->callbackspec.callback)(device->callbackspec.userdata, - device->hidden->buffer, device->hidden->bufferSize); - device->hidden->bufferOffset = 0; - SDL_PutAudioStreamData(device->stream, device->hidden->buffer, device->hidden->bufferSize); - } - if (SDL_GetAudioStreamAvailable(device->stream) > 0) { - int got; - UInt32 len = SDL_GetAudioStreamAvailable(device->stream); - if (len > remaining) { - len = remaining; - } - got = SDL_GetAudioStreamData(device->stream, ptr, len); - SDL_assert((got < 0) || (got == len)); - if (got != len) { - SDL_memset(ptr, device->spec.silence, len); - } - ptr = ptr + len; - remaining -= len; - } - } - } else { - UInt32 remaining = inBuffer->mAudioDataBytesCapacity; - Uint8 *ptr = (Uint8 *)inBuffer->mAudioData; - - while (remaining > 0) { - UInt32 len; - if (device->hidden->bufferOffset >= device->hidden->bufferSize) { - /* Generate the data */ - (*device->callbackspec.callback)(device->callbackspec.userdata, - device->hidden->buffer, device->hidden->bufferSize); - device->hidden->bufferOffset = 0; - } - - len = device->hidden->bufferSize - device->hidden->bufferOffset; - if (len > remaining) { - len = remaining; - } - SDL_memcpy(ptr, (char *)device->hidden->buffer + device->hidden->bufferOffset, len); - ptr = ptr + len; - remaining -= len; - device->hidden->bufferOffset += len; - } - } - - AudioQueueEnqueueBuffer(device->hidden->audioQueue, inBuffer, 0, NULL); - - inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; - - SDL_UnlockMutex(device->mixer_lock); + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + SDL_assert(current_buffer != NULL); // should have been called from OutputBufferReadyCallback + SDL_assert(buffer == (Uint8 *) current_buffer->mAudioData); + current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity; + device->hidden->current_buffer = NULL; + AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); } -static void inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, +static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) +{ + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + SDL_assert(current_buffer != NULL); // should have been called from OutputBufferReadyCallback + SDL_assert(current_buffer->mAudioData != NULL); + *buffer_size = (int) current_buffer->mAudioDataBytesCapacity; + return (Uint8 *) current_buffer->mAudioData; +} + +static void OutputBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; + SDL_assert(inBuffer != NULL); // ...right? + SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending + device->hidden->current_buffer = inBuffer; + SDL_OutputAudioThreadIterate(device); + SDL_assert(device->hidden->current_buffer == NULL); // PlayDevice should have enqueued and cleaned it out. +} + +static int COREAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + SDL_assert(current_buffer != NULL); // should have been called from InputBufferReadyCallback + SDL_assert(current_buffer->mAudioData != NULL); + SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); // `cpy` makes sure this won't overflow a buffer, but we _will_ drop samples if this assertion fails! + const int cpy = SDL_min(buflen, (int) current_buffer->mAudioDataByteSize); + SDL_memcpy(buffer, current_buffer->mAudioData, cpy); + device->hidden->current_buffer = NULL; + AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); // requeue for capturing more data later. + return cpy; +} + +static void COREAUDIO_FlushCapture(SDL_AudioDevice *device) +{ + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + if (current_buffer != NULL) { // also gets called at shutdown, when no buffer is available. + // just requeue the current buffer without reading from it, so it can be refilled with new data later. + device->hidden->current_buffer = NULL; + AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); + } +} + +static void InputBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, const AudioStreamPacketDescription *inPacketDescs) { SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - - if (SDL_AtomicGet(&device->shutdown)) { - return; /* don't do anything. */ - } - - /* ignore unless we're active. */ - if (!SDL_AtomicGet(&device->paused) && SDL_AtomicGet(&device->enabled)) { - const Uint8 *ptr = (const Uint8 *)inBuffer->mAudioData; - UInt32 remaining = inBuffer->mAudioDataByteSize; - while (remaining > 0) { - UInt32 len = device->hidden->bufferSize - device->hidden->bufferOffset; - if (len > remaining) { - len = remaining; - } - - SDL_memcpy((char *)device->hidden->buffer + device->hidden->bufferOffset, ptr, len); - ptr += len; - remaining -= len; - device->hidden->bufferOffset += len; - - if (device->hidden->bufferOffset >= device->hidden->bufferSize) { - SDL_LockMutex(device->mixer_lock); - (*device->callbackspec.callback)(device->callbackspec.userdata, device->hidden->buffer, device->hidden->bufferSize); - SDL_UnlockMutex(device->mixer_lock); - device->hidden->bufferOffset = 0; - } - } - } - - AudioQueueEnqueueBuffer(device->hidden->audioQueue, inBuffer, 0, NULL); + SDL_assert(inAQ == device->hidden->audioQueue); + SDL_assert(inBuffer != NULL); // ...right? + SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending + device->hidden->current_buffer = inBuffer; + SDL_CaptureAudioThreadIterate(device); + SDL_assert(device->hidden->current_buffer == NULL); // CaptureFromDevice/FlushCapture should have enqueued and cleaned it out. } -#ifdef MACOSX_COREAUDIO -static const AudioObjectPropertyAddress alive_address = { - kAudioDevicePropertyDeviceIsAlive, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain -}; - -static OSStatus device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)data; - SDL_bool dead = SDL_FALSE; - UInt32 isAlive = 1; - UInt32 size = sizeof(isAlive); - OSStatus error; - - if (!SDL_AtomicGet(&device->enabled)) { - return 0; /* already known to be dead. */ - } - - error = AudioObjectGetPropertyData(device->hidden->deviceID, &alive_address, - 0, NULL, &size, &isAlive); - - if (error == kAudioHardwareBadDeviceError) { - dead = SDL_TRUE; /* device was unplugged. */ - } else if ((error == kAudioHardwareNoError) && (!isAlive)) { - dead = SDL_TRUE; /* device died in some other way. */ - } - - if (dead) { - SDL_OpenedAudioDeviceDisconnected(device); - } - - return 0; -} - -/* macOS calls this when the default device changed (if we have a default device open). */ -static OSStatus default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; -#if DEBUG_COREAUDIO - printf("COREAUDIO: default device changed for SDL audio device %p!\n", device); -#endif - SDL_AtomicSet(&device->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */ - return noErr; -} -#endif - static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) { - const SDL_bool iscapture = device->iscapture; - int i; - -/* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ -/* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ -#ifdef MACOSX_COREAUDIO - if (device->handle != NULL) { /* we don't register this listener for default devices. */ - AudioObjectRemovePropertyListener(device->hidden->deviceID, &alive_address, device_unplugged, device); + if (!device->hidden) { + return; } -#endif - /* if callback fires again, feed silence; don't call into the app. */ - SDL_AtomicSet(&device->paused, 1); - - /* dispose of the audio queue before waiting on the thread, or it might stall for a long time! */ + // dispose of the audio queue before waiting on the thread, or it might stall for a long time! if (device->hidden->audioQueue) { AudioQueueFlush(device->hidden->audioQueue); AudioQueueStop(device->hidden->audioQueue, 0); @@ -696,55 +590,33 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) } if (device->hidden->thread) { - SDL_assert(SDL_AtomicGet(&device->shutdown) != 0); /* should have been set by SDL_audio.c */ + SDL_assert(SDL_AtomicGet(&device->shutdown) != 0); // should have been set by SDL_audio.c SDL_WaitThread(device->hidden->thread, NULL); } - if (iscapture) { - open_capture_devices--; - } else { - open_playback_devices--; - } - -#ifndef MACOSX_COREAUDIO - update_audio_session(device, SDL_FALSE, SDL_TRUE); -#endif - - for (i = 0; i < num_open_devices; ++i) { - if (open_devices[i] == device) { - --num_open_devices; - if (i < num_open_devices) { - SDL_memmove(&open_devices[i], &open_devices[i + 1], sizeof(open_devices[i]) * (num_open_devices - i)); - } - break; - } - } - if (num_open_devices == 0) { - SDL_free(open_devices); - open_devices = NULL; - } + #ifndef MACOSX_COREAUDIO + UpdateAudioSession(device, SDL_FALSE, SDL_TRUE); + #endif if (device->hidden->ready_semaphore) { SDL_DestroySemaphore(device->hidden->ready_semaphore); } - /* AudioQueueDispose() frees the actual buffer objects. */ + // AudioQueueDispose() frees the actual buffer objects. SDL_free(device->hidden->audioBuffer); SDL_free(device->hidden->thread_error); - SDL_free(device->hidden->buffer); SDL_free(device->hidden); } #ifdef MACOSX_COREAUDIO -static int prepare_device(SDL_AudioDevice *device) +static int PrepareDevice(SDL_AudioDevice *device) { void *handle = device->handle; - SDL_bool iscapture = device->iscapture; - AudioDeviceID devid = (AudioDeviceID)((size_t)handle); + SDL_assert(handle != NULL); // this meant "system default" in SDL2, but doesn't anymore + + const AudioDeviceID devid = (AudioDeviceID)((size_t)handle); OSStatus result = noErr; UInt32 size = 0; - UInt32 alive = 0; - pid_t pid = 0; AudioObjectPropertyAddress addr = { 0, @@ -752,42 +624,31 @@ static int prepare_device(SDL_AudioDevice *device) kAudioObjectPropertyElementMain }; - if (handle == NULL) { - size = sizeof(AudioDeviceID); - addr.mSelector = - ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice); - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, - 0, NULL, &size, &devid); - CHECK_RESULT("AudioHardwareGetProperty (default device)"); - } - - addr.mSelector = kAudioDevicePropertyDeviceIsAlive; - addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - + UInt32 alive = 0; size = sizeof(alive); + addr.mSelector = kAudioDevicePropertyDeviceIsAlive; + addr.mScope = device->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); - if (!alive) { - SDL_SetError("CoreAudio: requested device exists, but isn't alive."); - return 0; + return SDL_SetError("CoreAudio: requested device exists, but isn't alive."); } - addr.mSelector = kAudioDevicePropertyHogMode; + // some devices don't support this property, so errors are fine here. + pid_t pid = 0; size = sizeof(pid); + addr.mSelector = kAudioDevicePropertyHogMode; result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); - - /* some devices don't support this property, so errors are fine here. */ if ((result == noErr) && (pid != -1)) { - SDL_SetError("CoreAudio: requested device is being hogged."); - return 0; + return SDL_SetError("CoreAudio: requested device is being hogged."); } device->hidden->deviceID = devid; - return 1; + + return 0; } -static int assign_device_to_audioqueue(SDL_AudioDevice *device) +static int AssignDeviceToAudioQueue(SDL_AudioDevice *device) { const AudioObjectPropertyAddress prop = { kAudioDevicePropertyDeviceUID, @@ -803,51 +664,38 @@ static int assign_device_to_audioqueue(SDL_AudioDevice *device) result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); - return 1; + // !!! FIXME: do we need to CFRelease(devuid)? + + return 0; } #endif -static int prepare_audioqueue(SDL_AudioDevice *device) +static int PrepareAudioQueue(SDL_AudioDevice *device) { const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; - const int iscapture = device->iscapture; + const SDL_bool iscapture = device->iscapture; OSStatus result; - int i, numAudioBuffers = 2; - AudioChannelLayout layout; - double MINIMUM_AUDIO_BUFFER_TIME_MS; - const double msecs = (device->spec.samples / ((double)device->spec.freq)) * 1000.0; - ; SDL_assert(CFRunLoopGetCurrent() != NULL); if (iscapture) { - result = AudioQueueNewInput(strdesc, inputCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); + result = AudioQueueNewInput(strdesc, InputBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); CHECK_RESULT("AudioQueueNewInput"); } else { - result = AudioQueueNewOutput(strdesc, outputCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); + result = AudioQueueNewOutput(strdesc, OutputBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); CHECK_RESULT("AudioQueueNewOutput"); } -#ifdef MACOSX_COREAUDIO - if (!assign_device_to_audioqueue(device)) { - return 0; + #ifdef MACOSX_COREAUDIO + if (AssignDeviceToAudioQueue(device) < 0) { + return -1; } + #endif - /* only listen for unplugging on specific devices, not the default device, as that should - switch to a different device (or hang out silently if there _is_ no other device). */ - if (device->handle != NULL) { - /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ - /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ - /* Fire a callback if the device stops being "alive" (disconnected, etc). */ - /* If this fails, oh well, we won't notice a device had an extraordinary event take place. */ - AudioObjectAddPropertyListener(device->hidden->deviceID, &alive_address, device_unplugged, device); - } -#endif - - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&device->spec); + SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct. /* Set the channel layout for the audio queue */ + AudioChannelLayout layout; SDL_zero(layout); switch (device->spec.channels) { case 1: @@ -881,43 +729,35 @@ static int prepare_audioqueue(SDL_AudioDevice *device) CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)"); } - /* Allocate a sample buffer */ - device->hidden->bufferSize = device->spec.size; - device->hidden->bufferOffset = iscapture ? 0 : device->hidden->bufferSize; - - device->hidden->buffer = SDL_malloc(device->hidden->bufferSize); - if (device->hidden->buffer == NULL) { - SDL_OutOfMemory(); - return 0; - } - - /* Make sure we can feed the device a minimum amount of time */ - MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; -#ifdef __IOS__ + // Make sure we can feed the device a minimum amount of time + double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; + #ifdef __IOS__ if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { - /* Older iOS hardware, use 40 ms as a minimum time */ + // Older iOS hardware, use 40 ms as a minimum time MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; } -#endif + #endif + + int numAudioBuffers = 2; + const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0; if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */ numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); } device->hidden->numAudioBuffers = numAudioBuffers; - device->hidden->audioBuffer = SDL_calloc(1, sizeof(AudioQueueBufferRef) * numAudioBuffers); + device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef)); if (device->hidden->audioBuffer == NULL) { - SDL_OutOfMemory(); - return 0; + return SDL_OutOfMemory(); } -#if DEBUG_COREAUDIO - printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); -#endif + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); + #endif - for (i = 0; i < numAudioBuffers; i++) { - result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->spec.size, &device->hidden->audioBuffer[i]); + for (int i = 0; i < numAudioBuffers; i++) { + result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->buffer_size, &device->hidden->audioBuffer[i]); CHECK_RESULT("AudioQueueAllocateBuffer"); - SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->spec.silence, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); + SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity; /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); @@ -927,117 +767,55 @@ static int prepare_audioqueue(SDL_AudioDevice *device) result = AudioQueueStart(device->hidden->audioQueue, NULL); CHECK_RESULT("AudioQueueStart"); - /* We're running! */ - return 1; + return 0; // We're running! } -static int audioqueue_thread(void *arg) +static int AudioQueueThreadEntry(void *arg) { SDL_AudioDevice *device = (SDL_AudioDevice *)arg; - int rc; -#ifdef MACOSX_COREAUDIO - const AudioObjectPropertyAddress default_device_address = { - device->iscapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain - }; - - if (device->handle == NULL) { /* opened the default device? Register to know if the user picks a new default. */ - /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, device); + if (device->iscapture) { + SDL_CaptureAudioThreadSetup(device); + } else { + SDL_OutputAudioThreadSetup(device); } -#endif - rc = prepare_audioqueue(device); - if (!rc) { + if (PrepareAudioQueue(device) < 0) { device->hidden->thread_error = SDL_strdup(SDL_GetError()); SDL_PostSemaphore(device->hidden->ready_semaphore); return 0; } - SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); - - /* init was successful, alert parent thread and start running... */ + // init was successful, alert parent thread and start running... SDL_PostSemaphore(device->hidden->ready_semaphore); + // This would be WaitDevice/WaitCaptureDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate. while (!SDL_AtomicGet(&device->shutdown)) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); - -#ifdef MACOSX_COREAUDIO - if ((device->handle == NULL) && SDL_AtomicGet(&device->hidden->device_change_flag)) { - const AudioDeviceID prev_devid = device->hidden->deviceID; - SDL_AtomicSet(&device->hidden->device_change_flag, 0); - -#if DEBUG_COREAUDIO - printf("COREAUDIO: audioqueue_thread is trying to switch to new default device!\n"); -#endif - - /* if any of this fails, there's not much to do but wait to see if the user gives up - and quits (flagging the audioqueue for shutdown), or toggles to some other system - output device (in which case we'll try again). */ - if (prepare_device(device) && (prev_devid != device->hidden->deviceID)) { - AudioQueueStop(device->hidden->audioQueue, 1); - if (assign_device_to_audioqueue(device)) { - int i; - for (i = 0; i < device->hidden->numAudioBuffers; i++) { - SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->spec.silence, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); - /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ - AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); - } - AudioQueueStart(device->hidden->audioQueue, NULL); - } - } - } -#endif } - if (!device->iscapture) { /* Drain off any pending playback. */ - const CFTimeInterval secs = (((device->spec.size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8.0)) / device->spec.channels) / ((CFTimeInterval)device->spec.freq)) * 2.0; + if (device->iscapture) { + SDL_CaptureAudioThreadShutdown(device); + } else { + // Drain off any pending playback. + const CFTimeInterval secs = (((CFTimeInterval)device->sample_frames) / ((CFTimeInterval)device->spec.freq)) * 2.0; CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); + SDL_OutputAudioThreadShutdown(device); } -#ifdef MACOSX_COREAUDIO - if (device->handle == NULL) { - /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, device); - } -#endif - return 0; } -static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) +static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) { - AudioStreamBasicDescription *strdesc; - const SDL_AudioFormat *closefmts; - SDL_AudioFormat test_format; - SDL_bool iscapture = device->iscapture; - SDL_AudioDevice **new_open_devices; - /* Initialize all variables that we clean on shutdown */ - device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(device->hidden); - strdesc = &device->hidden->strdesc; - - if (iscapture) { - open_capture_devices++; - } else { - open_playback_devices++; - } - - new_open_devices = (SDL_AudioDevice **)SDL_realloc(open_devices, sizeof(open_devices[0]) * (num_open_devices + 1)); - if (new_open_devices) { - open_devices = new_open_devices; - open_devices[num_open_devices++] = device; - } - -#ifndef MACOSX_COREAUDIO - if (!update_audio_session(device, SDL_TRUE, SDL_TRUE)) { + #ifndef MACOSX_COREAUDIO + if (!UpdateAudioSession(device, SDL_TRUE, SDL_TRUE)) { return -1; } @@ -1046,29 +824,30 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) AVAudioSession *session = [AVAudioSession sharedInstance]; [session setPreferredSampleRate:device->spec.freq error:nil]; device->spec.freq = (int)session.sampleRate; -#if TARGET_OS_TV - if (iscapture) { + #if TARGET_OS_TV + if (device->iscapture) { [session setPreferredInputNumberOfChannels:device->spec.channels error:nil]; device->spec.channels = session.preferredInputNumberOfChannels; } else { [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil]; device->spec.channels = session.preferredOutputNumberOfChannels; } -#else + #else /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */ -#endif /* TARGET_OS_TV */ + #endif /* TARGET_OS_TV */ } -#endif + #endif /* Setup a AudioStreamBasicDescription with the requested format */ - SDL_zerop(strdesc); + AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; strdesc->mFormatID = kAudioFormatLinearPCM; strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; strdesc->mChannelsPerFrame = device->spec.channels; strdesc->mSampleRate = device->spec.freq; strdesc->mFramesPerPacket = 1; - closefmts = SDL_ClosestAudioFormats(device->spec.format); + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); + SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { /* CoreAudio handles most of SDL's formats natively. */ switch (test_format) { @@ -1107,7 +886,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; #ifdef MACOSX_COREAUDIO - if (!prepare_device(device)) { + if (PrepareDevice(device) < 0) { return -1; } #endif @@ -1118,7 +897,9 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) return -1; /* oh well. */ } - device->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, device); + char threadname[64]; + SDL_GetAudioThreadName(device, threadname, sizeof(threadname)); + device->hidden->thread = SDL_CreateThreadInternal(AudioQueueThreadEntry, threadname, 0, device); if (!device->hidden->thread) { return -1; } @@ -1128,154 +909,20 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) device->hidden->ready_semaphore = NULL; if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) { + SDL_WaitThread(device->hidden->thread, NULL); + device->hidden->thread = NULL; return SDL_SetError("%s", device->hidden->thread_error); } return (device->hidden->thread != NULL) ? 0 : -1; } -#ifndef MACOSX_COREAUDIO -static int COREAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - AVAudioSession *session = [AVAudioSession sharedInstance]; - - if (name != NULL) { - *name = NULL; - } - SDL_zerop(spec); - spec->freq = [session sampleRate]; - spec->channels = [session outputNumberOfChannels]; - return 0; -} -#else /* MACOSX_COREAUDIO */ -static int COREAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - AudioDeviceID devid; - AudioBufferList *buflist; - OSStatus result; - UInt32 size; - CFStringRef cfstr; - char *devname; - int usable; - double sampleRate; - CFIndex len; - - AudioObjectPropertyAddress addr = { - iscapture ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, - iscapture ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - AudioObjectPropertyAddress nameaddr = { - kAudioObjectPropertyName, - iscapture ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - AudioObjectPropertyAddress freqaddr = { - kAudioDevicePropertyNominalSampleRate, - iscapture ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - AudioObjectPropertyAddress bufaddr = { - kAudioDevicePropertyStreamConfiguration, - iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - /* Get the Device ID */ - cfstr = NULL; - size = sizeof(AudioDeviceID); - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, - 0, NULL, &size, &devid); - - if (result != noErr) { - return SDL_SetError("%s: Default Device ID not found", "coreaudio"); - } - - if (name != NULL) { - /* Use the Device ID to get the name */ - size = sizeof(CFStringRef); - result = AudioObjectGetPropertyData(devid, &nameaddr, 0, NULL, &size, &cfstr); - - if (result != noErr) { - return SDL_SetError("%s: Default Device Name not found", "coreaudio"); - } - - len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), - kCFStringEncodingUTF8); - devname = (char *)SDL_malloc(len + 1); - usable = ((devname != NULL) && - (CFStringGetCString(cfstr, devname, len + 1, kCFStringEncodingUTF8))); - CFRelease(cfstr); - - if (usable) { - usable = 0; - len = SDL_strlen(devname); - /* Some devices have whitespace at the end...trim it. */ - while ((len > 0) && (devname[len - 1] == ' ')) { - len--; - usable = (int)len; - } - } - - if (usable) { - devname[len] = '\0'; - } - *name = devname; - } - - /* Uses the Device ID to get the spec */ - SDL_zerop(spec); - - sampleRate = 0; - size = sizeof(sampleRate); - result = AudioObjectGetPropertyData(devid, &freqaddr, 0, NULL, &size, &sampleRate); - - if (result != noErr) { - return SDL_SetError("%s: Default Device Sample Rate not found", "coreaudio"); - } - - spec->freq = (int)sampleRate; - - result = AudioObjectGetPropertyDataSize(devid, &bufaddr, 0, NULL, &size); - if (result != noErr) { - return SDL_SetError("%s: Default Device Data Size not found", "coreaudio"); - } - - buflist = (AudioBufferList *)SDL_malloc(size); - if (buflist == NULL) { - return SDL_SetError("%s: Default Device Buffer List not found", "coreaudio"); - } - - result = AudioObjectGetPropertyData(devid, &bufaddr, 0, NULL, - &size, buflist); - - if (result == noErr) { - UInt32 j; - for (j = 0; j < buflist->mNumberBuffers; j++) { - spec->channels += buflist->mBuffers[j].mNumberChannels; - } - } - - SDL_free(buflist); - - if (spec->channels == 0) { - return SDL_SetError("%s: Default Device has no channels!", "coreaudio"); - } - - return 0; -} -#endif /* MACOSX_COREAUDIO */ - static void COREAUDIO_Deinitialize(void) { #ifdef MACOSX_COREAUDIO - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); - free_audio_device_list(&capture_devs); - free_audio_device_list(&output_devs); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_output_device_address, DefaultOutputDeviceChangedNotification, NULL); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_input_device_address, DefaultInputDeviceChangedNotification, NULL); #endif } @@ -1283,13 +930,16 @@ static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) { /* Set the function pointers */ impl->OpenDevice = COREAUDIO_OpenDevice; + impl->PlayDevice = COREAUDIO_PlayDevice; + impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; + impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice; + impl->FlushCapture = COREAUDIO_FlushCapture; impl->CloseDevice = COREAUDIO_CloseDevice; impl->Deinitialize = COREAUDIO_Deinitialize; - impl->GetDefaultAudioInfo = COREAUDIO_GetDefaultAudioInfo; #ifdef MACOSX_COREAUDIO impl->DetectDevices = COREAUDIO_DetectDevices; - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); + impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle; #else impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;