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