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