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