src/audio/coreaudio/SDL_coreaudio.m
author Ryan C. Gordon
Mon, 23 Jan 2017 12:06:10 -0500
changeset 10837 c2f241c2f6ad
parent 10737 3406a0f8b041
child 10906 66a188fbf8bf
permissions -rw-r--r--
audio: Fix same bug as last commit, but for _mm_bslli_si128 vs _mm_slli_si128.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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_audio_c.h"
    29 #include "../SDL_sysaudio.h"
    30 #include "SDL_coreaudio.h"
    31 #include "SDL_assert.h"
    32 #include "../../thread/SDL_systhread.h"
    33 
    34 #define DEBUG_COREAUDIO 0
    35 
    36 #define CHECK_RESULT(msg) \
    37     if (result != noErr) { \
    38         SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
    39         return 0; \
    40     }
    41 
    42 #if MACOSX_COREAUDIO
    43 static const AudioObjectPropertyAddress devlist_address = {
    44     kAudioHardwarePropertyDevices,
    45     kAudioObjectPropertyScopeGlobal,
    46     kAudioObjectPropertyElementMaster
    47 };
    48 
    49 typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
    50 
    51 typedef struct AudioDeviceList
    52 {
    53     AudioDeviceID devid;
    54     SDL_bool alive;
    55     struct AudioDeviceList *next;
    56 } AudioDeviceList;
    57 
    58 static AudioDeviceList *output_devs = NULL;
    59 static AudioDeviceList *capture_devs = NULL;
    60 
    61 static SDL_bool
    62 add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
    63 {
    64     AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
    65     if (item == NULL) {
    66         return SDL_FALSE;
    67     }
    68     item->devid = devId;
    69     item->alive = SDL_TRUE;
    70     item->next = iscapture ? capture_devs : output_devs;
    71     if (iscapture) {
    72         capture_devs = item;
    73     } else {
    74         output_devs = item;
    75     }
    76 
    77     return SDL_TRUE;
    78 }
    79 
    80 static void
    81 addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
    82 {
    83     if (add_to_internal_dev_list(iscapture, devId)) {
    84         SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
    85     }
    86 }
    87 
    88 static void
    89 build_device_list(int iscapture, addDevFn addfn, void *addfndata)
    90 {
    91     OSStatus result = noErr;
    92     UInt32 size = 0;
    93     AudioDeviceID *devs = NULL;
    94     UInt32 i = 0;
    95     UInt32 max = 0;
    96 
    97     result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
    98                                             &devlist_address, 0, NULL, &size);
    99     if (result != kAudioHardwareNoError)
   100         return;
   101 
   102     devs = (AudioDeviceID *) alloca(size);
   103     if (devs == NULL)
   104         return;
   105 
   106     result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
   107                                         &devlist_address, 0, NULL, &size, devs);
   108     if (result != kAudioHardwareNoError)
   109         return;
   110 
   111     max = size / sizeof (AudioDeviceID);
   112     for (i = 0; i < max; i++) {
   113         CFStringRef cfstr = NULL;
   114         char *ptr = NULL;
   115         AudioDeviceID dev = devs[i];
   116         AudioBufferList *buflist = NULL;
   117         int usable = 0;
   118         CFIndex len = 0;
   119         const AudioObjectPropertyAddress addr = {
   120             kAudioDevicePropertyStreamConfiguration,
   121             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   122             kAudioObjectPropertyElementMaster
   123         };
   124 
   125         const AudioObjectPropertyAddress nameaddr = {
   126             kAudioObjectPropertyName,
   127             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   128             kAudioObjectPropertyElementMaster
   129         };
   130 
   131         result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
   132         if (result != noErr)
   133             continue;
   134 
   135         buflist = (AudioBufferList *) SDL_malloc(size);
   136         if (buflist == NULL)
   137             continue;
   138 
   139         result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
   140                                             &size, buflist);
   141 
   142         if (result == noErr) {
   143             UInt32 j;
   144             for (j = 0; j < buflist->mNumberBuffers; j++) {
   145                 if (buflist->mBuffers[j].mNumberChannels > 0) {
   146                     usable = 1;
   147                     break;
   148                 }
   149             }
   150         }
   151 
   152         SDL_free(buflist);
   153 
   154         if (!usable)
   155             continue;
   156 
   157 
   158         size = sizeof (CFStringRef);
   159         result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
   160         if (result != kAudioHardwareNoError)
   161             continue;
   162 
   163         len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
   164                                                 kCFStringEncodingUTF8);
   165 
   166         ptr = (char *) SDL_malloc(len + 1);
   167         usable = ((ptr != NULL) &&
   168                   (CFStringGetCString
   169                    (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));
   170 
   171         CFRelease(cfstr);
   172 
   173         if (usable) {
   174             len = strlen(ptr);
   175             /* Some devices have whitespace at the end...trim it. */
   176             while ((len > 0) && (ptr[len - 1] == ' ')) {
   177                 len--;
   178             }
   179             usable = (len > 0);
   180         }
   181 
   182         if (usable) {
   183             ptr[len] = '\0';
   184 
   185 #if DEBUG_COREAUDIO
   186             printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
   187                    ((iscapture) ? "capture" : "output"),
   188                    (int) i, ptr, (int) dev);
   189 #endif
   190             addfn(ptr, iscapture, dev, addfndata);
   191         }
   192         SDL_free(ptr);  /* addfn() would have copied the string. */
   193     }
   194 }
   195 
   196 static void
   197 free_audio_device_list(AudioDeviceList **list)
   198 {
   199     AudioDeviceList *item = *list;
   200     while (item) {
   201         AudioDeviceList *next = item->next;
   202         SDL_free(item);
   203         item = next;
   204     }
   205     *list = NULL;
   206 }
   207 
   208 static void
   209 COREAUDIO_DetectDevices(void)
   210 {
   211     build_device_list(SDL_TRUE, addToDevList, NULL);
   212     build_device_list(SDL_FALSE, addToDevList, NULL);
   213 }
   214 
   215 static void
   216 build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
   217 {
   218     AudioDeviceList **list = (AudioDeviceList **) data;
   219     AudioDeviceList *item;
   220     for (item = *list; item != NULL; item = item->next) {
   221         if (item->devid == devId) {
   222             item->alive = SDL_TRUE;
   223             return;
   224         }
   225     }
   226 
   227     add_to_internal_dev_list(iscapture, devId);  /* new device, add it. */
   228     SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
   229 }
   230 
   231 static void
   232 reprocess_device_list(const int iscapture, AudioDeviceList **list)
   233 {
   234     AudioDeviceList *item;
   235     AudioDeviceList *prev = NULL;
   236     for (item = *list; item != NULL; item = item->next) {
   237         item->alive = SDL_FALSE;
   238     }
   239 
   240     build_device_list(iscapture, build_device_change_list, list);
   241 
   242     /* free items in the list that aren't still alive. */
   243     item = *list;
   244     while (item != NULL) {
   245         AudioDeviceList *next = item->next;
   246         if (item->alive) {
   247             prev = item;
   248         } else {
   249             SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid));
   250             if (prev) {
   251                 prev->next = item->next;
   252             } else {
   253                 *list = item->next;
   254             }
   255             SDL_free(item);
   256         }
   257         item = next;
   258     }
   259 }
   260 
   261 /* this is called when the system's list of available audio devices changes. */
   262 static OSStatus
   263 device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
   264 {
   265     reprocess_device_list(SDL_TRUE, &capture_devs);
   266     reprocess_device_list(SDL_FALSE, &output_devs);
   267     return 0;
   268 }
   269 #endif
   270 
   271 
   272 static int open_playback_devices = 0;
   273 static int open_capture_devices = 0;
   274 
   275 #if !MACOSX_COREAUDIO
   276 
   277 static void interruption_begin(_THIS)
   278 {
   279     if (this != NULL && this->hidden->audioQueue != NULL) {
   280         this->hidden->interrupted = SDL_TRUE;
   281         AudioQueuePause(this->hidden->audioQueue);
   282     }
   283 }
   284 
   285 static void interruption_end(_THIS)
   286 {
   287     if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL
   288     && this->hidden->interrupted) {
   289         this->hidden->interrupted = SDL_FALSE;
   290         AudioQueueStart(this->hidden->audioQueue, NULL);
   291     }
   292 }
   293 
   294 @interface SDLInterruptionListener : NSObject
   295 
   296 @property (nonatomic, assign) SDL_AudioDevice *device;
   297 
   298 @end
   299 
   300 @implementation SDLInterruptionListener
   301 
   302 - (void)audioSessionInterruption:(NSNotification *)note
   303 {
   304     @synchronized (self) {
   305         NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
   306         if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
   307             interruption_begin(self.device);
   308         } else {
   309             interruption_end(self.device);
   310         }
   311     }
   312 }
   313 
   314 - (void)applicationBecameActive:(NSNotification *)note
   315 {
   316     @synchronized (self) {
   317         interruption_end(self.device);
   318     }
   319 }
   320 
   321 @end
   322 
   323 static BOOL update_audio_session(_THIS, SDL_bool open)
   324 {
   325     @autoreleasepool {
   326         AVAudioSession *session = [AVAudioSession sharedInstance];
   327         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   328         NSString *category;
   329         NSError *err = nil;
   330 
   331         if (open_playback_devices && open_capture_devices) {
   332             category = AVAudioSessionCategoryPlayAndRecord;
   333         } else if (open_capture_devices) {
   334             category = AVAudioSessionCategoryRecord;
   335         } else {
   336             /* Set category to ambient so that other music continues playing.
   337              You can change this at runtime in your own code if you need different
   338              behavior. If this is common, we can add an SDL hint for this. */
   339             category = AVAudioSessionCategoryAmbient;
   340         }
   341 
   342         if (![session setCategory:category error:&err]) {
   343             NSString *desc = err.description;
   344             SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
   345             return NO;
   346         }
   347 
   348         if (open_playback_devices + open_capture_devices == 1) {
   349             if (![session setActive:YES error:&err]) {
   350                 NSString *desc = err.description;
   351                 SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
   352                 return NO;
   353             }
   354         } else if (!open_playback_devices && !open_capture_devices) {
   355             [session setActive:NO error:nil];
   356         }
   357 
   358         if (open) {
   359             SDLInterruptionListener *listener = [SDLInterruptionListener new];
   360             listener.device = this;
   361 
   362             [center addObserver:listener
   363                        selector:@selector(audioSessionInterruption:)
   364                            name:AVAudioSessionInterruptionNotification
   365                          object:session];
   366 
   367             /* An interruption end notification is not guaranteed to be sent if
   368              we were previously interrupted... resuming if needed when the app
   369              becomes active seems to be the way to go. */
   370             [center addObserver:listener
   371                        selector:@selector(applicationBecameActive:)
   372                            name:UIApplicationDidBecomeActiveNotification
   373                          object:session];
   374 
   375             [center addObserver:listener
   376                        selector:@selector(applicationBecameActive:)
   377                            name:UIApplicationWillEnterForegroundNotification
   378                          object:session];
   379 
   380             this->hidden->interruption_listener = CFBridgingRetain(listener);
   381         } else {
   382             if (this->hidden->interruption_listener != NULL) {
   383                 SDLInterruptionListener *listener = nil;
   384                 listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener);
   385                 @synchronized (listener) {
   386                     listener.device = NULL;
   387                 }
   388                 [center removeObserver:listener];
   389             }
   390         }
   391     }
   392 
   393     return YES;
   394 }
   395 #endif
   396 
   397 
   398 /* The AudioQueue callback */
   399 static void
   400 outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
   401 {
   402     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
   403     if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
   404         /* Supply silence if audio is enabled and not paused */
   405         SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity);
   406     } else {
   407         UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
   408         Uint8 *ptr = (Uint8 *) inBuffer->mAudioData;
   409 
   410         while (remaining > 0) {
   411             UInt32 len;
   412             if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
   413                 /* Generate the data */
   414                 SDL_LockMutex(this->mixer_lock);
   415                 (*this->spec.callback)(this->spec.userdata,
   416                             this->hidden->buffer, this->hidden->bufferSize);
   417                 SDL_UnlockMutex(this->mixer_lock);
   418                 this->hidden->bufferOffset = 0;
   419             }
   420 
   421             len = this->hidden->bufferSize - this->hidden->bufferOffset;
   422             if (len > remaining) {
   423                 len = remaining;
   424             }
   425             SDL_memcpy(ptr, (char *)this->hidden->buffer +
   426                        this->hidden->bufferOffset, len);
   427             ptr = ptr + len;
   428             remaining -= len;
   429             this->hidden->bufferOffset += len;
   430         }
   431     }
   432 
   433     if (!SDL_AtomicGet(&this->hidden->shutdown)) {
   434         AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
   435     }
   436 
   437     inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
   438 }
   439 
   440 static void
   441 inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
   442               const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
   443               const AudioStreamPacketDescription *inPacketDescs )
   444 {
   445     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
   446     if (SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) {  /* ignore unless we're active. */
   447         const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData;
   448         UInt32 remaining = inBuffer->mAudioDataByteSize;
   449         while (remaining > 0) {
   450             UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset;
   451             if (len > remaining) {
   452                 len = remaining;
   453             }
   454 
   455             SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len);
   456             ptr += len;
   457             remaining -= len;
   458             this->hidden->bufferOffset += len;
   459 
   460             if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
   461                 SDL_LockMutex(this->mixer_lock);
   462                 (*this->spec.callback)(this->spec.userdata, this->hidden->buffer, this->hidden->bufferSize);
   463                 SDL_UnlockMutex(this->mixer_lock);
   464                 this->hidden->bufferOffset = 0;
   465             }
   466         }
   467     }
   468 
   469     if (!SDL_AtomicGet(&this->hidden->shutdown)) {
   470         AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
   471     }
   472 }
   473 
   474 
   475 #if MACOSX_COREAUDIO
   476 static const AudioObjectPropertyAddress alive_address =
   477 {
   478     kAudioDevicePropertyDeviceIsAlive,
   479     kAudioObjectPropertyScopeGlobal,
   480     kAudioObjectPropertyElementMaster
   481 };
   482 
   483 static OSStatus
   484 device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
   485 {
   486     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   487     SDL_bool dead = SDL_FALSE;
   488     UInt32 isAlive = 1;
   489     UInt32 size = sizeof (isAlive);
   490     OSStatus error;
   491 
   492     if (!SDL_AtomicGet(&this->enabled)) {
   493         return 0;  /* already known to be dead. */
   494     }
   495 
   496     error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
   497                                        0, NULL, &size, &isAlive);
   498 
   499     if (error == kAudioHardwareBadDeviceError) {
   500         dead = SDL_TRUE;  /* device was unplugged. */
   501     } else if ((error == kAudioHardwareNoError) && (!isAlive)) {
   502         dead = SDL_TRUE;  /* device died in some other way. */
   503     }
   504 
   505     if (dead) {
   506         SDL_OpenedAudioDeviceDisconnected(this);
   507     }
   508 
   509     return 0;
   510 }
   511 #endif
   512 
   513 static void
   514 COREAUDIO_CloseDevice(_THIS)
   515 {
   516     const SDL_bool iscapture = this->iscapture;
   517     int i;
   518 
   519 /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
   520 /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
   521 #if MACOSX_COREAUDIO
   522     /* Fire a callback if the device stops being "alive" (disconnected, etc). */
   523     AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
   524 #endif
   525 
   526 #if !MACOSX_COREAUDIO
   527     update_audio_session(this, SDL_FALSE);
   528 #endif
   529 
   530     if (this->hidden->thread) {
   531         SDL_AtomicSet(&this->hidden->shutdown, 1);
   532         SDL_WaitThread(this->hidden->thread, NULL);
   533     }
   534 
   535     if (this->hidden->audioQueue) {
   536         for (i = 0; i < SDL_arraysize(this->hidden->audioBuffer); i++) {
   537             if (this->hidden->audioBuffer[i]) {
   538                 AudioQueueFreeBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i]);
   539             }
   540         }
   541         AudioQueueDispose(this->hidden->audioQueue, 1);
   542     }
   543 
   544     if (this->hidden->ready_semaphore) {
   545         SDL_DestroySemaphore(this->hidden->ready_semaphore);
   546     }
   547 
   548     SDL_free(this->hidden->thread_error);
   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 static int
   615 prepare_audioqueue(_THIS)
   616 {
   617     const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
   618     const int iscapture = this->iscapture;
   619     OSStatus result;
   620     int i;
   621 
   622     SDL_assert(CFRunLoopGetCurrent() != NULL);
   623 
   624     if (iscapture) {
   625         result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
   626         CHECK_RESULT("AudioQueueNewInput");
   627     } else {
   628         result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
   629         CHECK_RESULT("AudioQueueNewOutput");
   630     }
   631 
   632 #if MACOSX_COREAUDIO
   633 {
   634     const AudioObjectPropertyAddress prop = {
   635         kAudioDevicePropertyDeviceUID,
   636         iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   637         kAudioObjectPropertyElementMaster
   638     };
   639     CFStringRef devuid;
   640     UInt32 devuidsize = sizeof (devuid);
   641     result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
   642     CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
   643     result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
   644     CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
   645 
   646     /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
   647     /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
   648     /* Fire a callback if the device stops being "alive" (disconnected, etc). */
   649     AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
   650 }
   651 #endif
   652 
   653     /* Calculate the final parameters for this audio specification */
   654     SDL_CalculateAudioSpec(&this->spec);
   655 
   656     /* Allocate a sample buffer */
   657     this->hidden->bufferSize = this->spec.size;
   658     this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
   659 
   660     this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   661     if (this->hidden->buffer == NULL) {
   662         SDL_OutOfMemory();
   663         return 0;
   664     }
   665 
   666     for (i = 0; i < SDL_arraysize(this->hidden->audioBuffer); i++) {
   667         result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]);
   668         CHECK_RESULT("AudioQueueAllocateBuffer");
   669         SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
   670         this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
   671         result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL);
   672         CHECK_RESULT("AudioQueueEnqueueBuffer");
   673     }
   674 
   675     result = AudioQueueStart(this->hidden->audioQueue, NULL);
   676     CHECK_RESULT("AudioQueueStart");
   677 
   678     /* We're running! */
   679     return 1;
   680 }
   681 
   682 static int
   683 audioqueue_thread(void *arg)
   684 {
   685     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
   686     const int rc = prepare_audioqueue(this);
   687     if (!rc) {
   688         this->hidden->thread_error = SDL_strdup(SDL_GetError());
   689         SDL_SemPost(this->hidden->ready_semaphore);
   690         return 0;
   691     }
   692 
   693     /* init was successful, alert parent thread and start running... */
   694     SDL_SemPost(this->hidden->ready_semaphore);
   695     while (!SDL_AtomicGet(&this->hidden->shutdown)) {
   696         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
   697     }
   698 
   699     if (this->iscapture) {  /* just stop immediately for capture devices. */
   700         AudioQueueStop(this->hidden->audioQueue, 1);
   701     } else {  /* Drain off any pending playback. */
   702         AudioQueueStop(this->hidden->audioQueue, 0);
   703         const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0;
   704         CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
   705     }
   706 
   707     return 0;
   708 }
   709 
   710 static int
   711 COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   712 {
   713     AudioStreamBasicDescription *strdesc;
   714     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   715     int valid_datatype = 0;
   716 
   717     /* Initialize all variables that we clean on shutdown */
   718     this->hidden = (struct SDL_PrivateAudioData *)
   719         SDL_malloc((sizeof *this->hidden));
   720     if (this->hidden == NULL) {
   721         return SDL_OutOfMemory();
   722     }
   723     SDL_zerop(this->hidden);
   724 
   725     strdesc = &this->hidden->strdesc;
   726 
   727     if (iscapture) {
   728         open_capture_devices++;
   729     } else {
   730         open_playback_devices++;
   731     }
   732 
   733 #if !MACOSX_COREAUDIO
   734     if (!update_audio_session(this, SDL_TRUE)) {
   735         return -1;
   736     }
   737 #endif
   738 
   739     /* Setup a AudioStreamBasicDescription with the requested format */
   740     SDL_zerop(strdesc);
   741     strdesc->mFormatID = kAudioFormatLinearPCM;
   742     strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
   743     strdesc->mChannelsPerFrame = this->spec.channels;
   744     strdesc->mSampleRate = this->spec.freq;
   745     strdesc->mFramesPerPacket = 1;
   746 
   747     while ((!valid_datatype) && (test_format)) {
   748         this->spec.format = test_format;
   749         /* Just a list of valid SDL formats, so people don't pass junk here. */
   750         switch (test_format) {
   751         case AUDIO_U8:
   752         case AUDIO_S8:
   753         case AUDIO_U16LSB:
   754         case AUDIO_S16LSB:
   755         case AUDIO_U16MSB:
   756         case AUDIO_S16MSB:
   757         case AUDIO_S32LSB:
   758         case AUDIO_S32MSB:
   759         case AUDIO_F32LSB:
   760         case AUDIO_F32MSB:
   761             valid_datatype = 1;
   762             strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
   763             if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
   764                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
   765 
   766             if (SDL_AUDIO_ISFLOAT(this->spec.format))
   767                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
   768             else if (SDL_AUDIO_ISSIGNED(this->spec.format))
   769                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
   770             break;
   771         }
   772     }
   773 
   774     if (!valid_datatype) {      /* shouldn't happen, but just in case... */
   775         return SDL_SetError("Unsupported audio format");
   776     }
   777 
   778     strdesc->mBytesPerFrame = strdesc->mBitsPerChannel * strdesc->mChannelsPerFrame / 8;
   779     strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
   780 
   781 #if MACOSX_COREAUDIO
   782     if (!prepare_device(this, handle, iscapture)) {
   783         return -1;
   784     }
   785 #endif
   786 
   787     /* This has to init in a new thread so it can get its own CFRunLoop. :/ */
   788     SDL_AtomicSet(&this->hidden->shutdown, 0);
   789     this->hidden->ready_semaphore = SDL_CreateSemaphore(0);
   790     if (!this->hidden->ready_semaphore) {
   791         return -1;  /* oh well. */
   792     }
   793 
   794     this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, this);
   795     if (!this->hidden->thread) {
   796         return -1;
   797     }
   798 
   799     SDL_SemWait(this->hidden->ready_semaphore);
   800     SDL_DestroySemaphore(this->hidden->ready_semaphore);
   801     this->hidden->ready_semaphore = NULL;
   802 
   803     if ((this->hidden->thread != NULL) && (this->hidden->thread_error != NULL)) {
   804         SDL_SetError("%s", this->hidden->thread_error);
   805         return -1;
   806     }
   807 
   808     return (this->hidden->thread != NULL) ? 0 : -1;
   809 }
   810 
   811 static void
   812 COREAUDIO_Deinitialize(void)
   813 {
   814 #if MACOSX_COREAUDIO
   815     AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   816     free_audio_device_list(&capture_devs);
   817     free_audio_device_list(&output_devs);
   818 #endif
   819 }
   820 
   821 static int
   822 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
   823 {
   824     /* Set the function pointers */
   825     impl->OpenDevice = COREAUDIO_OpenDevice;
   826     impl->CloseDevice = COREAUDIO_CloseDevice;
   827     impl->Deinitialize = COREAUDIO_Deinitialize;
   828 
   829 #if MACOSX_COREAUDIO
   830     impl->DetectDevices = COREAUDIO_DetectDevices;
   831     AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   832 #else
   833     impl->OnlyHasDefaultOutputDevice = 1;
   834     impl->OnlyHasDefaultCaptureDevice = 1;
   835 #endif
   836 
   837     impl->ProvidesOwnCallbackThread = 1;
   838     impl->HasCaptureSupport = 1;
   839 
   840     return 1;   /* this audio target is available. */
   841 }
   842 
   843 AudioBootStrap COREAUDIO_bootstrap = {
   844     "coreaudio", "CoreAudio", COREAUDIO_Init, 0
   845 };
   846 
   847 #endif /* SDL_AUDIO_DRIVER_COREAUDIO */
   848 
   849 /* vi: set ts=4 sw=4 expandtab: */