Atlas - SDL_coreaudio.m

Home / ext / SDL2 / src / audio / coreaudio Lines: 3 | Size: 28364 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 Sam Lantinga <[email protected]> 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_timer.h" 30#include "../SDL_audio_c.h" 31#include "../SDL_sysaudio.h" 32#include "SDL_coreaudio.h" 33#include "SDL_assert.h" 34#include "../../thread/SDL_systhread.h" 35 36#define DEBUG_COREAUDIO 0 37 38#define CHECK_RESULT(msg) \ 39 if (result != noErr) { \ 40 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ 41 return 0; \ 42 } 43 44#if MACOSX_COREAUDIO 45static const AudioObjectPropertyAddress devlist_address = { 46 kAudioHardwarePropertyDevices, 47 kAudioObjectPropertyScopeGlobal, 48 kAudioObjectPropertyElementMaster 49}; 50 51typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data); 52 53typedef struct AudioDeviceList 54{ 55 AudioDeviceID devid; 56 SDL_bool alive; 57 struct AudioDeviceList *next; 58} AudioDeviceList; 59 60static AudioDeviceList *output_devs = NULL; 61static AudioDeviceList *capture_devs = NULL; 62 63static SDL_bool 64add_to_internal_dev_list(const int iscapture, AudioDeviceID devId) 65{ 66 AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList)); 67 if (item == NULL) { 68 return SDL_FALSE; 69 } 70 item->devid = devId; 71 item->alive = SDL_TRUE; 72 item->next = iscapture ? capture_devs : output_devs; 73 if (iscapture) { 74 capture_devs = item; 75 } else { 76 output_devs = item; 77 } 78 79 return SDL_TRUE; 80} 81 82static void 83addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data) 84{ 85 if (add_to_internal_dev_list(iscapture, devId)) { 86 SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId)); 87 } 88} 89 90static void 91build_device_list(int iscapture, addDevFn addfn, void *addfndata) 92{ 93 OSStatus result = noErr; 94 UInt32 size = 0; 95 AudioDeviceID *devs = NULL; 96 UInt32 i = 0; 97 UInt32 max = 0; 98 99 result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, 100 &devlist_address, 0, NULL, &size); 101 if (result != kAudioHardwareNoError) 102 return; 103 104 devs = (AudioDeviceID *) alloca(size); 105 if (devs == NULL) 106 return; 107 108 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, 109 &devlist_address, 0, NULL, &size, devs); 110 if (result != kAudioHardwareNoError) 111 return; 112 113 max = size / sizeof (AudioDeviceID); 114 for (i = 0; i < max; i++) { 115 CFStringRef cfstr = NULL; 116 char *ptr = NULL; 117 AudioDeviceID dev = devs[i]; 118 AudioBufferList *buflist = NULL; 119 int usable = 0; 120 CFIndex len = 0; 121 const AudioObjectPropertyAddress addr = { 122 kAudioDevicePropertyStreamConfiguration, 123 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 124 kAudioObjectPropertyElementMaster 125 }; 126 127 const AudioObjectPropertyAddress nameaddr = { 128 kAudioObjectPropertyName, 129 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 130 kAudioObjectPropertyElementMaster 131 }; 132 133 result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); 134 if (result != noErr) 135 continue; 136 137 buflist = (AudioBufferList *) SDL_malloc(size); 138 if (buflist == NULL) 139 continue; 140 141 result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, 142 &size, buflist); 143 144 if (result == noErr) { 145 UInt32 j; 146 for (j = 0; j < buflist->mNumberBuffers; j++) { 147 if (buflist->mBuffers[j].mNumberChannels > 0) { 148 usable = 1; 149 break; 150 } 151 } 152 } 153 154 SDL_free(buflist); 155 156 if (!usable) 157 continue; 158 159 160 size = sizeof (CFStringRef); 161 result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr); 162 if (result != kAudioHardwareNoError) 163 continue; 164 165 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), 166 kCFStringEncodingUTF8); 167 168 ptr = (char *) SDL_malloc(len + 1); 169 usable = ((ptr != NULL) && 170 (CFStringGetCString 171 (cfstr, ptr, len + 1, kCFStringEncodingUTF8))); 172 173 CFRelease(cfstr); 174 175 if (usable) { 176 len = strlen(ptr); 177 /* Some devices have whitespace at the end...trim it. */ 178 while ((len > 0) && (ptr[len - 1] == ' ')) { 179 len--; 180 } 181 usable = (len > 0); 182 } 183 184 if (usable) { 185 ptr[len] = '\0'; 186 187#if DEBUG_COREAUDIO 188 printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", 189 ((iscapture) ? "capture" : "output"), 190 (int) i, ptr, (int) dev); 191#endif 192 addfn(ptr, iscapture, dev, addfndata); 193 } 194 SDL_free(ptr); /* addfn() would have copied the string. */ 195 } 196} 197 198static void 199free_audio_device_list(AudioDeviceList **list) 200{ 201 AudioDeviceList *item = *list; 202 while (item) { 203 AudioDeviceList *next = item->next; 204 SDL_free(item); 205 item = next; 206 } 207 *list = NULL; 208} 209 210static void 211COREAUDIO_DetectDevices(void) 212{ 213 build_device_list(SDL_TRUE, addToDevList, NULL); 214 build_device_list(SDL_FALSE, addToDevList, NULL); 215} 216 217static void 218build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data) 219{ 220 AudioDeviceList **list = (AudioDeviceList **) data; 221 AudioDeviceList *item; 222 for (item = *list; item != NULL; item = item->next) { 223 if (item->devid == devId) { 224 item->alive = SDL_TRUE; 225 return; 226 } 227 } 228 229 add_to_internal_dev_list(iscapture, devId); /* new device, add it. */ 230 SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId)); 231} 232 233static void 234reprocess_device_list(const int iscapture, AudioDeviceList **list) 235{ 236 AudioDeviceList *item; 237 AudioDeviceList *prev = NULL; 238 for (item = *list; item != NULL; item = item->next) { 239 item->alive = SDL_FALSE; 240 } 241 242 build_device_list(iscapture, build_device_change_list, list); 243 244 /* free items in the list that aren't still alive. */ 245 item = *list; 246 while (item != NULL) { 247 AudioDeviceList *next = item->next; 248 if (item->alive) { 249 prev = item; 250 } else { 251 SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid)); 252 if (prev) { 253 prev->next = item->next; 254 } else { 255 *list = item->next; 256 } 257 SDL_free(item); 258 } 259 item = next; 260 } 261} 262 263/* this is called when the system's list of available audio devices changes. */ 264static OSStatus 265device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) 266{ 267 reprocess_device_list(SDL_TRUE, &capture_devs); 268 reprocess_device_list(SDL_FALSE, &output_devs); 269 return 0; 270} 271#endif 272 273 274static int open_playback_devices = 0; 275static int open_capture_devices = 0; 276 277#if !MACOSX_COREAUDIO 278 279static void interruption_begin(_THIS) 280{ 281 if (this != NULL && this->hidden->audioQueue != NULL) { 282 this->hidden->interrupted = SDL_TRUE; 283 AudioQueuePause(this->hidden->audioQueue); 284 } 285} 286 287static void interruption_end(_THIS) 288{ 289 if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL 290 && this->hidden->interrupted 291 && AudioQueueStart(this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { 292 this->hidden->interrupted = SDL_FALSE; 293 } 294} 295 296@interface SDLInterruptionListener : NSObject 297 298@property (nonatomic, assign) SDL_AudioDevice *device; 299 300@end 301 302@implementation SDLInterruptionListener 303 304- (void)audioSessionInterruption:(NSNotification *)note 305{ 306 @synchronized (self) { 307 NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; 308 if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { 309 interruption_begin(self.device); 310 } else { 311 interruption_end(self.device); 312 } 313 } 314} 315 316- (void)applicationBecameActive:(NSNotification *)note 317{ 318 @synchronized (self) { 319 interruption_end(self.device); 320 } 321} 322 323@end 324 325static BOOL update_audio_session(_THIS, SDL_bool open) 326{ 327 @autoreleasepool { 328 AVAudioSession *session = [AVAudioSession sharedInstance]; 329 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 330 /* Set category to ambient by default so that other music continues playing. */ 331 NSString *category = AVAudioSessionCategoryAmbient; 332 NSError *err = nil; 333 334 if (open_playback_devices && open_capture_devices) { 335 category = AVAudioSessionCategoryPlayAndRecord; 336 } else if (open_capture_devices) { 337 category = AVAudioSessionCategoryRecord; 338 } else { 339 const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); 340 if (hint) { 341 if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { 342 category = AVAudioSessionCategoryAmbient; 343 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { 344 category = AVAudioSessionCategorySoloAmbient; 345 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || 346 SDL_strcasecmp(hint, "playback") == 0) { 347 category = AVAudioSessionCategoryPlayback; 348 } 349 } 350 } 351 352 if (![session setCategory:category error:&err]) { 353 NSString *desc = err.description; 354 SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); 355 return NO; 356 } 357 358 if (open && (open_playback_devices + open_capture_devices) == 1) { 359 if (![session setActive:YES error:&err]) { 360 NSString *desc = err.description; 361 SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); 362 return NO; 363 } 364 } else if (!open_playback_devices && !open_capture_devices) { 365 [session setActive:NO error:nil]; 366 } 367 368 if (open) { 369 SDLInterruptionListener *listener = [SDLInterruptionListener new]; 370 listener.device = this; 371 372 [center addObserver:listener 373 selector:@selector(audioSessionInterruption:) 374 name:AVAudioSessionInterruptionNotification 375 object:session]; 376 377 /* An interruption end notification is not guaranteed to be sent if 378 we were previously interrupted... resuming if needed when the app 379 becomes active seems to be the way to go. */ 380 [center addObserver:listener 381 selector:@selector(applicationBecameActive:) 382 name:UIApplicationDidBecomeActiveNotification 383 object:session]; 384 385 [center addObserver:listener 386 selector:@selector(applicationBecameActive:) 387 name:UIApplicationWillEnterForegroundNotification 388 object:session]; 389 390 this->hidden->interruption_listener = CFBridgingRetain(listener); 391 } else { 392 if (this->hidden->interruption_listener != NULL) { 393 SDLInterruptionListener *listener = nil; 394 listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener); 395 [center removeObserver:listener]; 396 @synchronized (listener) { 397 listener.device = NULL; 398 } 399 } 400 } 401 } 402 403 return YES; 404} 405#endif 406 407 408/* The AudioQueue callback */ 409static void 410outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) 411{ 412 SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; 413 SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize); 414 SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize); 415 SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize); /* zero out in case we have to fill again without new data. */ 416 inBuffer->mAudioDataByteSize = this->hidden->bufferSize; 417 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); 418 this->hidden->refill = SDL_TRUE; 419} 420 421static Uint8 * 422COREAUDIO_GetDeviceBuf(_THIS) 423{ 424 return this->hidden->buffer; 425} 426 427static void 428COREAUDIO_WaitDevice(_THIS) 429{ 430 while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) { 431 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); 432 } 433 this->hidden->refill = SDL_FALSE; 434} 435 436static void 437inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, 438 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, 439 const AudioStreamPacketDescription *inPacketDescs ) 440{ 441 SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; 442 if (SDL_AtomicGet(&this->enabled)) { 443 SDL_AudioStream *stream = this->hidden->capturestream; 444 if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) { 445 /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */ 446 } 447 AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL); 448 this->hidden->refill = SDL_TRUE; 449 } 450} 451 452static int 453COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen) 454{ 455 SDL_AudioStream *stream = this->hidden->capturestream; 456 while (SDL_AtomicGet(&this->enabled)) { 457 const int avail = SDL_AudioStreamAvailable(stream); 458 if (avail > 0) { 459 const int cpy = SDL_min(buflen, avail); 460 SDL_AudioStreamGet(stream, buffer, cpy); 461 return cpy; 462 } 463 464 /* wait for more data, try again. */ 465 while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) { 466 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); 467 } 468 this->hidden->refill = SDL_FALSE; 469 } 470 471 return 0; /* not enabled, giving up. */ 472} 473 474static void 475COREAUDIO_FlushCapture(_THIS) 476{ 477 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) { 478 /* spin. */ 479 } 480 this->hidden->refill = SDL_FALSE; 481 SDL_AudioStreamClear(this->hidden->capturestream); 482} 483 484 485#if MACOSX_COREAUDIO 486static const AudioObjectPropertyAddress alive_address = 487{ 488 kAudioDevicePropertyDeviceIsAlive, 489 kAudioObjectPropertyScopeGlobal, 490 kAudioObjectPropertyElementMaster 491}; 492 493static OSStatus 494device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) 495{ 496 SDL_AudioDevice *this = (SDL_AudioDevice *) data; 497 SDL_bool dead = SDL_FALSE; 498 UInt32 isAlive = 1; 499 UInt32 size = sizeof (isAlive); 500 OSStatus error; 501 502 if (!SDL_AtomicGet(&this->enabled)) { 503 return 0; /* already known to be dead. */ 504 } 505 506 error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address, 507 0, NULL, &size, &isAlive); 508 509 if (error == kAudioHardwareBadDeviceError) { 510 dead = SDL_TRUE; /* device was unplugged. */ 511 } else if ((error == kAudioHardwareNoError) && (!isAlive)) { 512 dead = SDL_TRUE; /* device died in some other way. */ 513 } 514 515 if (dead) { 516 SDL_OpenedAudioDeviceDisconnected(this); 517 } 518 519 return 0; 520} 521#endif 522 523static void 524COREAUDIO_CloseDevice(_THIS) 525{ 526 const SDL_bool iscapture = this->iscapture; 527 528/* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ 529/* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ 530#if MACOSX_COREAUDIO 531 /* Fire a callback if the device stops being "alive" (disconnected, etc). */ 532 AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); 533#endif 534 535#if !MACOSX_COREAUDIO 536 update_audio_session(this, SDL_FALSE); 537#endif 538 539 if (this->hidden->audioQueue) { 540 AudioQueueDispose(this->hidden->audioQueue, 1); 541 } 542 543 if (this->hidden->capturestream) { 544 SDL_FreeAudioStream(this->hidden->capturestream); 545 } 546 547 /* AudioQueueDispose() frees the actual buffer objects. */ 548 SDL_free(this->hidden->audioBuffer); 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 614 615/* this all happens in the audio thread, since it needs a separate runloop. */ 616static int 617prepare_audioqueue(_THIS) 618{ 619 const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc; 620 const int iscapture = this->iscapture; 621 OSStatus result; 622 int i; 623 624 SDL_assert(CFRunLoopGetCurrent() != NULL); 625 626 if (iscapture) { 627 result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue); 628 CHECK_RESULT("AudioQueueNewInput"); 629 } else { 630 result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue); 631 CHECK_RESULT("AudioQueueNewOutput"); 632 } 633 634#if MACOSX_COREAUDIO 635{ 636 const AudioObjectPropertyAddress prop = { 637 kAudioDevicePropertyDeviceUID, 638 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 639 kAudioObjectPropertyElementMaster 640 }; 641 CFStringRef devuid; 642 UInt32 devuidsize = sizeof (devuid); 643 result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); 644 CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); 645 result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); 646 CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); 647 648 /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ 649 /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ 650 /* Fire a callback if the device stops being "alive" (disconnected, etc). */ 651 AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); 652} 653#endif 654 655 /* Make sure we can feed the device a minimum amount of time */ 656 double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; 657#if defined(__IPHONEOS__) 658 if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { 659 /* Older iOS hardware, use 40 ms as a minimum time */ 660 MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; 661 } 662#endif 663 const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0; 664 int numAudioBuffers = 2; 665 if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */ 666 numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); 667 } 668 669 this->hidden->numAudioBuffers = numAudioBuffers; 670 this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers); 671 if (this->hidden->audioBuffer == NULL) { 672 SDL_OutOfMemory(); 673 return 0; 674 } 675 676#if DEBUG_COREAUDIO 677 printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); 678#endif 679 680 for (i = 0; i < numAudioBuffers; i++) { 681 result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]); 682 CHECK_RESULT("AudioQueueAllocateBuffer"); 683 SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); 684 this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity; 685 result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL); 686 CHECK_RESULT("AudioQueueEnqueueBuffer"); 687 } 688 689 result = AudioQueueStart(this->hidden->audioQueue, NULL); 690 CHECK_RESULT("AudioQueueStart"); 691 692 /* We're running! */ 693 return 1; 694} 695 696static void 697COREAUDIO_ThreadInit(_THIS) 698{ 699 const int rc = prepare_audioqueue(this); 700 if (!rc) { 701 /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */ 702 SDL_OpenedAudioDeviceDisconnected(this); /* oh well. */ 703 } 704} 705 706static void 707COREAUDIO_PrepareToClose(_THIS) 708{ 709 /* run long enough to queue some silence, so we know our actual audio 710 has been played */ 711 CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0); 712 AudioQueueStop(this->hidden->audioQueue, 1); 713} 714 715static int 716COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture) 717{ 718 AudioStreamBasicDescription *strdesc; 719 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 720 int valid_datatype = 0; 721 722 /* Initialize all variables that we clean on shutdown */ 723 this->hidden = (struct SDL_PrivateAudioData *) 724 SDL_malloc((sizeof *this->hidden)); 725 if (this->hidden == NULL) { 726 return SDL_OutOfMemory(); 727 } 728 SDL_zerop(this->hidden); 729 730 strdesc = &this->hidden->strdesc; 731 732 if (iscapture) { 733 open_capture_devices++; 734 } else { 735 open_playback_devices++; 736 } 737 738#if !MACOSX_COREAUDIO 739 if (!update_audio_session(this, SDL_TRUE)) { 740 return -1; 741 } 742 743 /* Stop CoreAudio from doing expensive audio rate conversion */ 744 @autoreleasepool { 745 AVAudioSession* session = [AVAudioSession sharedInstance]; 746 [session setPreferredSampleRate:this->spec.freq error:nil]; 747 this->spec.freq = (int)session.sampleRate; 748 } 749#endif 750 751 /* Setup a AudioStreamBasicDescription with the requested format */ 752 SDL_zerop(strdesc); 753 strdesc->mFormatID = kAudioFormatLinearPCM; 754 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; 755 strdesc->mChannelsPerFrame = this->spec.channels; 756 strdesc->mSampleRate = this->spec.freq; 757 strdesc->mFramesPerPacket = 1; 758 759 while ((!valid_datatype) && (test_format)) { 760 this->spec.format = test_format; 761 /* Just a list of valid SDL formats, so people don't pass junk here. */ 762 switch (test_format) { 763 case AUDIO_U8: 764 case AUDIO_S8: 765 case AUDIO_U16LSB: 766 case AUDIO_S16LSB: 767 case AUDIO_U16MSB: 768 case AUDIO_S16MSB: 769 case AUDIO_S32LSB: 770 case AUDIO_S32MSB: 771 case AUDIO_F32LSB: 772 case AUDIO_F32MSB: 773 valid_datatype = 1; 774 strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format); 775 if (SDL_AUDIO_ISBIGENDIAN(this->spec.format)) 776 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; 777 778 if (SDL_AUDIO_ISFLOAT(this->spec.format)) 779 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat; 780 else if (SDL_AUDIO_ISSIGNED(this->spec.format)) 781 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; 782 break; 783 } 784 } 785 786 if (!valid_datatype) { /* shouldn't happen, but just in case... */ 787 return SDL_SetError("Unsupported audio format"); 788 } 789 790 strdesc->mBytesPerFrame = strdesc->mBitsPerChannel * strdesc->mChannelsPerFrame / 8; 791 strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; 792 793#if MACOSX_COREAUDIO 794 if (!prepare_device(this, handle, iscapture)) { 795 return -1; 796 } 797#endif 798 799 /* Calculate the final parameters for this audio specification */ 800 SDL_CalculateAudioSpec(&this->spec); 801 802 if (iscapture) { 803 this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq); 804 if (!this->hidden->capturestream) { 805 return -1; /* already set SDL_Error */ 806 } 807 } else { 808 this->hidden->bufferSize = this->spec.size; 809 this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); 810 if (this->hidden->buffer == NULL) { 811 return SDL_OutOfMemory(); 812 } 813 } 814 815 return 0; 816} 817 818static void 819COREAUDIO_Deinitialize(void) 820{ 821#if MACOSX_COREAUDIO 822 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); 823 free_audio_device_list(&capture_devs); 824 free_audio_device_list(&output_devs); 825#endif 826} 827 828static int 829COREAUDIO_Init(SDL_AudioDriverImpl * impl) 830{ 831 /* Set the function pointers */ 832 impl->OpenDevice = COREAUDIO_OpenDevice; 833 impl->CloseDevice = COREAUDIO_CloseDevice; 834 impl->Deinitialize = COREAUDIO_Deinitialize; 835 impl->ThreadInit = COREAUDIO_ThreadInit; 836 impl->WaitDevice = COREAUDIO_WaitDevice; 837 impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; 838 impl->PrepareToClose = COREAUDIO_PrepareToClose; 839 impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice; 840 impl->FlushCapture = COREAUDIO_FlushCapture; 841 842#if MACOSX_COREAUDIO 843 impl->DetectDevices = COREAUDIO_DetectDevices; 844 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); 845#else 846 impl->OnlyHasDefaultOutputDevice = 1; 847 impl->OnlyHasDefaultCaptureDevice = 1; 848#endif 849 850 impl->HasCaptureSupport = 1; 851 852 return 1; /* this audio target is available. */ 853} 854 855AudioBootStrap COREAUDIO_bootstrap = { 856 "coreaudio", "CoreAudio", COREAUDIO_Init, 0 857}; 858 859#endif /* SDL_AUDIO_DRIVER_COREAUDIO */ 860 861/* vi: set ts=4 sw=4 expandtab: */ 862
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.