sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_coreaudio.m (41380B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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_audio_c.h"
     30 #include "../SDL_sysaudio.h"
     31 #include "SDL_coreaudio.h"
     32 #include "../../thread/SDL_systhread.h"
     33 
     34 #define DEBUG_COREAUDIO 0
     35 
     36 #if DEBUG_COREAUDIO
     37     #define CHECK_RESULT(msg) \
     38         if (result != noErr) { \
     39             printf("COREAUDIO: Got error %d from '%s'!\n", (int) result, msg); \
     40             SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
     41             return 0; \
     42         }
     43 #else
     44     #define CHECK_RESULT(msg) \
     45         if (result != noErr) { \
     46             SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
     47             return 0; \
     48         }
     49 #endif
     50 
     51 
     52 #if MACOSX_COREAUDIO
     53 static const AudioObjectPropertyAddress devlist_address = {
     54     kAudioHardwarePropertyDevices,
     55     kAudioObjectPropertyScopeGlobal,
     56     kAudioObjectPropertyElementMaster
     57 };
     58 
     59 typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
     60 
     61 typedef struct AudioDeviceList
     62 {
     63     AudioDeviceID devid;
     64     SDL_bool alive;
     65     struct AudioDeviceList *next;
     66 } AudioDeviceList;
     67 
     68 static AudioDeviceList *output_devs = NULL;
     69 static AudioDeviceList *capture_devs = NULL;
     70 
     71 static SDL_bool
     72 add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
     73 {
     74     AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
     75     if (item == NULL) {
     76         return SDL_FALSE;
     77     }
     78     item->devid = devId;
     79     item->alive = SDL_TRUE;
     80     item->next = iscapture ? capture_devs : output_devs;
     81     if (iscapture) {
     82         capture_devs = item;
     83     } else {
     84         output_devs = item;
     85     }
     86 
     87     return SDL_TRUE;
     88 }
     89 
     90 static void
     91 addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
     92 {
     93     if (add_to_internal_dev_list(iscapture, devId)) {
     94         SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
     95     }
     96 }
     97 
     98 static void
     99 build_device_list(int iscapture, addDevFn addfn, void *addfndata)
    100 {
    101     OSStatus result = noErr;
    102     UInt32 size = 0;
    103     AudioDeviceID *devs = NULL;
    104     UInt32 i = 0;
    105     UInt32 max = 0;
    106 
    107     result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
    108                                             &devlist_address, 0, NULL, &size);
    109     if (result != kAudioHardwareNoError)
    110         return;
    111 
    112     devs = (AudioDeviceID *) alloca(size);
    113     if (devs == NULL)
    114         return;
    115 
    116     result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
    117                                         &devlist_address, 0, NULL, &size, devs);
    118     if (result != kAudioHardwareNoError)
    119         return;
    120 
    121     max = size / sizeof (AudioDeviceID);
    122     for (i = 0; i < max; i++) {
    123         CFStringRef cfstr = NULL;
    124         char *ptr = NULL;
    125         AudioDeviceID dev = devs[i];
    126         AudioBufferList *buflist = NULL;
    127         int usable = 0;
    128         CFIndex len = 0;
    129         const AudioObjectPropertyAddress addr = {
    130             kAudioDevicePropertyStreamConfiguration,
    131             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
    132             kAudioObjectPropertyElementMaster
    133         };
    134 
    135         const AudioObjectPropertyAddress nameaddr = {
    136             kAudioObjectPropertyName,
    137             iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
    138             kAudioObjectPropertyElementMaster
    139         };
    140 
    141         result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
    142         if (result != noErr)
    143             continue;
    144 
    145         buflist = (AudioBufferList *) SDL_malloc(size);
    146         if (buflist == NULL)
    147             continue;
    148 
    149         result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
    150                                             &size, buflist);
    151 
    152         if (result == noErr) {
    153             UInt32 j;
    154             for (j = 0; j < buflist->mNumberBuffers; j++) {
    155                 if (buflist->mBuffers[j].mNumberChannels > 0) {
    156                     usable = 1;
    157                     break;
    158                 }
    159             }
    160         }
    161 
    162         SDL_free(buflist);
    163 
    164         if (!usable)
    165             continue;
    166 
    167 
    168         size = sizeof (CFStringRef);
    169         result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
    170         if (result != kAudioHardwareNoError)
    171             continue;
    172 
    173         len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
    174                                                 kCFStringEncodingUTF8);
    175 
    176         ptr = (char *) SDL_malloc(len + 1);
    177         usable = ((ptr != NULL) &&
    178                   (CFStringGetCString
    179                    (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));
    180 
    181         CFRelease(cfstr);
    182 
    183         if (usable) {
    184             len = strlen(ptr);
    185             /* Some devices have whitespace at the end...trim it. */
    186             while ((len > 0) && (ptr[len - 1] == ' ')) {
    187                 len--;
    188             }
    189             usable = (len > 0);
    190         }
    191 
    192         if (usable) {
    193             ptr[len] = '\0';
    194 
    195 #if DEBUG_COREAUDIO
    196             printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
    197                    ((iscapture) ? "capture" : "output"),
    198                    (int) i, ptr, (int) dev);
    199 #endif
    200             addfn(ptr, iscapture, dev, addfndata);
    201         }
    202         SDL_free(ptr);  /* addfn() would have copied the string. */
    203     }
    204 }
    205 
    206 static void
    207 free_audio_device_list(AudioDeviceList **list)
    208 {
    209     AudioDeviceList *item = *list;
    210     while (item) {
    211         AudioDeviceList *next = item->next;
    212         SDL_free(item);
    213         item = next;
    214     }
    215     *list = NULL;
    216 }
    217 
    218 static void
    219 COREAUDIO_DetectDevices(void)
    220 {
    221     build_device_list(SDL_TRUE, addToDevList, NULL);
    222     build_device_list(SDL_FALSE, addToDevList, NULL);
    223 }
    224 
    225 static void
    226 build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
    227 {
    228     AudioDeviceList **list = (AudioDeviceList **) data;
    229     AudioDeviceList *item;
    230     for (item = *list; item != NULL; item = item->next) {
    231         if (item->devid == devId) {
    232             item->alive = SDL_TRUE;
    233             return;
    234         }
    235     }
    236 
    237     add_to_internal_dev_list(iscapture, devId);  /* new device, add it. */
    238     SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
    239 }
    240 
    241 static void
    242 reprocess_device_list(const int iscapture, AudioDeviceList **list)
    243 {
    244     AudioDeviceList *item;
    245     AudioDeviceList *prev = NULL;
    246     for (item = *list; item != NULL; item = item->next) {
    247         item->alive = SDL_FALSE;
    248     }
    249 
    250     build_device_list(iscapture, build_device_change_list, list);
    251 
    252     /* free items in the list that aren't still alive. */
    253     item = *list;
    254     while (item != NULL) {
    255         AudioDeviceList *next = item->next;
    256         if (item->alive) {
    257             prev = item;
    258         } else {
    259             SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid));
    260             if (prev) {
    261                 prev->next = item->next;
    262             } else {
    263                 *list = item->next;
    264             }
    265             SDL_free(item);
    266         }
    267         item = next;
    268     }
    269 }
    270 
    271 /* this is called when the system's list of available audio devices changes. */
    272 static OSStatus
    273 device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
    274 {
    275     reprocess_device_list(SDL_TRUE, &capture_devs);
    276     reprocess_device_list(SDL_FALSE, &output_devs);
    277     return 0;
    278 }
    279 #endif
    280 
    281 
    282 static int open_playback_devices;
    283 static int open_capture_devices;
    284 static int num_open_devices;
    285 static SDL_AudioDevice **open_devices;
    286 
    287 #if !MACOSX_COREAUDIO
    288 
    289 static BOOL session_active = NO;
    290 
    291 static void pause_audio_devices()
    292 {
    293     int i;
    294 
    295     if (!open_devices) {
    296         return;
    297     }
    298 
    299     for (i = 0; i < num_open_devices; ++i) {
    300         SDL_AudioDevice *device = open_devices[i];
    301         if (device->hidden->audioQueue && !device->hidden->interrupted) {
    302             AudioQueuePause(device->hidden->audioQueue);
    303         }
    304     }
    305 }
    306 
    307 static void resume_audio_devices()
    308 {
    309     int i;
    310 
    311     if (!open_devices) {
    312         return;
    313     }
    314 
    315     for (i = 0; i < num_open_devices; ++i) {
    316         SDL_AudioDevice *device = open_devices[i];
    317         if (device->hidden->audioQueue && !device->hidden->interrupted) {
    318             AudioQueueStart(device->hidden->audioQueue, NULL);
    319         }
    320     }
    321 }
    322 
    323 static void interruption_begin(_THIS)
    324 {
    325     if (this != NULL && this->hidden->audioQueue != NULL) {
    326         this->hidden->interrupted = SDL_TRUE;
    327         AudioQueuePause(this->hidden->audioQueue);
    328     }
    329 }
    330 
    331 static void interruption_end(_THIS)
    332 {
    333     if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL
    334     && this->hidden->interrupted
    335     && AudioQueueStart(this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) {
    336         this->hidden->interrupted = SDL_FALSE;
    337     }
    338 }
    339 
    340 @interface SDLInterruptionListener : NSObject
    341 
    342 @property (nonatomic, assign) SDL_AudioDevice *device;
    343 
    344 @end
    345 
    346 @implementation SDLInterruptionListener
    347 
    348 - (void)audioSessionInterruption:(NSNotification *)note
    349 {
    350     @synchronized (self) {
    351         NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
    352         if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
    353             interruption_begin(self.device);
    354         } else {
    355             interruption_end(self.device);
    356         }
    357     }
    358 }
    359 
    360 - (void)applicationBecameActive:(NSNotification *)note
    361 {
    362     @synchronized (self) {
    363         interruption_end(self.device);
    364     }
    365 }
    366 
    367 @end
    368 
    369 static BOOL update_audio_session(_THIS, SDL_bool open, SDL_bool allow_playandrecord)
    370 {
    371     @autoreleasepool {
    372         AVAudioSession *session = [AVAudioSession sharedInstance];
    373         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    374 
    375         NSString *category = AVAudioSessionCategoryPlayback;
    376         NSString *mode = AVAudioSessionModeDefault;
    377         NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers;
    378         NSError *err = nil;
    379         const char *hint;
    380 
    381         hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
    382         if (hint) {
    383             if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
    384                 category = AVAudioSessionCategoryAmbient;
    385             } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
    386                 category = AVAudioSessionCategorySoloAmbient;
    387                 options &= ~AVAudioSessionCategoryOptionMixWithOthers;
    388             } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 ||
    389                        SDL_strcasecmp(hint, "playback") == 0) {
    390                 category = AVAudioSessionCategoryPlayback;
    391                 options &= ~AVAudioSessionCategoryOptionMixWithOthers;
    392             } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 ||
    393                        SDL_strcasecmp(hint, "playandrecord") == 0) {
    394                 if (allow_playandrecord) {
    395                     category = AVAudioSessionCategoryPlayAndRecord;
    396                 }
    397             }
    398         } else if (open_playback_devices && open_capture_devices) {
    399             category = AVAudioSessionCategoryPlayAndRecord;
    400         } else if (open_capture_devices) {
    401             category = AVAudioSessionCategoryRecord;
    402         }
    403 
    404 #if !TARGET_OS_TV
    405         if (category == AVAudioSessionCategoryPlayAndRecord) {
    406             options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
    407         }
    408 #endif
    409         if (category == AVAudioSessionCategoryRecord ||
    410             category == AVAudioSessionCategoryPlayAndRecord) {
    411             /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for
    412                Apple TV but is still needed in order to output to Bluetooth devices.
    413              */
    414             options |= 0x4; /* AVAudioSessionCategoryOptionAllowBluetooth; */
    415         }
    416         if (category == AVAudioSessionCategoryPlayAndRecord) {
    417             options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP |
    418                        AVAudioSessionCategoryOptionAllowAirPlay;
    419         }
    420         if (category == AVAudioSessionCategoryPlayback ||
    421             category == AVAudioSessionCategoryPlayAndRecord) {
    422             options |= AVAudioSessionCategoryOptionDuckOthers;
    423         }
    424 
    425         if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) {
    426             if (![session.category isEqualToString:category] || session.categoryOptions != options) {
    427                 /* Stop the current session so we don't interrupt other application audio */
    428                 pause_audio_devices();
    429                 [session setActive:NO error:nil];
    430                 session_active = NO;
    431 
    432                 if (![session setCategory:category mode:mode options:options error:&err]) {
    433                     NSString *desc = err.description;
    434                     SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
    435                     return NO;
    436                 }
    437             }
    438         } else {
    439             if (![session.category isEqualToString:category]) {
    440                 /* Stop the current session so we don't interrupt other application audio */
    441                 pause_audio_devices();
    442                 [session setActive:NO error:nil];
    443                 session_active = NO;
    444 
    445                 if (![session setCategory:category error:&err]) {
    446                     NSString *desc = err.description;
    447                     SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
    448                     return NO;
    449                 }
    450             }
    451         }
    452 
    453         if ((open_playback_devices || open_capture_devices) && !session_active) {
    454             if (![session setActive:YES error:&err]) {
    455                 if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable &&
    456                     category == AVAudioSessionCategoryPlayAndRecord) {
    457                     return update_audio_session(this, open, SDL_FALSE);
    458                 }
    459 
    460                 NSString *desc = err.description;
    461                 SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
    462                 return NO;
    463             }
    464             session_active = YES;
    465             resume_audio_devices();
    466         } else if (!open_playback_devices && !open_capture_devices && session_active) {
    467             pause_audio_devices();
    468             [session setActive:NO error:nil];
    469             session_active = NO;
    470         }
    471 
    472         if (open) {
    473             SDLInterruptionListener *listener = [SDLInterruptionListener new];
    474             listener.device = this;
    475 
    476             [center addObserver:listener
    477                        selector:@selector(audioSessionInterruption:)
    478                            name:AVAudioSessionInterruptionNotification
    479                          object:session];
    480 
    481             /* An interruption end notification is not guaranteed to be sent if
    482              we were previously interrupted... resuming if needed when the app
    483              becomes active seems to be the way to go. */
    484             // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications.  johna
    485             [center addObserver:listener
    486                        selector:@selector(applicationBecameActive:)
    487                            name:UIApplicationDidBecomeActiveNotification
    488                          object:nil];
    489 
    490             [center addObserver:listener
    491                        selector:@selector(applicationBecameActive:)
    492                            name:UIApplicationWillEnterForegroundNotification
    493                          object:nil];
    494 
    495             this->hidden->interruption_listener = CFBridgingRetain(listener);
    496         } else {
    497             SDLInterruptionListener *listener = nil;
    498             listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener);
    499             [center removeObserver:listener];
    500             @synchronized (listener) {
    501                 listener.device = NULL;
    502             }
    503         }
    504     }
    505 
    506     return YES;
    507 }
    508 #endif
    509 
    510 
    511 /* The AudioQueue callback */
    512 static void
    513 outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
    514 {
    515     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
    516     if (SDL_AtomicGet(&this->hidden->shutdown)) {
    517         return;  /* don't do anything. */
    518     }
    519 
    520     if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
    521         /* Supply silence if audio is not enabled or paused */
    522         SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity);
    523     } else if (this->stream) {
    524         UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
    525         Uint8 *ptr = (Uint8 *) inBuffer->mAudioData;
    526 
    527         while (remaining > 0) {
    528             if (SDL_AudioStreamAvailable(this->stream) == 0) {
    529                 /* Generate the data */
    530                 SDL_LockMutex(this->mixer_lock);
    531                 (*this->callbackspec.callback)(this->callbackspec.userdata,
    532                                                this->hidden->buffer, this->hidden->bufferSize);
    533                 SDL_UnlockMutex(this->mixer_lock);
    534                 this->hidden->bufferOffset = 0;
    535                 SDL_AudioStreamPut(this->stream, this->hidden->buffer, this->hidden->bufferSize);
    536             }
    537             if (SDL_AudioStreamAvailable(this->stream) > 0) {
    538                 int got;
    539                 UInt32 len = SDL_AudioStreamAvailable(this->stream);
    540                 if (len > remaining)
    541                     len = remaining;
    542                 got = SDL_AudioStreamGet(this->stream, ptr, len);
    543                 SDL_assert((got < 0) || (got == len));
    544                 if (got != len) {
    545                     SDL_memset(ptr, this->spec.silence, len);
    546                 }
    547                 ptr = ptr + len;
    548                 remaining -= len;
    549             }
    550         }
    551     } else {
    552         UInt32 remaining = inBuffer->mAudioDataBytesCapacity;
    553         Uint8 *ptr = (Uint8 *) inBuffer->mAudioData;
    554 
    555         while (remaining > 0) {
    556             UInt32 len;
    557             if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
    558                 /* Generate the data */
    559                 SDL_LockMutex(this->mixer_lock);
    560                 (*this->callbackspec.callback)(this->callbackspec.userdata,
    561                             this->hidden->buffer, this->hidden->bufferSize);
    562                 SDL_UnlockMutex(this->mixer_lock);
    563                 this->hidden->bufferOffset = 0;
    564             }
    565 
    566             len = this->hidden->bufferSize - this->hidden->bufferOffset;
    567             if (len > remaining) {
    568                 len = remaining;
    569             }
    570             SDL_memcpy(ptr, (char *)this->hidden->buffer +
    571                        this->hidden->bufferOffset, len);
    572             ptr = ptr + len;
    573             remaining -= len;
    574             this->hidden->bufferOffset += len;
    575         }
    576     }
    577 
    578     AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
    579 
    580     inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
    581 }
    582 
    583 static void
    584 inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
    585               const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
    586               const AudioStreamPacketDescription *inPacketDescs)
    587 {
    588     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
    589 
    590     if (SDL_AtomicGet(&this->shutdown)) {
    591         return;  /* don't do anything. */
    592     }
    593 
    594     /* ignore unless we're active. */
    595     if (!SDL_AtomicGet(&this->paused) && SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) {
    596         const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData;
    597         UInt32 remaining = inBuffer->mAudioDataByteSize;
    598         while (remaining > 0) {
    599             UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset;
    600             if (len > remaining) {
    601                 len = remaining;
    602             }
    603 
    604             SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len);
    605             ptr += len;
    606             remaining -= len;
    607             this->hidden->bufferOffset += len;
    608 
    609             if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
    610                 SDL_LockMutex(this->mixer_lock);
    611                 (*this->callbackspec.callback)(this->callbackspec.userdata, this->hidden->buffer, this->hidden->bufferSize);
    612                 SDL_UnlockMutex(this->mixer_lock);
    613                 this->hidden->bufferOffset = 0;
    614             }
    615         }
    616     }
    617 
    618     AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
    619 }
    620 
    621 
    622 #if MACOSX_COREAUDIO
    623 static const AudioObjectPropertyAddress alive_address =
    624 {
    625     kAudioDevicePropertyDeviceIsAlive,
    626     kAudioObjectPropertyScopeGlobal,
    627     kAudioObjectPropertyElementMaster
    628 };
    629 
    630 static OSStatus
    631 device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
    632 {
    633     SDL_AudioDevice *this = (SDL_AudioDevice *) data;
    634     SDL_bool dead = SDL_FALSE;
    635     UInt32 isAlive = 1;
    636     UInt32 size = sizeof (isAlive);
    637     OSStatus error;
    638 
    639     if (!SDL_AtomicGet(&this->enabled)) {
    640         return 0;  /* already known to be dead. */
    641     }
    642 
    643     error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
    644                                        0, NULL, &size, &isAlive);
    645 
    646     if (error == kAudioHardwareBadDeviceError) {
    647         dead = SDL_TRUE;  /* device was unplugged. */
    648     } else if ((error == kAudioHardwareNoError) && (!isAlive)) {
    649         dead = SDL_TRUE;  /* device died in some other way. */
    650     }
    651 
    652     if (dead) {
    653         SDL_OpenedAudioDeviceDisconnected(this);
    654     }
    655 
    656     return 0;
    657 }
    658 
    659 /* macOS calls this when the default device changed (if we have a default device open). */
    660 static OSStatus
    661 default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
    662 {
    663     SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
    664     #if DEBUG_COREAUDIO
    665     printf("COREAUDIO: default device changed for SDL audio device %p!\n", this);
    666     #endif
    667     SDL_AtomicSet(&this->hidden->device_change_flag, 1);  /* let the audioqueue thread pick up on this when safe to do so. */
    668     return noErr;
    669 }
    670 #endif
    671 
    672 static void
    673 COREAUDIO_CloseDevice(_THIS)
    674 {
    675     const SDL_bool iscapture = this->iscapture;
    676     int i;
    677 
    678 /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
    679 /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
    680 #if MACOSX_COREAUDIO
    681     if (this->handle != NULL) {  /* we don't register this listener for default devices. */
    682         AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
    683     }
    684 #endif
    685 
    686     if (iscapture) {
    687         open_capture_devices--;
    688     } else {
    689         open_playback_devices--;
    690     }
    691 
    692 #if !MACOSX_COREAUDIO
    693     update_audio_session(this, SDL_FALSE, SDL_TRUE);
    694 #endif
    695 
    696     for (i = 0; i < num_open_devices; ++i) {
    697         if (open_devices[i] == this) {
    698             --num_open_devices;
    699             if (i < num_open_devices) {
    700                 SDL_memmove(&open_devices[i], &open_devices[i+1], sizeof(open_devices[i])*(num_open_devices - i));
    701             }
    702             break;
    703         }
    704     }
    705     if (num_open_devices == 0) {
    706         SDL_free(open_devices);
    707         open_devices = NULL;
    708     }
    709 
    710     /* if callback fires again, feed silence; don't call into the app. */
    711     SDL_AtomicSet(&this->paused, 1);
    712 
    713     if (this->hidden->audioQueue) {
    714         AudioQueueDispose(this->hidden->audioQueue, 1);
    715     }
    716 
    717     if (this->hidden->thread) {
    718         SDL_AtomicSet(&this->hidden->shutdown, 1);
    719         SDL_WaitThread(this->hidden->thread, NULL);
    720     }
    721 
    722     if (this->hidden->ready_semaphore) {
    723         SDL_DestroySemaphore(this->hidden->ready_semaphore);
    724     }
    725 
    726     /* AudioQueueDispose() frees the actual buffer objects. */
    727     SDL_free(this->hidden->audioBuffer);
    728     SDL_free(this->hidden->thread_error);
    729     SDL_free(this->hidden->buffer);
    730     SDL_free(this->hidden);
    731 }
    732 
    733 #if MACOSX_COREAUDIO
    734 static int
    735 prepare_device(_THIS, void *handle, int iscapture)
    736 {
    737     AudioDeviceID devid = (AudioDeviceID) ((size_t) handle);
    738     OSStatus result = noErr;
    739     UInt32 size = 0;
    740     UInt32 alive = 0;
    741     pid_t pid = 0;
    742 
    743     AudioObjectPropertyAddress addr = {
    744         0,
    745         kAudioObjectPropertyScopeGlobal,
    746         kAudioObjectPropertyElementMaster
    747     };
    748 
    749     if (handle == NULL) {
    750         size = sizeof (AudioDeviceID);
    751         addr.mSelector =
    752             ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
    753             kAudioHardwarePropertyDefaultOutputDevice);
    754         result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
    755                                             0, NULL, &size, &devid);
    756         CHECK_RESULT("AudioHardwareGetProperty (default device)");
    757     }
    758 
    759     addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
    760     addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
    761                     kAudioDevicePropertyScopeOutput;
    762 
    763     size = sizeof (alive);
    764     result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
    765     CHECK_RESULT
    766         ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
    767 
    768     if (!alive) {
    769         SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
    770         return 0;
    771     }
    772 
    773     addr.mSelector = kAudioDevicePropertyHogMode;
    774     size = sizeof (pid);
    775     result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
    776 
    777     /* some devices don't support this property, so errors are fine here. */
    778     if ((result == noErr) && (pid != -1)) {
    779         SDL_SetError("CoreAudio: requested device is being hogged.");
    780         return 0;
    781     }
    782 
    783     this->hidden->deviceID = devid;
    784     return 1;
    785 }
    786 
    787 static int
    788 assign_device_to_audioqueue(_THIS)
    789 {
    790     const AudioObjectPropertyAddress prop = {
    791         kAudioDevicePropertyDeviceUID,
    792         this->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
    793         kAudioObjectPropertyElementMaster
    794     };
    795 
    796     OSStatus result;
    797     CFStringRef devuid;
    798     UInt32 devuidsize = sizeof (devuid);
    799     result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
    800     CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
    801     result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
    802     CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
    803 
    804     return 1;
    805 }
    806 #endif
    807 
    808 static int
    809 prepare_audioqueue(_THIS)
    810 {
    811     const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
    812     const int iscapture = this->iscapture;
    813     OSStatus result;
    814     int i;
    815 
    816     SDL_assert(CFRunLoopGetCurrent() != NULL);
    817 
    818     if (iscapture) {
    819         result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
    820         CHECK_RESULT("AudioQueueNewInput");
    821     } else {
    822         result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
    823         CHECK_RESULT("AudioQueueNewOutput");
    824     }
    825 
    826     #if MACOSX_COREAUDIO
    827     if (!assign_device_to_audioqueue(this)) {
    828         return 0;
    829     }
    830 
    831     /* only listen for unplugging on specific devices, not the default device, as that should
    832        switch to a different device (or hang out silently if there _is_ no other device). */
    833     if (this->handle != NULL) {
    834         /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
    835         /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
    836         /* Fire a callback if the device stops being "alive" (disconnected, etc). */
    837         /* If this fails, oh well, we won't notice a device had an extraordinary event take place. */
    838         AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
    839     }
    840     #endif
    841 
    842     /* Calculate the final parameters for this audio specification */
    843     SDL_CalculateAudioSpec(&this->spec);
    844 
    845     /* Set the channel layout for the audio queue */
    846     AudioChannelLayout layout;
    847     SDL_zero(layout);
    848     switch (this->spec.channels) {
    849     case 1:
    850         layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
    851         break;
    852     case 2:
    853         layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
    854         break;
    855     case 3:
    856         layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
    857         break;
    858     case 4:
    859         layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
    860         break;
    861     case 5:
    862         layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_0_A;
    863         break;
    864     case 6:
    865         layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A;
    866         break;
    867     case 7:
    868         /* FIXME: Need to move channel[4] (BC) to channel[6] */
    869         layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
    870         break;
    871     case 8:
    872         layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A;
    873         break;
    874     }
    875     if (layout.mChannelLayoutTag != 0) {
    876         result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
    877         CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)");
    878     }
    879 
    880     /* Allocate a sample buffer */
    881     this->hidden->bufferSize = this->spec.size;
    882     this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
    883 
    884     this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
    885     if (this->hidden->buffer == NULL) {
    886         SDL_OutOfMemory();
    887         return 0;
    888     }
    889 
    890     /* Make sure we can feed the device a minimum amount of time */
    891     double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
    892 #if defined(__IPHONEOS__)
    893     if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
    894         /* Older iOS hardware, use 40 ms as a minimum time */
    895         MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
    896     }
    897 #endif
    898     const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0;
    899     int numAudioBuffers = 2;
    900     if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) {  /* use more buffers if we have a VERY small sample set. */
    901         numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
    902     }
    903 
    904     this->hidden->numAudioBuffers = numAudioBuffers;
    905     this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
    906     if (this->hidden->audioBuffer == NULL) {
    907         SDL_OutOfMemory();
    908         return 0;
    909     }
    910 
    911 #if DEBUG_COREAUDIO
    912     printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
    913 #endif
    914 
    915     for (i = 0; i < numAudioBuffers; i++) {
    916         result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]);
    917         CHECK_RESULT("AudioQueueAllocateBuffer");
    918         SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
    919         this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
    920         /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */
    921         result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL);
    922         CHECK_RESULT("AudioQueueEnqueueBuffer");
    923     }
    924 
    925     result = AudioQueueStart(this->hidden->audioQueue, NULL);
    926     CHECK_RESULT("AudioQueueStart");
    927 
    928     /* We're running! */
    929     return 1;
    930 }
    931 
    932 static int
    933 audioqueue_thread(void *arg)
    934 {
    935     SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
    936 
    937     #if MACOSX_COREAUDIO
    938     const AudioObjectPropertyAddress default_device_address = {
    939         this->iscapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
    940         kAudioObjectPropertyScopeGlobal,
    941         kAudioObjectPropertyElementMaster
    942     };
    943 
    944     if (this->handle == NULL) {  /* opened the default device? Register to know if the user picks a new default. */
    945         /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */
    946         AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, this);
    947     }
    948     #endif
    949 
    950     const int rc = prepare_audioqueue(this);
    951     if (!rc) {
    952         this->hidden->thread_error = SDL_strdup(SDL_GetError());
    953         SDL_SemPost(this->hidden->ready_semaphore);
    954         return 0;
    955     }
    956 
    957     SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
    958 
    959     /* init was successful, alert parent thread and start running... */
    960     SDL_SemPost(this->hidden->ready_semaphore);
    961 
    962     while (!SDL_AtomicGet(&this->hidden->shutdown)) {
    963         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
    964 
    965         #if MACOSX_COREAUDIO
    966         if ((this->handle == NULL) && SDL_AtomicGet(&this->hidden->device_change_flag)) {
    967             SDL_AtomicSet(&this->hidden->device_change_flag, 0);
    968 
    969             #if DEBUG_COREAUDIO
    970             printf("COREAUDIO: audioqueue_thread is trying to switch to new default device!\n");
    971             #endif
    972 
    973             /* if any of this fails, there's not much to do but wait to see if the user gives up
    974                and quits (flagging the audioqueue for shutdown), or toggles to some other system
    975                output device (in which case we'll try again). */
    976             const AudioDeviceID prev_devid = this->hidden->deviceID;
    977             if (prepare_device(this, this->handle, this->iscapture) && (prev_devid != this->hidden->deviceID)) {
    978                 AudioQueueStop(this->hidden->audioQueue, 1);
    979                 if (assign_device_to_audioqueue(this)) {
    980                     int i;
    981                     for (i = 0; i < this->hidden->numAudioBuffers; i++) {
    982                         SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
    983                         /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */
    984                         AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL);
    985                     }
    986                     AudioQueueStart(this->hidden->audioQueue, NULL);
    987                 }
    988             }
    989         }
    990         #endif
    991     }
    992 
    993     if (!this->iscapture) {  /* Drain off any pending playback. */
    994         const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0;
    995         CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
    996     }
    997 
    998     #if MACOSX_COREAUDIO
    999     if (this->handle == NULL) {
   1000         /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */
   1001         AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, this);
   1002     }
   1003     #endif
   1004 
   1005     return 0;
   1006 }
   1007 
   1008 static int
   1009 COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   1010 {
   1011     AudioStreamBasicDescription *strdesc;
   1012     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   1013     int valid_datatype = 0;
   1014     SDL_AudioDevice **new_open_devices;
   1015 
   1016     /* Initialize all variables that we clean on shutdown */
   1017     this->hidden = (struct SDL_PrivateAudioData *)
   1018         SDL_malloc((sizeof *this->hidden));
   1019     if (this->hidden == NULL) {
   1020         return SDL_OutOfMemory();
   1021     }
   1022     SDL_zerop(this->hidden);
   1023 
   1024     strdesc = &this->hidden->strdesc;
   1025 
   1026     if (iscapture) {
   1027         open_capture_devices++;
   1028     } else {
   1029         open_playback_devices++;
   1030     }
   1031 
   1032     new_open_devices = (SDL_AudioDevice **)SDL_realloc(open_devices, sizeof(open_devices[0]) * (num_open_devices + 1));
   1033     if (new_open_devices) {
   1034         open_devices = new_open_devices;
   1035         open_devices[num_open_devices++] = this;
   1036     }
   1037 
   1038 #if !MACOSX_COREAUDIO
   1039     if (!update_audio_session(this, SDL_TRUE, SDL_TRUE)) {
   1040         return -1;
   1041     }
   1042 
   1043     /* Stop CoreAudio from doing expensive audio rate conversion */
   1044     @autoreleasepool {
   1045         AVAudioSession* session = [AVAudioSession sharedInstance];
   1046         [session setPreferredSampleRate:this->spec.freq error:nil];
   1047         this->spec.freq = (int)session.sampleRate;
   1048 #if TARGET_OS_TV
   1049         if (iscapture) {
   1050             [session setPreferredInputNumberOfChannels:this->spec.channels error:nil];
   1051             this->spec.channels = session.preferredInputNumberOfChannels;
   1052         } else {
   1053             [session setPreferredOutputNumberOfChannels:this->spec.channels error:nil];
   1054             this->spec.channels = session.preferredOutputNumberOfChannels;
   1055         }
   1056 #else
   1057         /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */
   1058 #endif /* TARGET_OS_TV */
   1059     }
   1060 #endif
   1061 
   1062     /* Setup a AudioStreamBasicDescription with the requested format */
   1063     SDL_zerop(strdesc);
   1064     strdesc->mFormatID = kAudioFormatLinearPCM;
   1065     strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
   1066     strdesc->mChannelsPerFrame = this->spec.channels;
   1067     strdesc->mSampleRate = this->spec.freq;
   1068     strdesc->mFramesPerPacket = 1;
   1069 
   1070     while ((!valid_datatype) && (test_format)) {
   1071         this->spec.format = test_format;
   1072         /* CoreAudio handles most of SDL's formats natively, but not U16, apparently. */
   1073         switch (test_format) {
   1074         case AUDIO_U8:
   1075         case AUDIO_S8:
   1076         case AUDIO_S16LSB:
   1077         case AUDIO_S16MSB:
   1078         case AUDIO_S32LSB:
   1079         case AUDIO_S32MSB:
   1080         case AUDIO_F32LSB:
   1081         case AUDIO_F32MSB:
   1082             valid_datatype = 1;
   1083             strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
   1084             if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
   1085                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
   1086 
   1087             if (SDL_AUDIO_ISFLOAT(this->spec.format))
   1088                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
   1089             else if (SDL_AUDIO_ISSIGNED(this->spec.format))
   1090                 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
   1091             break;
   1092 
   1093         default:
   1094             test_format = SDL_NextAudioFormat();
   1095             break;
   1096         }
   1097     }
   1098 
   1099     if (!valid_datatype) {      /* shouldn't happen, but just in case... */
   1100         return SDL_SetError("Unsupported audio format");
   1101     }
   1102 
   1103     strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8;
   1104     strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
   1105 
   1106 #if MACOSX_COREAUDIO
   1107     if (!prepare_device(this, handle, iscapture)) {
   1108         return -1;
   1109     }
   1110 #endif
   1111 
   1112     /* This has to init in a new thread so it can get its own CFRunLoop. :/ */
   1113     SDL_AtomicSet(&this->hidden->shutdown, 0);
   1114     this->hidden->ready_semaphore = SDL_CreateSemaphore(0);
   1115     if (!this->hidden->ready_semaphore) {
   1116         return -1;  /* oh well. */
   1117     }
   1118 
   1119     this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, this);
   1120     if (!this->hidden->thread) {
   1121         return -1;
   1122     }
   1123 
   1124     SDL_SemWait(this->hidden->ready_semaphore);
   1125     SDL_DestroySemaphore(this->hidden->ready_semaphore);
   1126     this->hidden->ready_semaphore = NULL;
   1127 
   1128     if ((this->hidden->thread != NULL) && (this->hidden->thread_error != NULL)) {
   1129         SDL_SetError("%s", this->hidden->thread_error);
   1130         return -1;
   1131     }
   1132 
   1133     return (this->hidden->thread != NULL) ? 0 : -1;
   1134 }
   1135 
   1136 static void
   1137 COREAUDIO_Deinitialize(void)
   1138 {
   1139 #if MACOSX_COREAUDIO
   1140     AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   1141     free_audio_device_list(&capture_devs);
   1142     free_audio_device_list(&output_devs);
   1143 #endif
   1144 }
   1145 
   1146 static int
   1147 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
   1148 {
   1149     /* Set the function pointers */
   1150     impl->OpenDevice = COREAUDIO_OpenDevice;
   1151     impl->CloseDevice = COREAUDIO_CloseDevice;
   1152     impl->Deinitialize = COREAUDIO_Deinitialize;
   1153 
   1154 #if MACOSX_COREAUDIO
   1155     impl->DetectDevices = COREAUDIO_DetectDevices;
   1156     AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   1157 #else
   1158     impl->OnlyHasDefaultOutputDevice = 1;
   1159     impl->OnlyHasDefaultCaptureDevice = 1;
   1160 #endif
   1161 
   1162     impl->ProvidesOwnCallbackThread = 1;
   1163     impl->HasCaptureSupport = 1;
   1164 
   1165     return 1;   /* this audio target is available. */
   1166 }
   1167 
   1168 AudioBootStrap COREAUDIO_bootstrap = {
   1169     "coreaudio", "CoreAudio", COREAUDIO_Init, 0
   1170 };
   1171 
   1172 #endif /* SDL_AUDIO_DRIVER_COREAUDIO */
   1173 
   1174 /* vi: set ts=4 sw=4 expandtab: */