From 30f41c02aec763d32e62351452da9ef582bc3472 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 6 Mar 2026 13:30:59 -0800 Subject: Move contrib libraries to contrib repo --- .../SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h | 68 -- .../SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m | 1040 -------------------- 2 files changed, 1108 deletions(-) delete mode 100644 contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h delete mode 100644 contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m (limited to 'contrib/SDL-3.2.8/src/audio/coreaudio') diff --git a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h deleted file mode 100644 index f117c09..0000000 --- a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ -#include "SDL_internal.h" - -#ifndef SDL_coreaudio_h_ -#define SDL_coreaudio_h_ - -#include "../SDL_sysaudio.h" - -#ifndef SDL_PLATFORM_IOS -#define MACOSX_COREAUDIO -#endif - -#ifdef MACOSX_COREAUDIO -#include -#else -#import -#import -#endif - -#include -#include - -// Things named "Master" were renamed to "Main" in macOS 12.0's SDK. -#ifdef MACOSX_COREAUDIO -#include -#ifndef MAC_OS_VERSION_12_0 -#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster -#endif -#endif - -struct SDL_PrivateAudioData -{ - SDL_Thread *thread; - AudioQueueRef audioQueue; - int numAudioBuffers; - AudioQueueBufferRef *audioBuffer; - AudioQueueBufferRef current_buffer; - AudioStreamBasicDescription strdesc; - SDL_Semaphore *ready_semaphore; - char *thread_error; -#ifdef MACOSX_COREAUDIO - AudioDeviceID deviceID; -#else - bool interrupted; - CFTypeRef interruption_listener; -#endif -}; - -#endif // SDL_coreaudio_h_ diff --git a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m b/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m deleted file mode 100644 index 57b19c7..0000000 --- a/contrib/SDL-3.2.8/src/audio/coreaudio/SDL_coreaudio.m +++ /dev/null @@ -1,1040 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ -#include "SDL_internal.h" - -#ifdef SDL_AUDIO_DRIVER_COREAUDIO - -#include "../SDL_sysaudio.h" -#include "SDL_coreaudio.h" -#include "../../thread/SDL_systhread.h" - -#define DEBUG_COREAUDIO 0 - -#if DEBUG_COREAUDIO - #define CHECK_RESULT(msg) \ - if (result != noErr) { \ - SDL_Log("COREAUDIO: Got error %d from '%s'!", (int)result, msg); \ - return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ - } -#else - #define CHECK_RESULT(msg) \ - if (result != noErr) { \ - return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ - } -#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; - bool recording; -} SDLCoreAudioHandle; - -static 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->recording == !!b->recording); -} - -static SDL_AudioDevice *FindCoreAudioDeviceByHandle(const AudioDeviceID devid, const bool recording) -{ - SDLCoreAudioHandle handle = { devid, recording }; - return SDL_FindPhysicalAudioDeviceByCallback(TestCoreAudioDeviceHandleCallback, &handle); -} - -static const AudioObjectPropertyAddress devlist_address = { - kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain -}; - -static const AudioObjectPropertyAddress default_playback_device_address = { - kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain -}; - -static const AudioObjectPropertyAddress default_recording_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) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)data; - SDL_assert(((const SDLCoreAudioHandle *) device->handle)->devid == devid); - - UInt32 alive = 1; - UInt32 size = sizeof(alive); - const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive); - - bool dead = false; - if (error == kAudioHardwareBadDeviceError) { - dead = true; // device was unplugged. - } else if ((error == kAudioHardwareNoError) && (!alive)) { - dead = true; // device died in some other way. - } - - if (dead) { - #if DEBUG_COREAUDIO - SDL_Log("COREAUDIO: device '%s' is lost!", device->name); - #endif - SDL_AudioDeviceDisconnected(device); - } - - return noErr; -} - -static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *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. -static void RefreshPhysicalDevices(void) -{ - UInt32 size = 0; - AudioDeviceID *devs = NULL; - bool isstack; - - 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; - } - - const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID)); - for (UInt32 i = 0; i < total_devices; i++) { - if (FindCoreAudioDeviceByHandle(devs[i], true) || FindCoreAudioDeviceByHandle(devs[i], false)) { - devs[i] = 0; // The system and SDL both agree it's already here, don't check it again. - } - } - - // any non-zero items remaining in `devs` are new devices to be added. - for (int recording = 0; recording < 2; recording++) { - const AudioObjectPropertyAddress addr = { - kAudioDevicePropertyStreamConfiguration, - recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - const AudioObjectPropertyAddress nameaddr = { - kAudioObjectPropertyName, - recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - const AudioObjectPropertyAddress freqaddr = { - kAudioDevicePropertyNominalSampleRate, - recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - for (UInt32 i = 0; i < total_devices; i++) { - const AudioDeviceID dev = devs[i]; - if (!dev) { - continue; // already added. - } - - AudioBufferList *buflist = NULL; - double sampleRate = 0; - - if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) { - continue; - } else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) { - continue; - } - - OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist); - - 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(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); - bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))); - - 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)", ((recording) ? "recording" : "playback"), (int)i, name, (int)dev); - #endif - SDLCoreAudioHandle *newhandle = (SDLCoreAudioHandle *) SDL_calloc(1, sizeof (*newhandle)); - if (newhandle) { - newhandle->devid = dev; - newhandle->recording = recording ? true : false; - SDL_AudioDevice *device = SDL_AddAudioDevice(newhandle->recording, 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. - } - } - - SDL_small_free(devs, isstack); -} - -// 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) -{ - RefreshPhysicalDevices(); - return noErr; -} - -static OSStatus DefaultAudioDeviceChangedNotification(const bool recording, AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) -{ - AudioDeviceID devid; - UInt32 size = sizeof(devid); - if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { - SDL_DefaultAudioDeviceChanged(FindCoreAudioDeviceByHandle(devid, recording)); - } - return noErr; -} - -static OSStatus DefaultPlaybackDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) -{ - #if DEBUG_COREAUDIO - SDL_Log("COREAUDIO: default playback device changed!"); - #endif - SDL_assert(inNumberAddresses == 1); - return DefaultAudioDeviceChangedNotification(false, inObjectID, inAddresses); -} - -static OSStatus DefaultRecordingDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) -{ - #if DEBUG_COREAUDIO - SDL_Log("COREAUDIO: default recording device changed!"); - #endif - SDL_assert(inNumberAddresses == 1); - return DefaultAudioDeviceChangedNotification(true, inObjectID, inAddresses); -} - -static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) -{ - RefreshPhysicalDevices(); - - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); - - // Get the Device ID - UInt32 size; - AudioDeviceID devid; - - size = sizeof(AudioDeviceID); - if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_playback_device_address, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, false); - if (device) { - *default_playback = device; - } - } - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL); - - size = sizeof(AudioDeviceID); - if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_recording_device_address, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, true); - if (device) { - *default_recording = device; - } - } - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL); -} - -#else // iOS-specific section follows. - -static bool session_active = false; - -static bool PauseOneAudioDevice(SDL_AudioDevice *device, void *userdata) -{ - if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) { - AudioQueuePause(device->hidden->audioQueue); - } - return false; // keep enumerating devices until we've paused them all. -} - -static void PauseAudioDevices(void) -{ - (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL); -} - -static bool ResumeOneAudioDevice(SDL_AudioDevice *device, void *userdata) -{ - if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) { - AudioQueueStart(device->hidden->audioQueue, NULL); - } - return false; // keep enumerating devices until we've resumed them all. -} - -static void ResumeAudioDevices(void) -{ - (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL); -} - -static void InterruptionBegin(SDL_AudioDevice *device) -{ - if (device != NULL && device->hidden->audioQueue != NULL) { - device->hidden->interrupted = true; - AudioQueuePause(device->hidden->audioQueue); - } -} - -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 = false; - } -} - -@interface SDLInterruptionListener : NSObject - -@property(nonatomic, assign) SDL_AudioDevice *device; - -@end - -@implementation SDLInterruptionListener - -- (void)audioSessionInterruption:(NSNotification *)note -{ - @synchronized(self) { - NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; - if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { - InterruptionBegin(self.device); - } else { - InterruptionEnd(self.device); - } - } -} - -- (void)applicationBecameActive:(NSNotification *)note -{ - @synchronized(self) { - InterruptionEnd(self.device); - } -} - -@end - -typedef struct -{ - int playback; - int recording; -} CountOpenAudioDevicesData; - -static bool CountOpenAudioDevices(SDL_AudioDevice *device, void *userdata) -{ - CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata; - if (device->hidden != NULL) { // assume it's open if hidden != NULL - if (device->recording) { - data->recording++; - } else { - data->playback++; - } - } - return false; // keep enumerating until all devices have been checked. -} - -static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_playandrecord) -{ - @autoreleasepool { - AVAudioSession *session = [AVAudioSession sharedInstance]; - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - - NSString *category = AVAudioSessionCategoryPlayback; - NSString *mode = AVAudioSessionModeDefault; - NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; - NSError *err = nil; - const char *hint; - - CountOpenAudioDevicesData data; - SDL_zero(data); - (void) SDL_FindPhysicalAudioDeviceByCallback(CountOpenAudioDevices, &data); - - hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); - if (hint) { - if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { - category = AVAudioSessionCategoryAmbient; - } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { - category = AVAudioSessionCategorySoloAmbient; - options &= ~AVAudioSessionCategoryOptionMixWithOthers; - } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || - SDL_strcasecmp(hint, "playback") == 0) { - category = AVAudioSessionCategoryPlayback; - options &= ~AVAudioSessionCategoryOptionMixWithOthers; - } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 || - SDL_strcasecmp(hint, "playandrecord") == 0) { - if (allow_playandrecord) { - category = AVAudioSessionCategoryPlayAndRecord; - } - } - } else if (data.playback && data.recording) { - if (allow_playandrecord) { - category = AVAudioSessionCategoryPlayAndRecord; - } else { - // We already failed play and record with AVAudioSessionErrorCodeResourceNotAvailable - return false; - } - } else if (data.recording) { - category = AVAudioSessionCategoryRecord; - } - - #ifndef SDL_PLATFORM_TVOS - if (category == AVAudioSessionCategoryPlayAndRecord) { - options |= AVAudioSessionCategoryOptionDefaultToSpeaker; - } - #endif - if (category == AVAudioSessionCategoryRecord || - category == AVAudioSessionCategoryPlayAndRecord) { - /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for - Apple TV but is still needed in order to output to Bluetooth devices. - */ - options |= 0x4; // AVAudioSessionCategoryOptionAllowBluetooth; - } - if (category == AVAudioSessionCategoryPlayAndRecord) { - options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP | - AVAudioSessionCategoryOptionAllowAirPlay; - } - if (category == AVAudioSessionCategoryPlayback || - category == AVAudioSessionCategoryPlayAndRecord) { - options |= AVAudioSessionCategoryOptionDuckOthers; - } - - if (![session.category isEqualToString:category] || session.categoryOptions != options) { - // Stop the current session so we don't interrupt other application audio - PauseAudioDevices(); - [session setActive:NO error:nil]; - session_active = 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 false; - } - } - - if ((data.playback || data.recording) && !session_active) { - if (![session setActive:YES error:&err]) { - if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && - category == AVAudioSessionCategoryPlayAndRecord) { - if (UpdateAudioSession(device, open, false)) { - return true; - } else { - return SDL_SetError("Could not activate Audio Session: Resource not available"); - } - } - - NSString *desc = err.description; - return SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); - } - session_active = true; - ResumeAudioDevices(); - } else if (!data.playback && !data.recording && session_active) { - PauseAudioDevices(); - [session setActive:NO error:nil]; - session_active = false; - } - - if (open) { - SDLInterruptionListener *listener = [SDLInterruptionListener new]; - listener.device = device; - - [center addObserver:listener - selector:@selector(audioSessionInterruption:) - name:AVAudioSessionInterruptionNotification - object:session]; - - /* An interruption end notification is not guaranteed to be sent if - we were previously interrupted... resuming if needed when the app - becomes active seems to be the way to go. */ - // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications. - [center addObserver:listener - selector:@selector(applicationBecameActive:) - name:UIApplicationDidBecomeActiveNotification - object:nil]; - - [center addObserver:listener - selector:@selector(applicationBecameActive:) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - - device->hidden->interruption_listener = CFBridgingRetain(listener); - } else { - SDLInterruptionListener *listener = nil; - listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener); - [center removeObserver:listener]; - @synchronized(listener) { - listener.device = NULL; - } - } - } - - return true; -} -#endif - - -static bool COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) -{ - AudioQueueBufferRef current_buffer = device->hidden->current_buffer; - SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback - 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); - return true; -} - -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 PlaybackBufferReadyCallback - SDL_assert(current_buffer->mAudioData != NULL); - *buffer_size = (int) current_buffer->mAudioDataBytesCapacity; - return (Uint8 *) current_buffer->mAudioData; -} - -static void PlaybackBufferReadyCallback(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; - const bool okay = SDL_PlaybackAudioThreadIterate(device); - SDL_assert((device->hidden->current_buffer == NULL) || !okay); // PlayDevice should have enqueued and cleaned it out, unless we failed or shutdown. - - // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer with silence. - if (device->hidden->current_buffer) { - AudioQueueBufferRef current_buffer = device->hidden->current_buffer; - device->hidden->current_buffer = NULL; - SDL_memset(current_buffer->mAudioData, device->silence_value, (size_t) current_buffer->mAudioDataBytesCapacity); - AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); - } -} - -static int COREAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) -{ - AudioQueueBufferRef current_buffer = device->hidden->current_buffer; - SDL_assert(current_buffer != NULL); // should have been called from RecordingBufferReadyCallback - 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_FlushRecording(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 RecordingBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, - const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, - const AudioStreamPacketDescription *inPacketDescs) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - 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_RecordingAudioThreadIterate(device); - - // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer anyhow. - if (device->hidden->current_buffer != NULL) { - SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0); - COREAUDIO_FlushRecording(device); // just flush it manually, which will requeue it. - } -} - -static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) -{ - if (!device->hidden) { - return; - } - - // 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); - AudioQueueDispose(device->hidden->audioQueue, 0); - } - - if (device->hidden->thread) { - SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0); // should have been set by SDL_audio.c - SDL_WaitThread(device->hidden->thread, NULL); - } - - #ifndef MACOSX_COREAUDIO - UpdateAudioSession(device, false, true); - #endif - - if (device->hidden->ready_semaphore) { - SDL_DestroySemaphore(device->hidden->ready_semaphore); - } - - // AudioQueueDispose() frees the actual buffer objects. - SDL_free(device->hidden->audioBuffer); - SDL_free(device->hidden->thread_error); - SDL_free(device->hidden); -} - -#ifdef MACOSX_COREAUDIO -static bool PrepareDevice(SDL_AudioDevice *device) -{ - SDL_assert(device->handle != NULL); // this meant "system default" in SDL2, but doesn't anymore - - const SDLCoreAudioHandle *handle = (const SDLCoreAudioHandle *) device->handle; - const AudioDeviceID devid = handle->devid; - OSStatus result = noErr; - UInt32 size = 0; - - AudioObjectPropertyAddress addr = { - 0, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain - }; - - UInt32 alive = 0; - size = sizeof(alive); - addr.mSelector = kAudioDevicePropertyDeviceIsAlive; - addr.mScope = device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); - CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); - if (!alive) { - return SDL_SetError("CoreAudio: requested device exists, but isn't alive."); - } - - // 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); - if ((result == noErr) && (pid != -1)) { - return SDL_SetError("CoreAudio: requested device is being hogged."); - } - - device->hidden->deviceID = devid; - - return true; -} - -static bool AssignDeviceToAudioQueue(SDL_AudioDevice *device) -{ - const AudioObjectPropertyAddress prop = { - kAudioDevicePropertyDeviceUID, - device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - OSStatus result; - CFStringRef devuid; - UInt32 devuidsize = sizeof(devuid); - result = AudioObjectGetPropertyData(device->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); - CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); - result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); - CFRelease(devuid); // Release devuid; we're done with it and AudioQueueSetProperty should have retained if it wants to keep it. - CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); - return true; -} -#endif - -static bool PrepareAudioQueue(SDL_AudioDevice *device) -{ - const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; - const bool recording = device->recording; - OSStatus result; - - SDL_assert(CFRunLoopGetCurrent() != NULL); - - if (recording) { - result = AudioQueueNewInput(strdesc, RecordingBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); - CHECK_RESULT("AudioQueueNewInput"); - } else { - result = AudioQueueNewOutput(strdesc, PlaybackBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); - CHECK_RESULT("AudioQueueNewOutput"); - } - - #ifdef MACOSX_COREAUDIO - if (!AssignDeviceToAudioQueue(device)) { - return false; - } - #endif - - 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: - // a standard mono stream - layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; - break; - case 2: - // a standard stereo stream (L R) - implied playback - layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; - break; - case 3: - // L R LFE - layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4; - break; - case 4: - // front left, front right, back left, back right - layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic; - break; - case 5: - // L R LFE Ls Rs - layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_6; - break; - case 6: - // L R C LFE Ls Rs - layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_12; - break; - case 7: - if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { - // L R C LFE Cs Ls Rs - layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_6_1; - } else { - // L R C LFE Ls Rs Cs - layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A; - - // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_6_1_A - static const int swizzle_map[7] = { - 0, 1, 2, 3, 6, 4, 5 - }; - device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map)); - if (!device->chmap) { - return false; - } - } - break; - case 8: - if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { - // L R C LFE Rls Rrs Ls Rs - layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_7_1; - } else { - // L R C LFE Ls Rs Rls Rrs - layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_C; - - // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_7_1_C - static const int swizzle_map[8] = { - 0, 1, 2, 3, 6, 7, 4, 5 - }; - device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map)); - if (!device->chmap) { - return false; - } - } - break; - default: - return SDL_SetError("Unsupported audio channels"); - } - if (layout.mChannelLayoutTag != 0) { - result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout)); - CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)"); - } - - // Make sure we can feed the device a minimum amount of time - double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; - #ifdef SDL_PLATFORM_IOS - if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { - // Older iOS hardware, use 40 ms as a minimum time - MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; - } - #endif - - // we use THREE audio buffers by default, unlike most things that would - // choose two alternating buffers, because it helps with issues on - // Bluetooth headsets when recording and playing at the same time. - // See conversation in #8192 for details. - int numAudioBuffers = 3; - 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(numAudioBuffers, sizeof(AudioQueueBufferRef)); - if (device->hidden->audioBuffer == NULL) { - return false; - } - - #if DEBUG_COREAUDIO - SDL_Log("COREAUDIO: numAudioBuffers == %d", numAudioBuffers); - #endif - - 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->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); - CHECK_RESULT("AudioQueueEnqueueBuffer"); - } - - result = AudioQueueStart(device->hidden->audioQueue, NULL); - CHECK_RESULT("AudioQueueStart"); - - return true; // We're running! -} - -static int AudioQueueThreadEntry(void *arg) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)arg; - - if (device->recording) { - SDL_RecordingAudioThreadSetup(device); - } else { - SDL_PlaybackAudioThreadSetup(device); - } - - if (!PrepareAudioQueue(device)) { - device->hidden->thread_error = SDL_strdup(SDL_GetError()); - SDL_SignalSemaphore(device->hidden->ready_semaphore); - return 0; - } - - // init was successful, alert parent thread and start running... - SDL_SignalSemaphore(device->hidden->ready_semaphore); - - // This would be WaitDevice/WaitRecordingDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate. - while (!SDL_GetAtomicInt(&device->shutdown)) { - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); - } - - if (device->recording) { - SDL_RecordingAudioThreadShutdown(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_PlaybackAudioThreadShutdown(device); - } - - return 0; -} - -static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device) -{ - // Initialize all variables that we clean on shutdown - device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); - if (device->hidden == NULL) { - return false; - } - - #ifndef MACOSX_COREAUDIO - if (!UpdateAudioSession(device, true, true)) { - return false; - } - - // Stop CoreAudio from doing expensive audio rate conversion - @autoreleasepool { - AVAudioSession *session = [AVAudioSession sharedInstance]; - [session setPreferredSampleRate:device->spec.freq error:nil]; - device->spec.freq = (int)session.sampleRate; - #ifdef SDL_PLATFORM_TVOS - if (device->recording) { - [session setPreferredInputNumberOfChannels:device->spec.channels error:nil]; - device->spec.channels = (int)session.preferredInputNumberOfChannels; - } else { - [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil]; - device->spec.channels = (int)session.preferredOutputNumberOfChannels; - } - #else - // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS - #endif // SDL_PLATFORM_TVOS - } - #endif - - // Setup a AudioStreamBasicDescription with the requested format - AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; - strdesc->mFormatID = kAudioFormatLinearPCM; - strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; - strdesc->mChannelsPerFrame = device->spec.channels; - strdesc->mSampleRate = device->spec.freq; - strdesc->mFramesPerPacket = 1; - - 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) { - case SDL_AUDIO_U8: - case SDL_AUDIO_S8: - case SDL_AUDIO_S16LE: - case SDL_AUDIO_S16BE: - case SDL_AUDIO_S32LE: - case SDL_AUDIO_S32BE: - case SDL_AUDIO_F32LE: - case SDL_AUDIO_F32BE: - break; - - default: - continue; - } - break; - } - - if (!test_format) { // shouldn't happen, but just in case... - return SDL_SetError("%s: Unsupported audio format", "coreaudio"); - } - device->spec.format = test_format; - strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(test_format); - if (SDL_AUDIO_ISBIGENDIAN(test_format)) { - strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; - } - - if (SDL_AUDIO_ISFLOAT(test_format)) { - strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat; - } else if (SDL_AUDIO_ISSIGNED(test_format)) { - strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; - } - - strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8; - strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; - -#ifdef MACOSX_COREAUDIO - if (!PrepareDevice(device)) { - return false; - } -#endif - - // This has to init in a new thread so it can get its own CFRunLoop. :/ - device->hidden->ready_semaphore = SDL_CreateSemaphore(0); - if (!device->hidden->ready_semaphore) { - return false; // oh well. - } - - char threadname[64]; - SDL_GetAudioThreadName(device, threadname, sizeof(threadname)); - device->hidden->thread = SDL_CreateThread(AudioQueueThreadEntry, threadname, device); - if (!device->hidden->thread) { - return false; - } - - SDL_WaitSemaphore(device->hidden->ready_semaphore); - SDL_DestroySemaphore(device->hidden->ready_semaphore); - 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); -} - -static void COREAUDIO_DeinitializeStart(void) -{ -#ifdef MACOSX_COREAUDIO - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL); - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL); -#endif -} - -static bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) -{ - impl->OpenDevice = COREAUDIO_OpenDevice; - impl->PlayDevice = COREAUDIO_PlayDevice; - impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; - impl->RecordDevice = COREAUDIO_RecordDevice; - impl->FlushRecording = COREAUDIO_FlushRecording; - impl->CloseDevice = COREAUDIO_CloseDevice; - impl->DeinitializeStart = COREAUDIO_DeinitializeStart; - -#ifdef MACOSX_COREAUDIO - impl->DetectDevices = COREAUDIO_DetectDevices; - impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle; -#else - impl->OnlyHasDefaultPlaybackDevice = true; - impl->OnlyHasDefaultRecordingDevice = true; -#endif - - impl->ProvidesOwnCallbackThread = true; - impl->HasRecordingSupport = true; - - return true; -} - -AudioBootStrap COREAUDIO_bootstrap = { - "coreaudio", "CoreAudio", COREAUDIO_Init, false, false -}; - -#endif // SDL_AUDIO_DRIVER_COREAUDIO -- cgit v1.2.3