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