1/*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2016 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
43static const AudioObjectPropertyAddress devlist_address = {
44    kAudioHardwarePropertyDevices,
45    kAudioObjectPropertyScopeGlobal,
46    kAudioObjectPropertyElementMaster
47};
48
49typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
50
51typedef struct AudioDeviceList
52{
53    AudioDeviceID devid;
54    SDL_bool alive;
55    struct AudioDeviceList *next;
56} AudioDeviceList;
57
58static AudioDeviceList *output_devs = NULL;
59static AudioDeviceList *capture_devs = NULL;
60
61static SDL_bool
62add_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
80static void
81addToDevList(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
88static void
89build_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
196static void
197free_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
208static void
209COREAUDIO_DetectDevices(void)
210{
211    build_device_list(SDL_TRUE, addToDevList, NULL);
212    build_device_list(SDL_FALSE, addToDevList, NULL);
213}
214
215static void
216build_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
231static void
232reprocess_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. */
262static OSStatus
263device_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
272static int open_playback_devices = 0;
273static int open_capture_devices = 0;
274
275#if !MACOSX_COREAUDIO
276
277static 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
285static 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
323static 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 */
399static void
400outputCallback(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
440static void
441inputCallback(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
476static const AudioObjectPropertyAddress alive_address =
477{
478    kAudioDevicePropertyDeviceIsAlive,
479    kAudioObjectPropertyScopeGlobal,
480    kAudioObjectPropertyElementMaster
481};
482
483static OSStatus
484device_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
513static void
514COREAUDIO_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
560static int
561prepare_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
614static int
615prepare_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
682static int
683audioqueue_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
710static int
711COREAUDIO_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
811static void
812COREAUDIO_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
821static int
822COREAUDIO_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
843AudioBootStrap 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: */
850