#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
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; } else if ((error == kAudioHardwareNoError) && (!alive)) {
dead = true; }
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);
}
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; }
}
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; }
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) {
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_small_free(devs, isstack);
}
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);
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
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; }
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; }
static void ResumeAudioDevices(void)
{
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL);
}
static void InterruptionBegin(SDL_AudioDevice *device)
{
if (device != NULL && device->hidden != 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 && (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) { if (device->recording) {
data->recording++;
} else {
data->playback++;
}
}
return false; }
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 ||
SDL_strcasecmp(hint, "ambient") == 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 {
return false;
}
} else if (data.recording) {
category = AVAudioSessionCategoryRecord;
}
#ifndef SDL_PLATFORM_TVOS
if (category == AVAudioSessionCategoryPlayAndRecord) {
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
}
#endif
if (category == AVAudioSessionCategoryRecord ||
category == AVAudioSessionCategoryPlayAndRecord) {
options |= 0x4; }
if (category == AVAudioSessionCategoryPlayAndRecord) {
options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP |
AVAudioSessionCategoryOptionAllowAirPlay;
}
if (category == AVAudioSessionCategoryPlayback ||
category == AVAudioSessionCategoryPlayAndRecord) {
options |= AVAudioSessionCategoryOptionDuckOthers;
}
if (![session.category isEqualToString:category] || session.categoryOptions != options) {
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];
[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); 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); 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); SDL_assert(device->hidden->current_buffer == NULL); device->hidden->current_buffer = inBuffer;
const bool okay = SDL_PlaybackAudioThreadIterate(device);
SDL_assert((device->hidden->current_buffer == NULL) || !okay);
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); SDL_assert(current_buffer->mAudioData != NULL);
SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); 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); return cpy;
}
static void COREAUDIO_FlushRecording(SDL_AudioDevice *device)
{
AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
if (current_buffer != NULL) { 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); SDL_assert(device->hidden->current_buffer == NULL); device->hidden->current_buffer = inBuffer;
SDL_RecordingAudioThreadIterate(device);
if (device->hidden->current_buffer != NULL) {
SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0);
COREAUDIO_FlushRecording(device); }
}
static void COREAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (!device->hidden) {
return;
}
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); 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);
}
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);
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.");
}
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); 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);
AudioChannelLayout layout;
SDL_zero(layout);
switch (device->spec.channels) {
case 1:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
break;
case 2:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
break;
case 3:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
break;
case 4:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
break;
case 5:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_6;
break;
case 6:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_12;
break;
case 7:
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_6_1;
} else {
layout.mChannelLayoutTag = 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, *)) {
layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_7_1;
} else {
layout.mChannelLayoutTag = 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)");
}
double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
#ifdef SDL_PLATFORM_IOS
if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
}
#endif
int numAudioBuffers = 3;
const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0;
if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { 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;
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; }
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;
}
SDL_SignalSemaphore(device->hidden->ready_semaphore);
while (!SDL_GetAtomicInt(&device->shutdown)) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
}
if (device->recording) {
SDL_RecordingAudioThreadShutdown(device);
} else {
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)
{
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;
}
@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
#endif }
#endif
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) {
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) { 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
device->hidden->ready_semaphore = SDL_CreateSemaphore(0);
if (!device->hidden->ready_semaphore) {
return false; }
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