1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2016 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_audio_c.h" 29#include "../SDL_sysaudio.h" 30#include "SDL_coreaudio.h" 31#include "SDL_assert.h" 32#include "../../thread/SDL_systhread.h" 33 34#define DEBUG_COREAUDIO 0 35 36#define CHECK_RESULT(msg) \ 37 if (result != noErr) { \ 38 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ 39 return 0; \ 40 } 41 42#if MACOSX_COREAUDIO 43static const AudioObjectPropertyAddress devlist_address = { 44 kAudioHardwarePropertyDevices, 45 kAudioObjectPropertyScopeGlobal, 46 kAudioObjectPropertyElementMaster 47}; 48 49typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data); 50 51typedef struct AudioDeviceList 52{ 53 AudioDeviceID devid; 54 SDL_bool alive; 55 struct AudioDeviceList *next; 56} AudioDeviceList; 57 58static AudioDeviceList *output_devs = NULL; 59static AudioDeviceList *capture_devs = NULL; 60 61static SDL_bool 62add_to_internal_dev_list(const int iscapture, AudioDeviceID devId) 63{ 64 AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList)); 65 if (item == NULL) { 66 return SDL_FALSE; 67 } 68 item->devid = devId; 69 item->alive = SDL_TRUE; 70 item->next = iscapture ? capture_devs : output_devs; 71 if (iscapture) { 72 capture_devs = item; 73 } else { 74 output_devs = item; 75 } 76 77 return SDL_TRUE; 78} 79 80static void 81addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data) 82{ 83 if (add_to_internal_dev_list(iscapture, devId)) { 84 SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId)); 85 } 86} 87 88static void 89build_device_list(int iscapture, addDevFn addfn, void *addfndata) 90{ 91 OSStatus result = noErr; 92 UInt32 size = 0; 93 AudioDeviceID *devs = NULL; 94 UInt32 i = 0; 95 UInt32 max = 0; 96 97 result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, 98 &devlist_address, 0, NULL, &size); 99 if (result != kAudioHardwareNoError) 100 return; 101 102 devs = (AudioDeviceID *) alloca(size); 103 if (devs == NULL) 104 return; 105 106 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 107 &devlist_address, 0, NULL, &size, devs); 108 if (result != kAudioHardwareNoError) 109 return; 110 111 max = size / sizeof (AudioDeviceID); 112 for (i = 0; i < max; i++) { 113 CFStringRef cfstr = NULL; 114 char *ptr = NULL; 115 AudioDeviceID dev = devs[i]; 116 AudioBufferList *buflist = NULL; 117 int usable = 0; 118 CFIndex len = 0; 119 const AudioObjectPropertyAddress addr = { 120 kAudioDevicePropertyStreamConfiguration, 121 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 122 kAudioObjectPropertyElementMaster 123 }; 124 125 const AudioObjectPropertyAddress nameaddr = { 126 kAudioObjectPropertyName, 127 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 128 kAudioObjectPropertyElementMaster 129 }; 130 131 result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); 132 if (result != noErr) 133 continue; 134 135 buflist = (AudioBufferList *) SDL_malloc(size); 136 if (buflist == NULL) 137 continue; 138 139 result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, 140 &size, buflist); 141 142 if (result == noErr) { 143 UInt32 j; 144 for (j = 0; j < buflist->mNumberBuffers; j++) { 145 if (buflist->mBuffers[j].mNumberChannels > 0) { 146 usable = 1; 147 break; 148 } 149 } 150 } 151 152 SDL_free(buflist); 153 154 if (!usable) 155 continue; 156 157 158 size = sizeof (CFStringRef); 159 result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr); 160 if (result != kAudioHardwareNoError) 161 continue; 162 163 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), 164 kCFStringEncodingUTF8); 165 166 ptr = (char *) SDL_malloc(len + 1); 167 usable = ((ptr != NULL) && 168 (CFStringGetCString 169 (cfstr, ptr, len + 1, kCFStringEncodingUTF8))); 170 171 CFRelease(cfstr); 172 173 if (usable) { 174 len = strlen(ptr); 175 /* Some devices have whitespace at the end...trim it. */ 176 while ((len > 0) && (ptr[len - 1] == ' ')) { 177 len--; 178 } 179 usable = (len > 0); 180 } 181 182 if (usable) { 183 ptr[len] = '\0'; 184 185#if DEBUG_COREAUDIO 186 printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", 187 ((iscapture) ? "capture" : "output"), 188 (int) i, ptr, (int) dev); 189#endif 190 addfn(ptr, iscapture, dev, addfndata); 191 } 192 SDL_free(ptr); /* addfn() would have copied the string. */ 193 } 194} 195 196static void 197free_audio_device_list(AudioDeviceList **list) 198{ 199 AudioDeviceList *item = *list; 200 while (item) { 201 AudioDeviceList *next = item->next; 202 SDL_free(item); 203 item = next; 204 } 205 *list = NULL; 206} 207 208static void 209COREAUDIO_DetectDevices(void) 210{ 211 build_device_list(SDL_TRUE, addToDevList, NULL); 212 build_device_list(SDL_FALSE, addToDevList, NULL); 213} 214 215static void 216build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data) 217{ 218 AudioDeviceList **list = (AudioDeviceList **) data; 219 AudioDeviceList *item; 220 for (item = *list; item != NULL; item = item->next) { 221 if (item->devid == devId) { 222 item->alive = SDL_TRUE; 223 return; 224 } 225 } 226 227 add_to_internal_dev_list(iscapture, devId); /* new device, add it. */ 228 SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId)); 229} 230 231static void 232reprocess_device_list(const int iscapture, AudioDeviceList **list) 233{ 234 AudioDeviceList *item; 235 AudioDeviceList *prev = NULL; 236 for (item = *list; item != NULL; item = item->next) { 237 item->alive = SDL_FALSE; 238 } 239 240 build_device_list(iscapture, build_device_change_list, list); 241 242 /* free items in the list that aren't still alive. */ 243 item = *list; 244 while (item != NULL) { 245 AudioDeviceList *next = item->next; 246 if (item->alive) { 247 prev = item; 248 } else { 249 SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid)); 250 if (prev) { 251 prev->next = item->next; 252 } else { 253 *list = item->next; 254 } 255 SDL_free(item); 256 } 257 item = next; 258 } 259} 260 261/* this is called when the system's list of available audio devices changes. */ 262static OSStatus 263device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) 264{ 265 reprocess_device_list(SDL_TRUE, &capture_devs); 266 reprocess_device_list(SDL_FALSE, &output_devs); 267 return 0; 268} 269#endif 270 271 272static int open_playback_devices = 0; 273static int open_capture_devices = 0; 274 275#if !MACOSX_COREAUDIO 276 277static void interruption_begin(_THIS) 278{ 279 if (this != NULL && this->hidden->audioQueue != NULL) { 280 this->hidden->interrupted = SDL_TRUE; 281 AudioQueuePause(this->hidden->audioQueue); 282 } 283} 284 285static void interruption_end(_THIS) 286{ 287 if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL 288 && this->hidden->interrupted) { 289 this->hidden->interrupted = SDL_FALSE; 290 AudioQueueStart(this->hidden->audioQueue, NULL); 291 } 292} 293 294@interface SDLInterruptionListener : NSObject 295 296@property (nonatomic, assign) SDL_AudioDevice *device; 297 298@end 299 300@implementation SDLInterruptionListener 301 302- (void)audioSessionInterruption:(NSNotification *)note 303{ 304 @synchronized (self) { 305 NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; 306 if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { 307 interruption_begin(self.device); 308 } else { 309 interruption_end(self.device); 310 } 311 } 312} 313 314- (void)applicationBecameActive:(NSNotification *)note 315{ 316 @synchronized (self) { 317 interruption_end(self.device); 318 } 319} 320 321@end 322 323static BOOL update_audio_session(_THIS, SDL_bool open) 324{ 325 @autoreleasepool { 326 AVAudioSession *session = [AVAudioSession sharedInstance]; 327 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 328 NSString *category; 329 NSError *err = nil; 330 331 if (open_playback_devices && open_capture_devices) { 332 category = AVAudioSessionCategoryPlayAndRecord; 333 } else if (open_capture_devices) { 334 category = AVAudioSessionCategoryRecord; 335 } else { 336 /* Set category to ambient so that other music continues playing. 337 You can change this at runtime in your own code if you need different 338 behavior. If this is common, we can add an SDL hint for this. */ 339 category = AVAudioSessionCategoryAmbient; 340 } 341 342 if (![session setCategory:category error:&err]) { 343 NSString *desc = err.description; 344 SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); 345 return NO; 346 } 347 348 if (open_playback_devices + open_capture_devices == 1) { 349 if (![session setActive:YES error:&err]) { 350 NSString *desc = err.description; 351 SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); 352 return NO; 353 } 354 } else if (!open_playback_devices && !open_capture_devices) { 355 [session setActive:NO error:nil]; 356 } 357 358 if (open) { 359 SDLInterruptionListener *listener = [SDLInterruptionListener new]; 360 listener.device = this; 361 362 [center addObserver:listener 363 selector:@selector(audioSessionInterruption:) 364 name:AVAudioSessionInterruptionNotification 365 object:session]; 366 367 /* An interruption end notification is not guaranteed to be sent if 368 we were previously interrupted... resuming if needed when the app 369 becomes active seems to be the way to go. */ 370 [center addObserver:listener 371 selector:@selector(applicationBecameActive:) 372 name:UIApplicationDidBecomeActiveNotification 373 object:session]; 374 375 [center addObserver:listener 376 selector:@selector(applicationBecameActive:) 377 name:UIApplicationWillEnterForegroundNotification 378 object:session]; 379 380 this->hidden->interruption_listener = CFBridgingRetain(listener); 381 } else { 382 if (this->hidden->interruption_listener != NULL) { 383 SDLInterruptionListener *listener = nil; 384 listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener); 385 @synchronized (listener) { 386 listener.device = NULL; 387 } 388 [center removeObserver:listener]; 389 } 390 } 391 } 392 393 return YES; 394} 395#endif 396 397 398/* The AudioQueue callback */ 399static void 400outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) 401{ 402 SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; 403 if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) { 404 /* Supply silence if audio is enabled and not paused */ 405 SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity); 406 } else { 407 UInt32 remaining = inBuffer->mAudioDataBytesCapacity; 408 Uint8 *ptr = (Uint8 *) inBuffer->mAudioData; 409 410 while (remaining > 0) { 411 UInt32 len; 412 if (this->hidden->bufferOffset >= this->hidden->bufferSize) { 413 /* Generate the data */ 414 SDL_LockMutex(this->mixer_lock); 415 (*this->spec.callback)(this->spec.userdata, 416 this->hidden->buffer, this->hidden->bufferSize); 417 SDL_UnlockMutex(this->mixer_lock); 418 this->hidden->bufferOffset = 0; 419 } 420 421 len = this->hidden->bufferSize - this->hidden->bufferOffset; 422 if (len > remaining) { 423 len = remaining; 424 } 425 SDL_memcpy(ptr, (char *)this->hidden->buffer + 426 this->hidden->bufferOffset, len); 427 ptr = ptr + len; 428 remaining -= len; 429 this->hidden->bufferOffset += len; 430 } 431 } 432 433 if (!SDL_AtomicGet(&this->hidden->shutdown)) { 434 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); 435 } 436 437 inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; 438} 439 440static void 441inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, 442 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, 443 const AudioStreamPacketDescription *inPacketDescs ) 444{ 445 SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; 446 if (SDL_AtomicGet(&this->enabled) && !SDL_AtomicGet(&this->paused)) { /* ignore unless we're active. */ 447 const Uint8 *ptr = (const Uint8 *) inBuffer->mAudioData; 448 UInt32 remaining = inBuffer->mAudioDataByteSize; 449 while (remaining > 0) { 450 UInt32 len = this->hidden->bufferSize - this->hidden->bufferOffset; 451 if (len > remaining) { 452 len = remaining; 453 } 454 455 SDL_memcpy((char *)this->hidden->buffer + this->hidden->bufferOffset, ptr, len); 456 ptr += len; 457 remaining -= len; 458 this->hidden->bufferOffset += len; 459 460 if (this->hidden->bufferOffset >= this->hidden->bufferSize) { 461 SDL_LockMutex(this->mixer_lock); 462 (*this->spec.callback)(this->spec.userdata, this->hidden->buffer, this->hidden->bufferSize); 463 SDL_UnlockMutex(this->mixer_lock); 464 this->hidden->bufferOffset = 0; 465 } 466 } 467 } 468 469 if (!SDL_AtomicGet(&this->hidden->shutdown)) { 470 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); 471 } 472} 473 474 475#if MACOSX_COREAUDIO 476static const AudioObjectPropertyAddress alive_address = 477{ 478 kAudioDevicePropertyDeviceIsAlive, 479 kAudioObjectPropertyScopeGlobal, 480 kAudioObjectPropertyElementMaster 481}; 482 483static OSStatus 484device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) 485{ 486 SDL_AudioDevice *this = (SDL_AudioDevice *) data; 487 SDL_bool dead = SDL_FALSE; 488 UInt32 isAlive = 1; 489 UInt32 size = sizeof (isAlive); 490 OSStatus error; 491 492 if (!SDL_AtomicGet(&this->enabled)) { 493 return 0; /* already known to be dead. */ 494 } 495 496 error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address, 497 0, NULL, &size, &isAlive); 498 499 if (error == kAudioHardwareBadDeviceError) { 500 dead = SDL_TRUE; /* device was unplugged. */ 501 } else if ((error == kAudioHardwareNoError) && (!isAlive)) { 502 dead = SDL_TRUE; /* device died in some other way. */ 503 } 504 505 if (dead) { 506 SDL_OpenedAudioDeviceDisconnected(this); 507 } 508 509 return 0; 510} 511#endif 512 513static void 514COREAUDIO_CloseDevice(_THIS) 515{ 516 const SDL_bool iscapture = this->iscapture; 517 int i; 518 519/* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ 520/* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ 521#if MACOSX_COREAUDIO 522 /* Fire a callback if the device stops being "alive" (disconnected, etc). */ 523 AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); 524#endif 525 526#if !MACOSX_COREAUDIO 527 update_audio_session(this, SDL_FALSE); 528#endif 529 530 if (this->hidden->thread) { 531 SDL_AtomicSet(&this->hidden->shutdown, 1); 532 SDL_WaitThread(this->hidden->thread, NULL); 533 } 534 535 if (this->hidden->audioQueue) { 536 for (i = 0; i < SDL_arraysize(this->hidden->audioBuffer); i++) { 537 if (this->hidden->audioBuffer[i]) { 538 AudioQueueFreeBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i]); 539 } 540 } 541 AudioQueueDispose(this->hidden->audioQueue, 1); 542 } 543 544 if (this->hidden->ready_semaphore) { 545 SDL_DestroySemaphore(this->hidden->ready_semaphore); 546 } 547 548 SDL_free(this->hidden->thread_error); 549 SDL_free(this->hidden->buffer); 550 SDL_free(this->hidden); 551 552 if (iscapture) { 553 open_capture_devices--; 554 } else { 555 open_playback_devices--; 556 } 557} 558 559#if MACOSX_COREAUDIO 560static int 561prepare_device(_THIS, void *handle, int iscapture) 562{ 563 AudioDeviceID devid = (AudioDeviceID) ((size_t) handle); 564 OSStatus result = noErr; 565 UInt32 size = 0; 566 UInt32 alive = 0; 567 pid_t pid = 0; 568 569 AudioObjectPropertyAddress addr = { 570 0, 571 kAudioObjectPropertyScopeGlobal, 572 kAudioObjectPropertyElementMaster 573 }; 574 575 if (handle == NULL) { 576 size = sizeof (AudioDeviceID); 577 addr.mSelector = 578 ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice : 579 kAudioHardwarePropertyDefaultOutputDevice); 580 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 581 0, NULL, &size, &devid); 582 CHECK_RESULT("AudioHardwareGetProperty (default device)"); 583 } 584 585 addr.mSelector = kAudioDevicePropertyDeviceIsAlive; 586 addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : 587 kAudioDevicePropertyScopeOutput; 588 589 size = sizeof (alive); 590 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); 591 CHECK_RESULT 592 ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); 593 594 if (!alive) { 595 SDL_SetError("CoreAudio: requested device exists, but isn't alive."); 596 return 0; 597 } 598 599 addr.mSelector = kAudioDevicePropertyHogMode; 600 size = sizeof (pid); 601 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); 602 603 /* some devices don't support this property, so errors are fine here. */ 604 if ((result == noErr) && (pid != -1)) { 605 SDL_SetError("CoreAudio: requested device is being hogged."); 606 return 0; 607 } 608 609 this->hidden->deviceID = devid; 610 return 1; 611} 612#endif 613 614static int 615prepare_audioqueue(_THIS) 616{ 617 const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc; 618 const int iscapture = this->iscapture; 619 OSStatus result; 620 int i; 621 622 SDL_assert(CFRunLoopGetCurrent() != NULL); 623 624 if (iscapture) { 625 result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue); 626 CHECK_RESULT("AudioQueueNewInput"); 627 } else { 628 result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue); 629 CHECK_RESULT("AudioQueueNewOutput"); 630 } 631 632#if MACOSX_COREAUDIO 633{ 634 const AudioObjectPropertyAddress prop = { 635 kAudioDevicePropertyDeviceUID, 636 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 637 kAudioObjectPropertyElementMaster 638 }; 639 CFStringRef devuid; 640 UInt32 devuidsize = sizeof (devuid); 641 result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); 642 CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); 643 result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); 644 CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); 645 646 /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ 647 /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ 648 /* Fire a callback if the device stops being "alive" (disconnected, etc). */ 649 AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); 650} 651#endif 652 653 /* Calculate the final parameters for this audio specification */ 654 SDL_CalculateAudioSpec(&this->spec); 655 656 /* Allocate a sample buffer */ 657 this->hidden->bufferSize = this->spec.size; 658 this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize; 659 660 this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); 661 if (this->hidden->buffer == NULL) { 662 SDL_OutOfMemory(); 663 return 0; 664 } 665 666 for (i = 0; i < SDL_arraysize(this->hidden->audioBuffer); i++) { 667 result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]); 668 CHECK_RESULT("AudioQueueAllocateBuffer"); 669 SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); 670 this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity; 671 result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL); 672 CHECK_RESULT("AudioQueueEnqueueBuffer"); 673 } 674 675 result = AudioQueueStart(this->hidden->audioQueue, NULL); 676 CHECK_RESULT("AudioQueueStart"); 677 678 /* We're running! */ 679 return 1; 680} 681 682static int 683audioqueue_thread(void *arg) 684{ 685 SDL_AudioDevice *this = (SDL_AudioDevice *) arg; 686 const int rc = prepare_audioqueue(this); 687 if (!rc) { 688 this->hidden->thread_error = SDL_strdup(SDL_GetError()); 689 SDL_SemPost(this->hidden->ready_semaphore); 690 return 0; 691 } 692 693 /* init was successful, alert parent thread and start running... */ 694 SDL_SemPost(this->hidden->ready_semaphore); 695 while (!SDL_AtomicGet(&this->hidden->shutdown)) { 696 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); 697 } 698 699 if (this->iscapture) { /* just stop immediately for capture devices. */ 700 AudioQueueStop(this->hidden->audioQueue, 1); 701 } else { /* Drain off any pending playback. */ 702 AudioQueueStop(this->hidden->audioQueue, 0); 703 const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0; 704 CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); 705 } 706 707 return 0; 708} 709 710static int 711COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) 712{ 713 AudioStreamBasicDescription *strdesc; 714 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 715 int valid_datatype = 0; 716 717 /* Initialize all variables that we clean on shutdown */ 718 this->hidden = (struct SDL_PrivateAudioData *) 719 SDL_malloc((sizeof *this->hidden)); 720 if (this->hidden == NULL) { 721 return SDL_OutOfMemory(); 722 } 723 SDL_zerop(this->hidden); 724 725 strdesc = &this->hidden->strdesc; 726 727 if (iscapture) { 728 open_capture_devices++; 729 } else { 730 open_playback_devices++; 731 } 732 733#if !MACOSX_COREAUDIO 734 if (!update_audio_session(this, SDL_TRUE)) { 735 return -1; 736 } 737#endif 738 739 /* Setup a AudioStreamBasicDescription with the requested format */ 740 SDL_zerop(strdesc); 741 strdesc->mFormatID = kAudioFormatLinearPCM; 742 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; 743 strdesc->mChannelsPerFrame = this->spec.channels; 744 strdesc->mSampleRate = this->spec.freq; 745 strdesc->mFramesPerPacket = 1; 746 747 while ((!valid_datatype) && (test_format)) { 748 this->spec.format = test_format; 749 /* Just a list of valid SDL formats, so people don't pass junk here. */ 750 switch (test_format) { 751 case AUDIO_U8: 752 case AUDIO_S8: 753 case AUDIO_U16LSB: 754 case AUDIO_S16LSB: 755 case AUDIO_U16MSB: 756 case AUDIO_S16MSB: 757 case AUDIO_S32LSB: 758 case AUDIO_S32MSB: 759 case AUDIO_F32LSB: 760 case AUDIO_F32MSB: 761 valid_datatype = 1; 762 strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format); 763 if (SDL_AUDIO_ISBIGENDIAN(this->spec.format)) 764 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; 765 766 if (SDL_AUDIO_ISFLOAT(this->spec.format)) 767 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat; 768 else if (SDL_AUDIO_ISSIGNED(this->spec.format)) 769 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; 770 break; 771 } 772 } 773 774 if (!valid_datatype) { /* shouldn't happen, but just in case... */ 775 return SDL_SetError("Unsupported audio format"); 776 } 777 778 strdesc->mBytesPerFrame = strdesc->mBitsPerChannel * strdesc->mChannelsPerFrame / 8; 779 strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; 780 781#if MACOSX_COREAUDIO 782 if (!prepare_device(this, handle, iscapture)) { 783 return -1; 784 } 785#endif 786 787 /* This has to init in a new thread so it can get its own CFRunLoop. :/ */ 788 SDL_AtomicSet(&this->hidden->shutdown, 0); 789 this->hidden->ready_semaphore = SDL_CreateSemaphore(0); 790 if (!this->hidden->ready_semaphore) { 791 return -1; /* oh well. */ 792 } 793 794 this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, this); 795 if (!this->hidden->thread) { 796 return -1; 797 } 798 799 SDL_SemWait(this->hidden->ready_semaphore); 800 SDL_DestroySemaphore(this->hidden->ready_semaphore); 801 this->hidden->ready_semaphore = NULL; 802 803 if ((this->hidden->thread != NULL) && (this->hidden->thread_error != NULL)) { 804 SDL_SetError("%s", this->hidden->thread_error); 805 return -1; 806 } 807 808 return (this->hidden->thread != NULL) ? 0 : -1; 809} 810 811static void 812COREAUDIO_Deinitialize(void) 813{ 814#if MACOSX_COREAUDIO 815 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); 816 free_audio_device_list(&capture_devs); 817 free_audio_device_list(&output_devs); 818#endif 819} 820 821static int 822COREAUDIO_Init(SDL_AudioDriverImpl * impl) 823{ 824 /* Set the function pointers */ 825 impl->OpenDevice = COREAUDIO_OpenDevice; 826 impl->CloseDevice = COREAUDIO_CloseDevice; 827 impl->Deinitialize = COREAUDIO_Deinitialize; 828 829#if MACOSX_COREAUDIO 830 impl->DetectDevices = COREAUDIO_DetectDevices; 831 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); 832#else 833 impl->OnlyHasDefaultOutputDevice = 1; 834 impl->OnlyHasDefaultCaptureDevice = 1; 835#endif 836 837 impl->ProvidesOwnCallbackThread = 1; 838 impl->HasCaptureSupport = 1; 839 840 return 1; /* this audio target is available. */ 841} 842 843AudioBootStrap COREAUDIO_bootstrap = { 844 "coreaudio", "CoreAudio", COREAUDIO_Init, 0 845}; 846 847#endif /* SDL_AUDIO_DRIVER_COREAUDIO */ 848 849/* vi: set ts=4 sw=4 expandtab: */ 850