21 #include "../../SDL_internal.h"
23 #if SDL_AUDIO_DRIVER_COREAUDIO
29 #include "../SDL_audio_c.h"
30 #include "../SDL_sysaudio.h"
33 #include "../../thread/SDL_systhread.h"
35 #define DEBUG_COREAUDIO 0
37 #define CHECK_RESULT(msg) \
38 if (result != noErr) { \
39 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
44 static const AudioObjectPropertyAddress devlist_address = {
45 kAudioHardwarePropertyDevices,
46 kAudioObjectPropertyScopeGlobal,
47 kAudioObjectPropertyElementMaster
50 typedef void (*addDevFn)(
const char *
name,
const int iscapture, AudioDeviceID devId,
void *
data);
52 typedef struct AudioDeviceList
56 struct AudioDeviceList *next;
59 static AudioDeviceList *output_devs =
NULL;
60 static AudioDeviceList *capture_devs =
NULL;
63 add_to_internal_dev_list(
const int iscapture, AudioDeviceID devId)
65 AudioDeviceList *item = (AudioDeviceList *)
SDL_malloc(
sizeof (AudioDeviceList));
71 item->next = iscapture ? capture_devs : output_devs;
82 addToDevList(
const char *
name,
const int iscapture, AudioDeviceID devId,
void *
data)
84 if (add_to_internal_dev_list(iscapture, devId)) {
90 build_device_list(
int iscapture, addDevFn addfn,
void *addfndata)
94 AudioDeviceID *devs =
NULL;
98 result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
100 if (
result != kAudioHardwareNoError)
103 devs = (AudioDeviceID *) alloca(
size);
107 result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
108 &devlist_address, 0,
NULL, &
size, devs);
109 if (
result != kAudioHardwareNoError)
112 max =
size /
sizeof (AudioDeviceID);
113 for (
i = 0;
i < max;
i++) {
114 CFStringRef cfstr =
NULL;
116 AudioDeviceID dev = devs[i];
117 AudioBufferList *buflist =
NULL;
120 const AudioObjectPropertyAddress
addr = {
121 kAudioDevicePropertyStreamConfiguration,
122 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
123 kAudioObjectPropertyElementMaster
126 const AudioObjectPropertyAddress nameaddr = {
127 kAudioObjectPropertyName,
128 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
129 kAudioObjectPropertyElementMaster
145 for (
j = 0;
j < buflist->mNumberBuffers;
j++) {
146 if (buflist->mBuffers[
j].mNumberChannels > 0) {
159 size =
sizeof (CFStringRef);
160 result = AudioObjectGetPropertyData(dev, &nameaddr, 0,
NULL, &
size, &cfstr);
161 if (
result != kAudioHardwareNoError)
164 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
165 kCFStringEncodingUTF8);
168 usable = ((ptr !=
NULL) &&
170 (cfstr, ptr,
len + 1, kCFStringEncodingUTF8)));
177 while ((
len > 0) && (ptr[
len - 1] ==
' ')) {
187 printf(
"COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
188 ((iscapture) ?
"capture" :
"output"),
189 (
int)
i, ptr, (
int) dev);
191 addfn(ptr, iscapture, dev, addfndata);
198 free_audio_device_list(AudioDeviceList **list)
200 AudioDeviceList *item = *list;
202 AudioDeviceList *next = item->next;
210 COREAUDIO_DetectDevices(
void)
217 build_device_change_list(
const char *
name,
const int iscapture, AudioDeviceID devId,
void *
data)
219 AudioDeviceList **list = (AudioDeviceList **)
data;
220 AudioDeviceList *item;
221 for (item = *list; item !=
NULL; item = item->next) {
222 if (item->devid == devId) {
228 add_to_internal_dev_list(iscapture, devId);
233 reprocess_device_list(
const int iscapture, AudioDeviceList **list)
235 AudioDeviceList *item;
236 AudioDeviceList *prev =
NULL;
237 for (item = *list; item !=
NULL; item = item->next) {
241 build_device_list(iscapture, build_device_change_list, list);
245 while (item !=
NULL) {
246 AudioDeviceList *next = item->next;
252 prev->next = item->next;
264 device_list_changed(AudioObjectID systemObj, UInt32 num_addr,
const AudioObjectPropertyAddress *addrs,
void *
data)
266 reprocess_device_list(
SDL_TRUE, &capture_devs);
267 reprocess_device_list(
SDL_FALSE, &output_devs);
273 static int open_playback_devices = 0;
274 static int open_capture_devices = 0;
276 #if !MACOSX_COREAUDIO
278 static void interruption_begin(
_THIS)
280 if (
this !=
NULL && this->hidden->audioQueue !=
NULL) {
281 this->hidden->interrupted =
SDL_TRUE;
282 AudioQueuePause(this->hidden->audioQueue);
286 static void interruption_end(
_THIS)
288 if (
this !=
NULL && this->hidden !=
NULL && this->hidden->audioQueue !=
NULL
289 && this->hidden->interrupted
290 && AudioQueueStart(this->hidden->audioQueue,
NULL) == AVAudioSessionErrorCodeNone) {
295 @interface SDLInterruptionListener : NSObject
301 @implementation SDLInterruptionListener
303 - (
void)audioSessionInterruption:(NSNotification *)note
305 @
synchronized (
self) {
306 NSNumber *
type = note.userInfo[AVAudioSessionInterruptionTypeKey];
307 if (
type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
308 interruption_begin(
self.
device);
310 interruption_end(
self.
device);
315 - (
void)applicationBecameActive:(NSNotification *)note
317 @
synchronized (
self) {
318 interruption_end(
self.
device);
327 AVAudioSession *session = [AVAudioSession sharedInstance];
328 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
330 NSString *category = AVAudioSessionCategoryAmbient;
333 if (open_playback_devices && open_capture_devices) {
334 category = AVAudioSessionCategoryPlayAndRecord;
335 }
else if (open_capture_devices) {
336 category = AVAudioSessionCategoryRecord;
341 category = AVAudioSessionCategoryAmbient;
342 }
else if (
SDL_strcasecmp(hint,
"AVAudioSessionCategorySoloAmbient") == 0) {
343 category = AVAudioSessionCategorySoloAmbient;
344 }
else if (
SDL_strcasecmp(hint,
"AVAudioSessionCategoryPlayback") == 0 ||
346 category = AVAudioSessionCategoryPlayback;
351 if (![session setCategory:category error:&err]) {
352 NSString *desc = err.description;
353 SDL_SetError(
"Could not set Audio Session category: %s", desc.UTF8String);
357 if (open && (open_playback_devices + open_capture_devices) == 1) {
358 if (![session setActive:YES error:&err]) {
359 NSString *desc = err.description;
360 SDL_SetError(
"Could not activate Audio Session: %s", desc.UTF8String);
363 }
else if (!open_playback_devices && !open_capture_devices) {
364 [session setActive:NO error:nil];
368 SDLInterruptionListener *listener = [SDLInterruptionListener new];
369 listener.device =
this;
371 [center addObserver:listener
372 selector:@selector(audioSessionInterruption:)
373 name:AVAudioSessionInterruptionNotification
380 [center addObserver:listener
381 selector:@selector(applicationBecameActive:)
382 name:UIApplicationDidBecomeActiveNotification
385 [center addObserver:listener
386 selector:@selector(applicationBecameActive:)
387 name:UIApplicationWillEnterForegroundNotification
390 this->hidden->interruption_listener = CFBridgingRetain(listener);
392 if (this->hidden->interruption_listener !=
NULL) {
393 SDLInterruptionListener *listener = nil;
394 listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener);
395 [center removeObserver:listener];
396 @
synchronized (listener) {
397 listener.device =
NULL;
410 outputCallback(
void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
419 SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity);
421 UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
424 while (remaining > 0) {
426 if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
429 (*this->callbackspec.callback)(this->callbackspec.userdata,
430 this->hidden->buffer, this->hidden->bufferSize);
432 this->hidden->bufferOffset = 0;
435 len = this->hidden->bufferSize - this->hidden->bufferOffset;
436 if (
len > remaining) {
439 SDL_memcpy(ptr, (
char *)this->hidden->buffer +
440 this->hidden->bufferOffset,
len);
443 this->hidden->bufferOffset +=
len;
447 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0,
NULL);
449 inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
453 inputCallback(
void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
454 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
455 const AudioStreamPacketDescription *inPacketDescs )
465 const Uint8 *ptr = (
const Uint8 *) inBuffer->mAudioData;
466 UInt32 remaining = inBuffer->mAudioDataByteSize;
467 while (remaining > 0) {
468 UInt32
len = this->hidden->bufferSize - this->hidden->bufferOffset;
469 if (
len > remaining) {
473 SDL_memcpy((
char *)this->hidden->buffer + this->hidden->bufferOffset, ptr,
len);
476 this->hidden->bufferOffset +=
len;
478 if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
480 (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize);
482 this->hidden->bufferOffset = 0;
487 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0,
NULL);
492 static const AudioObjectPropertyAddress alive_address =
494 kAudioDevicePropertyDeviceIsAlive,
495 kAudioObjectPropertyScopeGlobal,
496 kAudioObjectPropertyElementMaster
500 device_unplugged(AudioObjectID devid, UInt32 num_addr,
const AudioObjectPropertyAddress *addrs,
void *
data)
505 UInt32
size =
sizeof (isAlive);
512 error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
515 if (error == kAudioHardwareBadDeviceError) {
517 }
else if ((error == kAudioHardwareNoError) && (!isAlive)) {
530 COREAUDIO_CloseDevice(
_THIS)
532 const SDL_bool iscapture = this->iscapture;
538 AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged,
this);
541 #if !MACOSX_COREAUDIO
548 if (this->hidden->audioQueue) {
549 AudioQueueDispose(this->hidden->audioQueue, 1);
552 if (this->hidden->thread) {
557 if (this->hidden->ready_semaphore) {
562 SDL_free(this->hidden->audioBuffer);
563 SDL_free(this->hidden->thread_error);
568 open_capture_devices--;
570 open_playback_devices--;
578 AudioDeviceID devid = (AudioDeviceID) ((
size_t)
handle);
584 AudioObjectPropertyAddress
addr = {
586 kAudioObjectPropertyScopeGlobal,
587 kAudioObjectPropertyElementMaster
591 size =
sizeof (AudioDeviceID);
593 ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
594 kAudioHardwarePropertyDefaultOutputDevice);
595 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &
addr,
597 CHECK_RESULT(
"AudioHardwareGetProperty (default device)");
600 addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
601 addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
602 kAudioDevicePropertyScopeOutput;
607 (
"AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
610 SDL_SetError(
"CoreAudio: requested device exists, but isn't alive.");
614 addr.mSelector = kAudioDevicePropertyHogMode;
619 if ((
result == noErr) && (pid != -1)) {
620 SDL_SetError(
"CoreAudio: requested device is being hogged.");
624 this->hidden->deviceID = devid;
630 prepare_audioqueue(
_THIS)
632 const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
633 const int iscapture = this->iscapture;
640 result = AudioQueueNewInput(strdesc, inputCallback,
this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
641 CHECK_RESULT(
"AudioQueueNewInput");
643 result = AudioQueueNewOutput(strdesc, outputCallback,
this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
644 CHECK_RESULT(
"AudioQueueNewOutput");
649 const AudioObjectPropertyAddress prop = {
650 kAudioDevicePropertyDeviceUID,
651 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
652 kAudioObjectPropertyElementMaster
655 UInt32 devuidsize =
sizeof (devuid);
656 result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0,
NULL, &devuidsize, &devuid);
657 CHECK_RESULT(
"AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
658 result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
659 CHECK_RESULT(
"AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
664 AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged,
this);
672 this->hidden->bufferSize = this->
spec.
size;
673 this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
675 this->hidden->buffer =
SDL_malloc(this->hidden->bufferSize);
676 if (this->hidden->buffer ==
NULL) {
682 double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
683 #if defined(__IPHONEOS__)
684 if (
floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
686 MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
690 int numAudioBuffers = 2;
691 if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) {
692 numAudioBuffers = ((int)
SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
695 this->hidden->audioBuffer =
SDL_calloc(1,
sizeof (AudioQueueBufferRef) * numAudioBuffers);
696 if (this->hidden->audioBuffer ==
NULL) {
702 printf(
"COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
705 for (
i = 0;
i < numAudioBuffers;
i++) {
706 result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[
i]);
707 CHECK_RESULT(
"AudioQueueAllocateBuffer");
708 SDL_memset(this->hidden->audioBuffer[
i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[
i]->mAudioDataBytesCapacity);
709 this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
710 result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[
i], 0,
NULL);
711 CHECK_RESULT(
"AudioQueueEnqueueBuffer");
714 result = AudioQueueStart(this->hidden->audioQueue,
NULL);
715 CHECK_RESULT(
"AudioQueueStart");
722 audioqueue_thread(
void *arg)
725 const int rc = prepare_audioqueue(
this);
737 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
740 if (!this->iscapture) {
742 CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
749 COREAUDIO_OpenDevice(
_THIS,
void *
handle,
const char *devname,
int iscapture)
751 AudioStreamBasicDescription *strdesc;
753 int valid_datatype = 0;
758 if (this->hidden ==
NULL) {
763 strdesc = &this->hidden->strdesc;
766 open_capture_devices++;
768 open_playback_devices++;
771 #if !MACOSX_COREAUDIO
772 if (!update_audio_session(
this,
SDL_TRUE)) {
778 AVAudioSession* session = [AVAudioSession sharedInstance];
779 [session setPreferredSampleRate:this->spec.freq error:nil];
780 this->
spec.
freq = (int)session.sampleRate;
786 strdesc->mFormatID = kAudioFormatLinearPCM;
787 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
789 strdesc->mSampleRate = this->
spec.
freq;
790 strdesc->mFramesPerPacket = 1;
792 while ((!valid_datatype) && (test_format)) {
795 switch (test_format) {
809 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
812 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
814 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
819 if (!valid_datatype) {
835 if (!this->hidden->ready_semaphore) {
840 if (!this->hidden->thread) {
846 this->hidden->ready_semaphore =
NULL;
848 if ((this->hidden->thread !=
NULL) && (this->hidden->thread_error !=
NULL)) {
853 return (this->hidden->thread !=
NULL) ? 0 : -1;
857 COREAUDIO_Deinitialize(
void)
860 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed,
NULL);
861 free_audio_device_list(&capture_devs);
862 free_audio_device_list(&output_devs);
876 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed,
NULL);
889 "coreaudio",
"CoreAudio", COREAUDIO_Init, 0