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