src/audio/coreaudio/SDL_coreaudio.m
author Sam Lantinga <slouken@libsdl.org>
Thu, 24 May 2018 07:30:24 -0700
changeset 12001 84c2582329b0
parent 11945 ffd52bb02bcc
child 12503 806492103856
permissions -rw-r--r--
Fixed bug 4169 - Crash due to audio session observer race condition

Jona

The following explains why this bug was happening:
This crash was caused because the audio session was being set as active [session setActive:YES error:&err] when the audio device was actually being CLOSED. Certain cases the audio session being set to active would fail and the method would return right away. Because of the way the error was handled we never removed the SDLInterruptionListener thus leaking it. Later when an interruption was received the THIS_ object would contain a pointer to an already released device causing the crash.

The fix:
When only one device remained open and it was being closed we needed to set the audio session as NOT active and completely ignore the returned error to successfully release the SDLInterruptionListener. I think the user assumed that the open_playback_devices and open_capture_devices would equal 0 when all of them where closed but the truth is that at the end of the closing process that the open devices count is decremented.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #if SDL_AUDIO_DRIVER_COREAUDIO
    24 
    25 /* !!! FIXME: clean out some of the macro salsa in here. */
    26 
    27 #include "SDL_audio.h"
    28 #include "SDL_hints.h"
    29 #include "SDL_timer.h"
    30 #include "../SDL_audio_c.h"
    31 #include "../SDL_sysaudio.h"
    32 #include "SDL_coreaudio.h"
    33 #include "SDL_assert.h"
    34 #include "../../thread/SDL_systhread.h"
    35 
    36 #define DEBUG_COREAUDIO 0
    37 
    38 #define CHECK_RESULT(msg) \
    39     if (result != noErr) { \
    40         SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
    41         return 0; \
    42     }
    43 
    44 #if MACOSX_COREAUDIO
    45 static const AudioObjectPropertyAddress devlist_address = {
    46     kAudioHardwarePropertyDevices,
    47     kAudioObjectPropertyScopeGlobal,
    48     kAudioObjectPropertyElementMaster
    49 };
    50 
    51 typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
    52 
    53 typedef struct AudioDeviceList
    54 {
    55     AudioDeviceID devid;
    56     SDL_bool alive;
    57     struct AudioDeviceList *next;
    58 } AudioDeviceList;
    59 
    60 static AudioDeviceList *output_devs = NULL;
    61 static AudioDeviceList *capture_devs = NULL;
    62 
    63 static SDL_bool
    64 add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
    65 {
    66     AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
    67     if (item == NULL) {
    68         return SDL_FALSE;
    69     }
    70     item->devid = devId;
    71     item->alive = SDL_TRUE;
    72     item->next = iscapture ? capture_devs : output_devs;
    73     if (iscapture) {
    74         capture_devs = item;
    75     } else {
    76         output_devs = item;
    77     }
    78 
    79     return SDL_TRUE;
    80 }
    81 
    82 static void
    83 addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
    84 {
    85     if (add_to_internal_dev_list(iscapture, devId)) {
    86         SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
    87     }
    88 }
    89 
    90 static void
    91 build_device_list(int iscapture, addDevFn addfn, void *addfndata)
    92 {
    93     OSStatus result = noErr;
    94     UInt32 size = 0;
    95     AudioDeviceID *devs = NULL;
    96     UInt32 i = 0;
    97     UInt32 max = 0;
    98 
    99     result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
   100                                             &devlist_address, 0, NULL, &size);
   101     if (result != kAudioHardwareNoError)
   102         return;
   103 
   104     devs = (AudioDeviceID *) alloca(size);
   105     if (devs == NULL)
   106         return;
   107 
   108     result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
   109                                         &devlist_address, 0, NULL, &size, devs);
   110     if (result != kAudioHardwareNoError)
   111         return;
   112 
   113     max = size / sizeof (AudioDeviceID);
   114     for (i = 0; i < max; i++) {
   115         CFStringRef cfstr = NULL;
   116         char *ptr = NULL;
   117         AudioDeviceID dev = devs[i];
   118         AudioBufferList *buflist = NULL;
   119         int usable = 0;
   120         CFIndex len = 0;
   121         const AudioObjectPropertyAddress addr = {
   122             kAudioDevicePropertyStreamConfiguration,
   123             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   124             kAudioObjectPropertyElementMaster
   125         };
   126 
   127         const AudioObjectPropertyAddress nameaddr = {
   128             kAudioObjectPropertyName,
   129             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   130             kAudioObjectPropertyElementMaster
   131         };
   132 
   133         result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
   134         if (result != noErr)
   135             continue;
   136 
   137         buflist = (AudioBufferList *) SDL_malloc(size);
   138         if (buflist == NULL)
   139             continue;
   140 
   141         result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
   142                                             &size, buflist);
   143 
   144         if (result == noErr) {
   145             UInt32 j;
   146             for (j = 0; j < buflist->mNumberBuffers; j++) {
   147                 if (buflist->mBuffers[j].mNumberChannels > 0) {
   148                     usable = 1;
   149                     break;
   150                 }
   151             }
   152         }
   153 
   154         SDL_free(buflist);
   155 
   156         if (!usable)
   157             continue;
   158 
   159 
   160         size = sizeof (CFStringRef);
   161         result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
   162         if (result != kAudioHardwareNoError)
   163             continue;
   164 
   165         len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
   166                                                 kCFStringEncodingUTF8);
   167 
   168         ptr = (char *) SDL_malloc(len + 1);
   169         usable = ((ptr != NULL) &&
   170                   (CFStringGetCString
   171                    (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));
   172 
   173         CFRelease(cfstr);
   174 
   175         if (usable) {
   176             len = strlen(ptr);
   177             /* Some devices have whitespace at the end...trim it. */
   178             while ((len > 0) && (ptr[len - 1] == ' ')) {
   179                 len--;
   180             }
   181             usable = (len > 0);
   182         }
   183 
   184         if (usable) {
   185             ptr[len] = '\0';
   186 
   187 #if DEBUG_COREAUDIO
   188             printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
   189                    ((iscapture) ? "capture" : "output"),
   190                    (int) i, ptr, (int) dev);
   191 #endif
   192             addfn(ptr, iscapture, dev, addfndata);
   193         }
   194         SDL_free(ptr);  /* addfn() would have copied the string. */
   195     }
   196 }
   197 
   198 static void
   199 free_audio_device_list(AudioDeviceList **list)
   200 {
   201     AudioDeviceList *item = *list;
   202     while (item) {
   203         AudioDeviceList *next = item->next;
   204         SDL_free(item);
   205         item = next;
   206     }
   207     *list = NULL;
   208 }
   209 
   210 static void
   211 COREAUDIO_DetectDevices(void)
   212 {
   213     build_device_list(SDL_TRUE, addToDevList, NULL);
   214     build_device_list(SDL_FALSE, addToDevList, NULL);
   215 }
   216 
   217 static void
   218 build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
   219 {
   220     AudioDeviceList **list = (AudioDeviceList **) data;
   221     AudioDeviceList *item;
   222     for (item = *list; item != NULL; item = item->next) {
   223         if (item->devid == devId) {
   224             item->alive = SDL_TRUE;
   225             return;
   226         }
   227     }
   228 
   229     add_to_internal_dev_list(iscapture, devId);  /* new device, add it. */
   230     SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
   231 }
   232 
   233 static void
   234 reprocess_device_list(const int iscapture, AudioDeviceList **list)
   235 {
   236     AudioDeviceList *item;
   237     AudioDeviceList *prev = NULL;
   238     for (item = *list; item != NULL; item = item->next) {
   239         item->alive = SDL_FALSE;
   240     }
   241 
   242     build_device_list(iscapture, build_device_change_list, list);
   243 
   244     /* free items in the list that aren't still alive. */
   245     item = *list;
   246     while (item != NULL) {
   247         AudioDeviceList *next = item->next;
   248         if (item->alive) {
   249             prev = item;
   250         } else {
   251             SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid));
   252             if (prev) {
   253                 prev->next = item->next;
   254             } else {
   255                 *list = item->next;
   256             }
   257             SDL_free(item);
   258         }
   259         item = next;
   260     }
   261 }
   262 
   263 /* this is called when the system's list of available audio devices changes. */
   264 static OSStatus
   265 device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
   266 {
   267     reprocess_device_list(SDL_TRUE, &capture_devs);
   268     reprocess_device_list(SDL_FALSE, &output_devs);
   269     return 0;
   270 }
   271 #endif
   272 
   273 
   274 static int open_playback_devices = 0;
   275 static int open_capture_devices = 0;
   276 
   277 #if !MACOSX_COREAUDIO
   278 
   279 static void interruption_begin(_THIS)
   280 {
   281     if (this != NULL && this->hidden->audioQueue != NULL) {
   282         this->hidden->interrupted = SDL_TRUE;
   283         AudioQueuePause(this->hidden->audioQueue);
   284     }
   285 }
   286 
   287 static void interruption_end(_THIS)
   288 {
   289     if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL
   290     && this->hidden->interrupted
   291     && AudioQueueStart(this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) {
   292         this->hidden->interrupted = SDL_FALSE;
   293     }
   294 }
   295 
   296 @interface SDLInterruptionListener : NSObject
   297 
   298 @property (nonatomic, assign) SDL_AudioDevice *device;
   299 
   300 @end
   301 
   302 @implementation SDLInterruptionListener
   303 
   304 - (void)audioSessionInterruption:(NSNotification *)note
   305 {
   306     @synchronized (self) {
   307         NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
   308         if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
   309             interruption_begin(self.device);
   310         } else {
   311             interruption_end(self.device);
   312         }
   313     }
   314 }
   315 
   316 - (void)applicationBecameActive:(NSNotification *)note
   317 {
   318     @synchronized (self) {
   319         interruption_end(self.device);
   320     }
   321 }
   322 
   323 @end
   324 
   325 static BOOL update_audio_session(_THIS, SDL_bool open)
   326 {
   327     @autoreleasepool {
   328         AVAudioSession *session = [AVAudioSession sharedInstance];
   329         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   330         /* Set category to ambient by default so that other music continues playing. */
   331         NSString *category = AVAudioSessionCategoryAmbient;
   332         NSError *err = nil;
   333 
   334         if (open_playback_devices && open_capture_devices) {
   335             category = AVAudioSessionCategoryPlayAndRecord;
   336         } else if (open_capture_devices) {
   337             category = AVAudioSessionCategoryRecord;
   338         } else {
   339             const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
   340             if (hint) {
   341                 if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
   342                     category = AVAudioSessionCategoryAmbient;
   343                 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
   344                     category = AVAudioSessionCategorySoloAmbient;
   345                 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 ||
   346                            SDL_strcasecmp(hint, "playback") == 0) {
   347                     category = AVAudioSessionCategoryPlayback;
   348                 }
   349             }
   350         }
   351 
   352         if (![session setCategory:category error:&err]) {
   353             NSString *desc = err.description;
   354             SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
   355             return NO;
   356         }
   357 
   358         if (open && (open_playback_devices + open_capture_devices) == 1) {
   359             if (![session setActive:YES error:&err]) {
   360                 NSString *desc = err.description;
   361                 SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
   362                 return NO;
   363             }
   364         } else if (!open_playback_devices && !open_capture_devices) {
   365             [session setActive:NO error:nil];
   366         }
   367 
   368         if (open) {
   369             SDLInterruptionListener *listener = [SDLInterruptionListener new];
   370             listener.device = this;
   371 
   372             [center addObserver:listener
   373                        selector:@selector(audioSessionInterruption:)
   374                            name:AVAudioSessionInterruptionNotification
   375                          object:session];
   376 
   377             /* An interruption end notification is not guaranteed to be sent if
   378              we were previously interrupted... resuming if needed when the app
   379              becomes active seems to be the way to go. */
   380             [center addObserver:listener
   381                        selector:@selector(applicationBecameActive:)
   382                            name:UIApplicationDidBecomeActiveNotification
   383                          object:session];
   384 
   385             [center addObserver:listener
   386                        selector:@selector(applicationBecameActive:)
   387                            name:UIApplicationWillEnterForegroundNotification
   388                          object:session];
   389 
   390             this->hidden->interruption_listener = CFBridgingRetain(listener);
   391         } else {
   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;
   398                 }
   399             }
   400         }
   401     }
   402 
   403     return YES;
   404 }
   405 #endif
   406 
   407 
   408 /* The AudioQueue callback */
   409 static void
   410 outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
   411 {
   412     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
   413     SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize);
   414     SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize);
   415     SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize);  /* zero out in case we have to fill again without new data. */
   416     inBuffer->mAudioDataByteSize = this->hidden->bufferSize;
   417     AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
   418     this->hidden->refill = SDL_TRUE;
   419 }
   420 
   421 static Uint8 *
   422 COREAUDIO_GetDeviceBuf(_THIS)
   423 {
   424     return this->hidden->buffer;
   425 }
   426 
   427 static void
   428 COREAUDIO_WaitDevice(_THIS)
   429 {
   430     while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
   431         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
   432     }
   433     this->hidden->refill = SDL_FALSE;
   434 }
   435 
   436 static void
   437 inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
   438               const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
   439               const AudioStreamPacketDescription *inPacketDescs )
   440 {
   441     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
   442     if (SDL_AtomicGet(&this->enabled)) {
   443         SDL_AudioStream *stream = this->hidden->capturestream;
   444         if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) {
   445             /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */
   446         }
   447         AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
   448         this->hidden->refill = SDL_TRUE;
   449     }
   450 }
   451 
   452 static int
   453 COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
   454 {
   455     SDL_AudioStream *stream = this->hidden->capturestream;
   456     while (SDL_AtomicGet(&this->enabled)) {
   457         const int avail = SDL_AudioStreamAvailable(stream);
   458         if (avail > 0) {
   459             const int cpy = SDL_min(buflen, avail);
   460             SDL_AudioStreamGet(stream, buffer, cpy);
   461             return cpy;
   462         }
   463 
   464         /* wait for more data, try again. */
   465         while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
   466             CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
   467         }
   468         this->hidden->refill = SDL_FALSE;
   469     }
   470 
   471     return 0;  /* not enabled, giving up. */
   472 }
   473 
   474 static void
   475 COREAUDIO_FlushCapture(_THIS)
   476 {
   477     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) {
   478         /* spin. */
   479     }
   480     this->hidden->refill = SDL_FALSE;
   481     SDL_AudioStreamClear(this->hidden->capturestream);
   482 }
   483 
   484 
   485 #if MACOSX_COREAUDIO
   486 static const AudioObjectPropertyAddress alive_address =
   487 {
   488     kAudioDevicePropertyDeviceIsAlive,
   489     kAudioObjectPropertyScopeGlobal,
   490     kAudioObjectPropertyElementMaster
   491 };
   492 
   493 static OSStatus
   494 device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
   495 {
   496     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   497     SDL_bool dead = SDL_FALSE;
   498     UInt32 isAlive = 1;
   499     UInt32 size = sizeof (isAlive);
   500     OSStatus error;
   501 
   502     if (!SDL_AtomicGet(&this->enabled)) {
   503         return 0;  /* already known to be dead. */
   504     }
   505 
   506     error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
   507                                        0, NULL, &size, &isAlive);
   508 
   509     if (error == kAudioHardwareBadDeviceError) {
   510         dead = SDL_TRUE;  /* device was unplugged. */
   511     } else if ((error == kAudioHardwareNoError) && (!isAlive)) {
   512         dead = SDL_TRUE;  /* device died in some other way. */
   513     }
   514 
   515     if (dead) {
   516         SDL_OpenedAudioDeviceDisconnected(this);
   517     }
   518 
   519     return 0;
   520 }
   521 #endif
   522 
   523 static void
   524 COREAUDIO_CloseDevice(_THIS)
   525 {
   526     const SDL_bool iscapture = this->iscapture;
   527 
   528 /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
   529 /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
   530 #if MACOSX_COREAUDIO
   531     /* Fire a callback if the device stops being "alive" (disconnected, etc). */
   532     AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
   533 #endif
   534 
   535 #if !MACOSX_COREAUDIO
   536     update_audio_session(this, SDL_FALSE);
   537 #endif
   538 
   539     if (this->hidden->audioQueue) {
   540         AudioQueueDispose(this->hidden->audioQueue, 1);
   541     }
   542 
   543     if (this->hidden->capturestream) {
   544         SDL_FreeAudioStream(this->hidden->capturestream);
   545     }
   546 
   547     /* AudioQueueDispose() frees the actual buffer objects. */
   548     SDL_free(this->hidden->audioBuffer);
   549     SDL_free(this->hidden->buffer);
   550     SDL_free(this->hidden);
   551 
   552     if (iscapture) {
   553         open_capture_devices--;
   554     } else {
   555         open_playback_devices--;
   556     }
   557 }
   558 
   559 #if MACOSX_COREAUDIO
   560 static int
   561 prepare_device(_THIS, void *handle, int iscapture)
   562 {
   563     AudioDeviceID devid = (AudioDeviceID) ((size_t) handle);
   564     OSStatus result = noErr;
   565     UInt32 size = 0;
   566     UInt32 alive = 0;
   567     pid_t pid = 0;
   568 
   569     AudioObjectPropertyAddress addr = {
   570         0,
   571         kAudioObjectPropertyScopeGlobal,
   572         kAudioObjectPropertyElementMaster
   573     };
   574 
   575     if (handle == NULL) {
   576         size = sizeof (AudioDeviceID);
   577         addr.mSelector =
   578             ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
   579             kAudioHardwarePropertyDefaultOutputDevice);
   580         result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
   581                                             0, NULL, &size, &devid);
   582         CHECK_RESULT("AudioHardwareGetProperty (default device)");
   583     }
   584 
   585     addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
   586     addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
   587                     kAudioDevicePropertyScopeOutput;
   588 
   589     size = sizeof (alive);
   590     result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
   591     CHECK_RESULT
   592         ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
   593 
   594     if (!alive) {
   595         SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
   596         return 0;
   597     }
   598 
   599     addr.mSelector = kAudioDevicePropertyHogMode;
   600     size = sizeof (pid);
   601     result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
   602 
   603     /* some devices don't support this property, so errors are fine here. */
   604     if ((result == noErr) && (pid != -1)) {
   605         SDL_SetError("CoreAudio: requested device is being hogged.");
   606         return 0;
   607     }
   608 
   609     this->hidden->deviceID = devid;
   610     return 1;
   611 }
   612 #endif
   613 
   614 
   615 /* this all happens in the audio thread, since it needs a separate runloop. */
   616 static int
   617 prepare_audioqueue(_THIS)
   618 {
   619     const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
   620     const int iscapture = this->iscapture;
   621     OSStatus result;
   622     int i;
   623 
   624     SDL_assert(CFRunLoopGetCurrent() != NULL);
   625 
   626     if (iscapture) {
   627         result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
   628         CHECK_RESULT("AudioQueueNewInput");
   629     } else {
   630         result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
   631         CHECK_RESULT("AudioQueueNewOutput");
   632     }
   633 
   634 #if MACOSX_COREAUDIO
   635 {
   636     const AudioObjectPropertyAddress prop = {
   637         kAudioDevicePropertyDeviceUID,
   638         iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   639         kAudioObjectPropertyElementMaster
   640     };
   641     CFStringRef devuid;
   642     UInt32 devuidsize = sizeof (devuid);
   643     result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
   644     CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
   645     result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
   646     CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
   647 
   648     /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
   649     /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
   650     /* Fire a callback if the device stops being "alive" (disconnected, etc). */
   651     AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
   652 }
   653 #endif
   654 
   655     /* Make sure we can feed the device a minimum amount of time */
   656     double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
   657 #if defined(__IPHONEOS__)
   658     if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
   659         /* Older iOS hardware, use 40 ms as a minimum time */
   660         MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
   661     }
   662 #endif
   663     const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0;
   664     int numAudioBuffers = 2;
   665     if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) {  /* use more buffers if we have a VERY small sample set. */
   666         numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
   667     }
   668 
   669     this->hidden->numAudioBuffers = numAudioBuffers;
   670     this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
   671     if (this->hidden->audioBuffer == NULL) {
   672         SDL_OutOfMemory();
   673         return 0;
   674     }
   675 
   676 #if DEBUG_COREAUDIO
   677     printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
   678 #endif
   679 
   680     for (i = 0; i < numAudioBuffers; i++) {
   681         result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]);
   682         CHECK_RESULT("AudioQueueAllocateBuffer");
   683         SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
   684         this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
   685         result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL);
   686         CHECK_RESULT("AudioQueueEnqueueBuffer");
   687     }
   688 
   689     result = AudioQueueStart(this->hidden->audioQueue, NULL);
   690     CHECK_RESULT("AudioQueueStart");
   691 
   692     /* We're running! */
   693     return 1;
   694 }
   695 
   696 static void
   697 COREAUDIO_ThreadInit(_THIS)
   698 {
   699     const int rc = prepare_audioqueue(this);
   700     if (!rc) {
   701         /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */
   702         SDL_OpenedAudioDeviceDisconnected(this);  /* oh well. */
   703     }
   704 }
   705 
   706 static void
   707 COREAUDIO_PrepareToClose(_THIS)
   708 {
   709     /* run long enough to queue some silence, so we know our actual audio
   710        has been played */
   711     CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0);
   712     AudioQueueStop(this->hidden->audioQueue, 1);
   713 }
   714 
   715 static int
   716 COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   717 {
   718     AudioStreamBasicDescription *strdesc;
   719     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   720     int valid_datatype = 0;
   721 
   722     /* Initialize all variables that we clean on shutdown */
   723     this->hidden = (struct SDL_PrivateAudioData *)
   724         SDL_malloc((sizeof *this->hidden));
   725     if (this->hidden == NULL) {
   726         return SDL_OutOfMemory();
   727     }
   728     SDL_zerop(this->hidden);
   729 
   730     strdesc = &this->hidden->strdesc;
   731 
   732     if (iscapture) {
   733         open_capture_devices++;
   734     } else {
   735         open_playback_devices++;
   736     }
   737 
   738 #if !MACOSX_COREAUDIO
   739     if (!update_audio_session(this, SDL_TRUE)) {
   740         return -1;
   741     }
   742 
   743     /* Stop CoreAudio from doing expensive audio rate conversion */
   744     @autoreleasepool {
   745         AVAudioSession* session = [AVAudioSession sharedInstance];
   746         [session setPreferredSampleRate:this->spec.freq error:nil];
   747         this->spec.freq = (int)session.sampleRate;
   748     }
   749 #endif
   750 
   751     /* Setup a AudioStreamBasicDescription with the requested format */
   752     SDL_zerop(strdesc);
   753     strdesc->mFormatID = kAudioFormatLinearPCM;
   754     strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
   755     strdesc->mChannelsPerFrame = this->spec.channels;
   756     strdesc->mSampleRate = this->spec.freq;
   757     strdesc->mFramesPerPacket = 1;
   758 
   759     while ((!valid_datatype) && (test_format)) {
   760         this->spec.format = test_format;
   761         /* Just a list of valid SDL formats, so people don't pass junk here. */
   762         switch (test_format) {
   763         case AUDIO_U8:
   764         case AUDIO_S8:
   765         case AUDIO_U16LSB:
   766         case AUDIO_S16LSB:
   767         case AUDIO_U16MSB:
   768         case AUDIO_S16MSB:
   769         case AUDIO_S32LSB:
   770         case AUDIO_S32MSB:
   771         case AUDIO_F32LSB:
   772         case AUDIO_F32MSB:
   773             valid_datatype = 1;
   774             strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
   775             if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
   776                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
   777 
   778             if (SDL_AUDIO_ISFLOAT(this->spec.format))
   779                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
   780             else if (SDL_AUDIO_ISSIGNED(this->spec.format))
   781                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
   782             break;
   783         }
   784     }
   785 
   786     if (!valid_datatype) {      /* shouldn't happen, but just in case... */
   787         return SDL_SetError("Unsupported audio format");
   788     }
   789 
   790     strdesc->mBytesPerFrame = strdesc->mBitsPerChannel * strdesc->mChannelsPerFrame / 8;
   791     strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
   792 
   793 #if MACOSX_COREAUDIO
   794     if (!prepare_device(this, handle, iscapture)) {
   795         return -1;
   796     }
   797 #endif
   798 
   799     /* Calculate the final parameters for this audio specification */
   800     SDL_CalculateAudioSpec(&this->spec);
   801 
   802     if (iscapture) {
   803         this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq);
   804         if (!this->hidden->capturestream) {
   805             return -1;  /* already set SDL_Error */
   806         }
   807     } else {
   808         this->hidden->bufferSize = this->spec.size;
   809         this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   810         if (this->hidden->buffer == NULL) {
   811             return SDL_OutOfMemory();
   812         }
   813     }
   814 
   815     return 0;
   816 }
   817 
   818 static void
   819 COREAUDIO_Deinitialize(void)
   820 {
   821 #if MACOSX_COREAUDIO
   822     AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   823     free_audio_device_list(&capture_devs);
   824     free_audio_device_list(&output_devs);
   825 #endif
   826 }
   827 
   828 static int
   829 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
   830 {
   831     /* Set the function pointers */
   832     impl->OpenDevice = COREAUDIO_OpenDevice;
   833     impl->CloseDevice = COREAUDIO_CloseDevice;
   834     impl->Deinitialize = COREAUDIO_Deinitialize;
   835     impl->ThreadInit = COREAUDIO_ThreadInit;
   836     impl->WaitDevice = COREAUDIO_WaitDevice;
   837     impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
   838     impl->PrepareToClose = COREAUDIO_PrepareToClose;
   839     impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice;
   840     impl->FlushCapture = COREAUDIO_FlushCapture;
   841 
   842 #if MACOSX_COREAUDIO
   843     impl->DetectDevices = COREAUDIO_DetectDevices;
   844     AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   845 #else
   846     impl->OnlyHasDefaultOutputDevice = 1;
   847     impl->OnlyHasDefaultCaptureDevice = 1;
   848 #endif
   849 
   850     impl->HasCaptureSupport = 1;
   851 
   852     return 1;   /* this audio target is available. */
   853 }
   854 
   855 AudioBootStrap COREAUDIO_bootstrap = {
   856     "coreaudio", "CoreAudio", COREAUDIO_Init, 0
   857 };
   858 
   859 #endif /* SDL_AUDIO_DRIVER_COREAUDIO */
   860 
   861 /* vi: set ts=4 sw=4 expandtab: */