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);
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];
331 NSString *category = AVAudioSessionCategoryAmbient;
332 NSString *
mode = AVAudioSessionModeDefault;
333 NSUInteger options = 0;
336 if (open_playback_devices && open_capture_devices) {
337 category = AVAudioSessionCategoryPlayAndRecord;
339 options = AVAudioSessionCategoryOptionDefaultToSpeaker;
341 }
else if (open_capture_devices) {
342 category = AVAudioSessionCategoryRecord;
347 category = AVAudioSessionCategoryAmbient;
348 }
else if (
SDL_strcasecmp(hint,
"AVAudioSessionCategorySoloAmbient") == 0) {
349 category = AVAudioSessionCategorySoloAmbient;
350 }
else if (
SDL_strcasecmp(hint,
"AVAudioSessionCategoryPlayback") == 0 ||
352 category = AVAudioSessionCategoryPlayback;
357 if ([session respondsToSelector:
@selector(setCategory:
mode:options:error:)]) {
358 if (![session setCategory:category
mode:
mode options:options error:&err]) {
359 NSString *desc = err.description;
360 SDL_SetError(
"Could not set Audio Session category: %s", desc.UTF8String);
364 if (![session setCategory:category error:&err]) {
365 NSString *desc = err.description;
366 SDL_SetError(
"Could not set Audio Session category: %s", desc.UTF8String);
371 if (open && (open_playback_devices + open_capture_devices) == 1) {
372 if (![session setActive:YES error:&err]) {
373 NSString *desc = err.description;
374 SDL_SetError(
"Could not activate Audio Session: %s", desc.UTF8String);
377 }
else if (!open_playback_devices && !open_capture_devices) {
378 [session setActive:NO error:nil];
382 SDLInterruptionListener *listener = [SDLInterruptionListener new];
383 listener.device =
this;
385 [center addObserver:listener
386 selector:@selector(audioSessionInterruption:)
387 name:AVAudioSessionInterruptionNotification
394 [center addObserver:listener
395 selector:@selector(applicationBecameActive:)
396 name:UIApplicationDidBecomeActiveNotification
399 [center addObserver:listener
400 selector:@selector(applicationBecameActive:)
401 name:UIApplicationWillEnterForegroundNotification
404 this->hidden->interruption_listener = CFBridgingRetain(listener);
406 if (this->hidden->interruption_listener !=
NULL) {
407 SDLInterruptionListener *listener = nil;
408 listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener);
409 [center removeObserver:listener];
410 @
synchronized (listener) {
411 listener.device =
NULL;
424 outputCallback(
void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
433 SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity);
434 }
else if (this->
stream ) {
435 UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
438 while (remaining > 0) {
442 (*this->callbackspec.callback)(this->callbackspec.userdata,
443 this->hidden->buffer, this->hidden->bufferSize);
445 this->hidden->bufferOffset = 0;
451 if (
len > remaining )
463 UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
466 while (remaining > 0) {
468 if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
471 (*this->callbackspec.callback)(this->callbackspec.userdata,
472 this->hidden->buffer, this->hidden->bufferSize);
474 this->hidden->bufferOffset = 0;
477 len = this->hidden->bufferSize - this->hidden->bufferOffset;
478 if (
len > remaining) {
482 this->hidden->bufferOffset,
len);
485 this->hidden->bufferOffset +=
len;
489 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0,
NULL);
491 inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
495 inputCallback(
void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
496 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
497 const AudioStreamPacketDescription *inPacketDescs )
508 UInt32 remaining = inBuffer->mAudioDataByteSize;
509 while (remaining > 0) {
510 UInt32
len = this->hidden->bufferSize - this->hidden->bufferOffset;
511 if (
len > remaining) {
515 SDL_memcpy((
char *)this->hidden->buffer + this->hidden->bufferOffset,
ptr,
len);
518 this->hidden->bufferOffset +=
len;
520 if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
522 (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize);
524 this->hidden->bufferOffset = 0;
529 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0,
NULL);
534 static const AudioObjectPropertyAddress alive_address =
536 kAudioDevicePropertyDeviceIsAlive,
537 kAudioObjectPropertyScopeGlobal,
538 kAudioObjectPropertyElementMaster
542 device_unplugged(AudioObjectID devid, UInt32 num_addr,
const AudioObjectPropertyAddress *addrs,
void *
data)
547 UInt32
size =
sizeof (isAlive);
554 error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
557 if (error == kAudioHardwareBadDeviceError) {
559 }
else if ((error == kAudioHardwareNoError) && (!isAlive)) {
572 COREAUDIO_CloseDevice(
_THIS)
574 const SDL_bool iscapture = this->iscapture;
580 AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged,
this);
584 open_capture_devices--;
586 open_playback_devices--;
589 #if !MACOSX_COREAUDIO
596 if (this->hidden->audioQueue) {
597 AudioQueueDispose(this->hidden->audioQueue, 1);
600 if (this->hidden->thread) {
605 if (this->hidden->ready_semaphore) {
610 SDL_free(this->hidden->audioBuffer);
611 SDL_free(this->hidden->thread_error);
620 AudioDeviceID devid = (AudioDeviceID) ((
size_t)
handle);
626 AudioObjectPropertyAddress
addr = {
628 kAudioObjectPropertyScopeGlobal,
629 kAudioObjectPropertyElementMaster
633 size =
sizeof (AudioDeviceID);
635 ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
636 kAudioHardwarePropertyDefaultOutputDevice);
637 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &
addr,
639 CHECK_RESULT(
"AudioHardwareGetProperty (default device)");
642 addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
643 addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
644 kAudioDevicePropertyScopeOutput;
649 (
"AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
652 SDL_SetError(
"CoreAudio: requested device exists, but isn't alive.");
656 addr.mSelector = kAudioDevicePropertyHogMode;
661 if ((
result == noErr) && (pid != -1)) {
662 SDL_SetError(
"CoreAudio: requested device is being hogged.");
666 this->hidden->deviceID = devid;
672 prepare_audioqueue(
_THIS)
674 const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
675 const int iscapture = this->iscapture;
682 result = AudioQueueNewInput(strdesc, inputCallback,
this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
683 CHECK_RESULT(
"AudioQueueNewInput");
685 result = AudioQueueNewOutput(strdesc, outputCallback,
this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
686 CHECK_RESULT(
"AudioQueueNewOutput");
691 const AudioObjectPropertyAddress prop = {
692 kAudioDevicePropertyDeviceUID,
693 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
694 kAudioObjectPropertyElementMaster
697 UInt32 devuidsize =
sizeof (devuid);
698 result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0,
NULL, &devuidsize, &devuid);
699 CHECK_RESULT(
"AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
700 result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
701 CHECK_RESULT(
"AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
706 AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged,
this);
714 AudioChannelLayout layout;
718 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
721 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
724 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
727 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
730 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_0_A;
733 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A;
737 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
740 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A;
743 if (layout.mChannelLayoutTag != 0) {
744 result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout,
sizeof(layout));
745 CHECK_RESULT(
"AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)");
749 this->hidden->bufferSize = this->
spec.
size;
750 this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
752 this->hidden->buffer =
SDL_malloc(this->hidden->bufferSize);
753 if (this->hidden->buffer ==
NULL) {
759 double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
760 #if defined(__IPHONEOS__)
761 if (
floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
763 MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
767 int numAudioBuffers = 2;
768 if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) {
769 numAudioBuffers = ((int)
SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
772 this->hidden->audioBuffer =
SDL_calloc(1,
sizeof (AudioQueueBufferRef) * numAudioBuffers);
773 if (this->hidden->audioBuffer ==
NULL) {
779 printf(
"COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
782 for (
i = 0;
i < numAudioBuffers;
i++) {
783 result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[
i]);
784 CHECK_RESULT(
"AudioQueueAllocateBuffer");
785 SDL_memset(this->hidden->audioBuffer[
i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[
i]->mAudioDataBytesCapacity);
786 this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
787 result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[
i], 0,
NULL);
788 CHECK_RESULT(
"AudioQueueEnqueueBuffer");
791 result = AudioQueueStart(this->hidden->audioQueue,
NULL);
792 CHECK_RESULT(
"AudioQueueStart");
799 audioqueue_thread(
void *arg)
802 const int rc = prepare_audioqueue(
this);
814 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
817 if (!this->iscapture) {
819 CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
826 COREAUDIO_OpenDevice(
_THIS,
void *
handle,
const char *devname,
int iscapture)
828 AudioStreamBasicDescription *strdesc;
830 int valid_datatype = 0;
835 if (this->hidden ==
NULL) {
840 strdesc = &this->hidden->strdesc;
843 open_capture_devices++;
845 open_playback_devices++;
848 #if !MACOSX_COREAUDIO
849 if (!update_audio_session(
this,
SDL_TRUE)) {
855 AVAudioSession* session = [AVAudioSession sharedInstance];
856 [session setPreferredSampleRate:this->spec.freq error:nil];
857 this->
spec.
freq = (int)session.sampleRate;
860 [session setPreferredInputNumberOfChannels:this->spec.channels error:nil];
861 this->
spec.
channels = session.preferredInputNumberOfChannels;
863 [session setPreferredOutputNumberOfChannels:this->spec.channels error:nil];
864 this->
spec.
channels = session.preferredOutputNumberOfChannels;
874 strdesc->mFormatID = kAudioFormatLinearPCM;
875 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
877 strdesc->mSampleRate = this->
spec.
freq;
878 strdesc->mFramesPerPacket = 1;
880 while ((!valid_datatype) && (test_format)) {
883 switch (test_format) {
895 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
898 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
900 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
909 if (!valid_datatype) {
925 if (!this->hidden->ready_semaphore) {
930 if (!this->hidden->thread) {
936 this->hidden->ready_semaphore =
NULL;
938 if ((this->hidden->thread !=
NULL) && (this->hidden->thread_error !=
NULL)) {
943 return (this->hidden->thread !=
NULL) ? 0 : -1;
947 COREAUDIO_Deinitialize(
void)
950 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed,
NULL);
951 free_audio_device_list(&capture_devs);
952 free_audio_device_list(&output_devs);
966 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed,
NULL);
979 "coreaudio",
"CoreAudio", COREAUDIO_Init, 0