Atlas - SDL_coreaudio.m

Home / ext / SDL / src / audio / coreaudio Lines: 1 | Size: 41015 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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#ifdef SDL_AUDIO_DRIVER_COREAUDIO 24 25#include "../SDL_sysaudio.h" 26#include "SDL_coreaudio.h" 27#include "../../thread/SDL_systhread.h" 28 29#define DEBUG_COREAUDIO 0 30 31#if DEBUG_COREAUDIO 32 #define CHECK_RESULT(msg) \ 33 if (result != noErr) { \ 34 SDL_Log("COREAUDIO: Got error %d from '%s'!", (int)result, msg); \ 35 return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ 36 } 37#else 38 #define CHECK_RESULT(msg) \ 39 if (result != noErr) { \ 40 return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ 41 } 42#endif 43 44#ifdef MACOSX_COREAUDIO 45// Apparently AudioDeviceID values might not be unique, so we wrap it in an SDL_malloc()'d pointer 46// to make it so. Use FindCoreAudioDeviceByHandle to deal with this redirection, if you need to 47// map from an AudioDeviceID to a SDL handle. 48typedef struct SDLCoreAudioHandle 49{ 50 AudioDeviceID devid; 51 bool recording; 52} SDLCoreAudioHandle; 53 54static bool TestCoreAudioDeviceHandleCallback(SDL_AudioDevice *device, void *handle) 55{ 56 const SDLCoreAudioHandle *a = (const SDLCoreAudioHandle *) device->handle; 57 const SDLCoreAudioHandle *b = (const SDLCoreAudioHandle *) handle; 58 return (a->devid == b->devid) && (!!a->recording == !!b->recording); 59} 60 61static SDL_AudioDevice *FindCoreAudioDeviceByHandle(const AudioDeviceID devid, const bool recording) 62{ 63 SDLCoreAudioHandle handle = { devid, recording }; 64 return SDL_FindPhysicalAudioDeviceByCallback(TestCoreAudioDeviceHandleCallback, &handle); 65} 66 67static const AudioObjectPropertyAddress devlist_address = { 68 kAudioHardwarePropertyDevices, 69 kAudioObjectPropertyScopeGlobal, 70 kAudioObjectPropertyElementMain 71}; 72 73static const AudioObjectPropertyAddress default_playback_device_address = { 74 kAudioHardwarePropertyDefaultOutputDevice, 75 kAudioObjectPropertyScopeGlobal, 76 kAudioObjectPropertyElementMain 77}; 78 79static const AudioObjectPropertyAddress default_recording_device_address = { 80 kAudioHardwarePropertyDefaultInputDevice, 81 kAudioObjectPropertyScopeGlobal, 82 kAudioObjectPropertyElementMain 83}; 84 85static const AudioObjectPropertyAddress alive_address = { 86 kAudioDevicePropertyDeviceIsAlive, 87 kAudioObjectPropertyScopeGlobal, 88 kAudioObjectPropertyElementMain 89}; 90 91 92static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) 93{ 94 SDL_AudioDevice *device = (SDL_AudioDevice *)data; 95 SDL_assert(((const SDLCoreAudioHandle *) device->handle)->devid == devid); 96 97 UInt32 alive = 1; 98 UInt32 size = sizeof(alive); 99 const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive); 100 101 bool dead = false; 102 if (error == kAudioHardwareBadDeviceError) { 103 dead = true; // device was unplugged. 104 } else if ((error == kAudioHardwareNoError) && (!alive)) { 105 dead = true; // device died in some other way. 106 } 107 108 if (dead) { 109 #if DEBUG_COREAUDIO 110 SDL_Log("COREAUDIO: device '%s' is lost!", device->name); 111 #endif 112 SDL_AudioDeviceDisconnected(device); 113 } 114 115 return noErr; 116} 117 118static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device) 119{ 120 SDLCoreAudioHandle *handle = (SDLCoreAudioHandle *) device->handle; 121 AudioObjectRemovePropertyListener(handle->devid, &alive_address, DeviceAliveNotification, device); 122 SDL_free(handle); 123} 124 125// This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes. 126static void RefreshPhysicalDevices(void) 127{ 128 UInt32 size = 0; 129 AudioDeviceID *devs = NULL; 130 bool isstack; 131 132 if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size) != kAudioHardwareNoError) { 133 return; 134 } else if ((devs = (AudioDeviceID *) SDL_small_alloc(Uint8, size, &isstack)) == NULL) { 135 return; 136 } else if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size, devs) != kAudioHardwareNoError) { 137 SDL_small_free(devs, isstack); 138 return; 139 } 140 141 const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID)); 142 for (UInt32 i = 0; i < total_devices; i++) { 143 if (FindCoreAudioDeviceByHandle(devs[i], true) || FindCoreAudioDeviceByHandle(devs[i], false)) { 144 devs[i] = 0; // The system and SDL both agree it's already here, don't check it again. 145 } 146 } 147 148 // any non-zero items remaining in `devs` are new devices to be added. 149 for (int recording = 0; recording < 2; recording++) { 150 const AudioObjectPropertyAddress addr = { 151 kAudioDevicePropertyStreamConfiguration, 152 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 153 kAudioObjectPropertyElementMain 154 }; 155 const AudioObjectPropertyAddress nameaddr = { 156 kAudioObjectPropertyName, 157 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 158 kAudioObjectPropertyElementMain 159 }; 160 const AudioObjectPropertyAddress freqaddr = { 161 kAudioDevicePropertyNominalSampleRate, 162 recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 163 kAudioObjectPropertyElementMain 164 }; 165 166 for (UInt32 i = 0; i < total_devices; i++) { 167 const AudioDeviceID dev = devs[i]; 168 if (!dev) { 169 continue; // already added. 170 } 171 172 AudioBufferList *buflist = NULL; 173 double sampleRate = 0; 174 175 if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) { 176 continue; 177 } else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) { 178 continue; 179 } 180 181 OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist); 182 183 SDL_AudioSpec spec; 184 SDL_zero(spec); 185 if (result == noErr) { 186 for (Uint32 j = 0; j < buflist->mNumberBuffers; j++) { 187 spec.channels += buflist->mBuffers[j].mNumberChannels; 188 } 189 } 190 191 SDL_free(buflist); 192 193 if (spec.channels == 0) { 194 continue; 195 } 196 197 size = sizeof(sampleRate); 198 if (AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate) == noErr) { 199 spec.freq = (int)sampleRate; 200 } 201 202 CFStringRef cfstr = NULL; 203 size = sizeof(CFStringRef); 204 if (AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr) != kAudioHardwareNoError) { 205 continue; 206 } 207 208 CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8); 209 char *name = (char *)SDL_malloc(len + 1); 210 bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))); 211 212 CFRelease(cfstr); 213 214 if (usable) { 215 // Some devices have whitespace at the end...trim it. 216 len = (CFIndex) SDL_strlen(name); 217 while ((len > 0) && (name[len - 1] == ' ')) { 218 len--; 219 } 220 usable = (len > 0); 221 } 222 223 if (usable) { 224 name[len] = '\0'; 225 226 #if DEBUG_COREAUDIO 227 SDL_Log("COREAUDIO: Found %s device #%d: '%s' (devid %d)", ((recording) ? "recording" : "playback"), (int)i, name, (int)dev); 228 #endif 229 SDLCoreAudioHandle *newhandle = (SDLCoreAudioHandle *) SDL_calloc(1, sizeof (*newhandle)); 230 if (newhandle) { 231 newhandle->devid = dev; 232 newhandle->recording = recording ? true : false; 233 SDL_AudioDevice *device = SDL_AddAudioDevice(newhandle->recording, name, &spec, newhandle); 234 if (device) { 235 AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device); 236 } else { 237 SDL_free(newhandle); 238 } 239 } 240 } 241 SDL_free(name); // SDL_AddAudioDevice() would have copied the string. 242 } 243 } 244 245 SDL_small_free(devs, isstack); 246} 247 248// this is called when the system's list of available audio devices changes. 249static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) 250{ 251 RefreshPhysicalDevices(); 252 return noErr; 253} 254 255static OSStatus DefaultAudioDeviceChangedNotification(const bool recording, AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) 256{ 257 AudioDeviceID devid; 258 UInt32 size = sizeof(devid); 259 if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { 260 SDL_DefaultAudioDeviceChanged(FindCoreAudioDeviceByHandle(devid, recording)); 261 } 262 return noErr; 263} 264 265static OSStatus DefaultPlaybackDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) 266{ 267 #if DEBUG_COREAUDIO 268 SDL_Log("COREAUDIO: default playback device changed!"); 269 #endif 270 SDL_assert(inNumberAddresses == 1); 271 return DefaultAudioDeviceChangedNotification(false, inObjectID, inAddresses); 272} 273 274static OSStatus DefaultRecordingDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) 275{ 276 #if DEBUG_COREAUDIO 277 SDL_Log("COREAUDIO: default recording device changed!"); 278 #endif 279 SDL_assert(inNumberAddresses == 1); 280 return DefaultAudioDeviceChangedNotification(true, inObjectID, inAddresses); 281} 282 283static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) 284{ 285 RefreshPhysicalDevices(); 286 287 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); 288 289 // Get the Device ID 290 UInt32 size; 291 AudioDeviceID devid; 292 293 size = sizeof(AudioDeviceID); 294 if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_playback_device_address, 0, NULL, &size, &devid) == noErr) { 295 SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, false); 296 if (device) { 297 *default_playback = device; 298 } 299 } 300 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL); 301 302 size = sizeof(AudioDeviceID); 303 if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_recording_device_address, 0, NULL, &size, &devid) == noErr) { 304 SDL_AudioDevice *device = FindCoreAudioDeviceByHandle(devid, true); 305 if (device) { 306 *default_recording = device; 307 } 308 } 309 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL); 310} 311 312#else // iOS-specific section follows. 313 314static bool session_active = false; 315 316static bool PauseOneAudioDevice(SDL_AudioDevice *device, void *userdata) 317{ 318 if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) { 319 AudioQueuePause(device->hidden->audioQueue); 320 } 321 return false; // keep enumerating devices until we've paused them all. 322} 323 324static void PauseAudioDevices(void) 325{ 326 (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL); 327} 328 329static bool ResumeOneAudioDevice(SDL_AudioDevice *device, void *userdata) 330{ 331 if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) { 332 AudioQueueStart(device->hidden->audioQueue, NULL); 333 } 334 return false; // keep enumerating devices until we've resumed them all. 335} 336 337static void ResumeAudioDevices(void) 338{ 339 (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL); 340} 341 342static void InterruptionBegin(SDL_AudioDevice *device) 343{ 344 if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL) { 345 device->hidden->interrupted = true; 346 AudioQueuePause(device->hidden->audioQueue); 347 } 348} 349 350static void InterruptionEnd(SDL_AudioDevice *device) 351{ 352 if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { 353 device->hidden->interrupted = false; 354 } 355} 356 357@interface SDLInterruptionListener : NSObject 358 359@property(nonatomic, assign) SDL_AudioDevice *device; 360 361@end 362 363@implementation SDLInterruptionListener 364 365- (void)audioSessionInterruption:(NSNotification *)note 366{ 367 @synchronized(self) { 368 NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; 369 if (type && (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan)) { 370 InterruptionBegin(self.device); 371 } else { 372 InterruptionEnd(self.device); 373 } 374 } 375} 376 377- (void)applicationBecameActive:(NSNotification *)note 378{ 379 @synchronized(self) { 380 InterruptionEnd(self.device); 381 } 382} 383 384@end 385 386typedef struct 387{ 388 int playback; 389 int recording; 390} CountOpenAudioDevicesData; 391 392static bool CountOpenAudioDevices(SDL_AudioDevice *device, void *userdata) 393{ 394 CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata; 395 if (device->hidden != NULL) { // assume it's open if hidden != NULL 396 if (device->recording) { 397 data->recording++; 398 } else { 399 data->playback++; 400 } 401 } 402 return false; // keep enumerating until all devices have been checked. 403} 404 405static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_playandrecord) 406{ 407 @autoreleasepool { 408 AVAudioSession *session = [AVAudioSession sharedInstance]; 409 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 410 411 NSString *category = AVAudioSessionCategoryPlayback; 412 NSString *mode = AVAudioSessionModeDefault; 413 NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; 414 NSError *err = nil; 415 const char *hint; 416 417 CountOpenAudioDevicesData data; 418 SDL_zero(data); 419 (void) SDL_FindPhysicalAudioDeviceByCallback(CountOpenAudioDevices, &data); 420 421 hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); 422 if (hint) { 423 if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0 || 424 SDL_strcasecmp(hint, "ambient") == 0) { 425 category = AVAudioSessionCategoryAmbient; 426 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { 427 category = AVAudioSessionCategorySoloAmbient; 428 options &= ~AVAudioSessionCategoryOptionMixWithOthers; 429 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || 430 SDL_strcasecmp(hint, "playback") == 0) { 431 category = AVAudioSessionCategoryPlayback; 432 options &= ~AVAudioSessionCategoryOptionMixWithOthers; 433 } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 || 434 SDL_strcasecmp(hint, "playandrecord") == 0) { 435 if (allow_playandrecord) { 436 category = AVAudioSessionCategoryPlayAndRecord; 437 } 438 } 439 } else if (data.playback && data.recording) { 440 if (allow_playandrecord) { 441 category = AVAudioSessionCategoryPlayAndRecord; 442 } else { 443 // We already failed play and record with AVAudioSessionErrorCodeResourceNotAvailable 444 return false; 445 } 446 } else if (data.recording) { 447 category = AVAudioSessionCategoryRecord; 448 } 449 450 #ifndef SDL_PLATFORM_TVOS 451 if (category == AVAudioSessionCategoryPlayAndRecord) { 452 options |= AVAudioSessionCategoryOptionDefaultToSpeaker; 453 } 454 #endif 455 if (category == AVAudioSessionCategoryRecord || 456 category == AVAudioSessionCategoryPlayAndRecord) { 457 /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for 458 Apple TV but is still needed in order to output to Bluetooth devices. 459 */ 460 options |= 0x4; // AVAudioSessionCategoryOptionAllowBluetooth; 461 } 462 if (category == AVAudioSessionCategoryPlayAndRecord) { 463 options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP | 464 AVAudioSessionCategoryOptionAllowAirPlay; 465 } 466 if (category == AVAudioSessionCategoryPlayback || 467 category == AVAudioSessionCategoryPlayAndRecord) { 468 options |= AVAudioSessionCategoryOptionDuckOthers; 469 } 470 471 if (![session.category isEqualToString:category] || session.categoryOptions != options) { 472 // Stop the current session so we don't interrupt other application audio 473 PauseAudioDevices(); 474 [session setActive:NO error:nil]; 475 session_active = false; 476 477 if (![session setCategory:category mode:mode options:options error:&err]) { 478 NSString *desc = err.description; 479 SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); 480 return false; 481 } 482 } 483 484 if ((data.playback || data.recording) && !session_active) { 485 if (![session setActive:YES error:&err]) { 486 if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && 487 category == AVAudioSessionCategoryPlayAndRecord) { 488 if (UpdateAudioSession(device, open, false)) { 489 return true; 490 } else { 491 return SDL_SetError("Could not activate Audio Session: Resource not available"); 492 } 493 } 494 495 NSString *desc = err.description; 496 return SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); 497 } 498 session_active = true; 499 ResumeAudioDevices(); 500 } else if (!data.playback && !data.recording && session_active) { 501 PauseAudioDevices(); 502 [session setActive:NO error:nil]; 503 session_active = false; 504 } 505 506 if (open) { 507 SDLInterruptionListener *listener = [SDLInterruptionListener new]; 508 listener.device = device; 509 510 [center addObserver:listener 511 selector:@selector(audioSessionInterruption:) 512 name:AVAudioSessionInterruptionNotification 513 object:session]; 514 515 /* An interruption end notification is not guaranteed to be sent if 516 we were previously interrupted... resuming if needed when the app 517 becomes active seems to be the way to go. */ 518 // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications. 519 [center addObserver:listener 520 selector:@selector(applicationBecameActive:) 521 name:UIApplicationDidBecomeActiveNotification 522 object:nil]; 523 524 [center addObserver:listener 525 selector:@selector(applicationBecameActive:) 526 name:UIApplicationWillEnterForegroundNotification 527 object:nil]; 528 529 device->hidden->interruption_listener = CFBridgingRetain(listener); 530 } else { 531 SDLInterruptionListener *listener = nil; 532 listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener); 533 [center removeObserver:listener]; 534 @synchronized(listener) { 535 listener.device = NULL; 536 } 537 } 538 } 539 540 return true; 541} 542#endif 543 544 545static bool COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) 546{ 547 AudioQueueBufferRef current_buffer = device->hidden->current_buffer; 548 SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback 549 SDL_assert(buffer == (Uint8 *) current_buffer->mAudioData); 550 current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity; 551 device->hidden->current_buffer = NULL; 552 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); 553 return true; 554} 555 556static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) 557{ 558 AudioQueueBufferRef current_buffer = device->hidden->current_buffer; 559 SDL_assert(current_buffer != NULL); // should have been called from PlaybackBufferReadyCallback 560 SDL_assert(current_buffer->mAudioData != NULL); 561 *buffer_size = (int) current_buffer->mAudioDataBytesCapacity; 562 return (Uint8 *) current_buffer->mAudioData; 563} 564 565static void PlaybackBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) 566{ 567 SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; 568 SDL_assert(inBuffer != NULL); // ...right? 569 SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending 570 device->hidden->current_buffer = inBuffer; 571 const bool okay = SDL_PlaybackAudioThreadIterate(device); 572 SDL_assert((device->hidden->current_buffer == NULL) || !okay); // PlayDevice should have enqueued and cleaned it out, unless we failed or shutdown. 573 574 // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer with silence. 575 if (device->hidden->current_buffer) { 576 AudioQueueBufferRef current_buffer = device->hidden->current_buffer; 577 device->hidden->current_buffer = NULL; 578 SDL_memset(current_buffer->mAudioData, device->silence_value, (size_t) current_buffer->mAudioDataBytesCapacity); 579 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); 580 } 581} 582 583static int COREAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen) 584{ 585 AudioQueueBufferRef current_buffer = device->hidden->current_buffer; 586 SDL_assert(current_buffer != NULL); // should have been called from RecordingBufferReadyCallback 587 SDL_assert(current_buffer->mAudioData != NULL); 588 SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); // `cpy` makes sure this won't overflow a buffer, but we _will_ drop samples if this assertion fails! 589 const int cpy = SDL_min(buflen, (int) current_buffer->mAudioDataByteSize); 590 SDL_memcpy(buffer, current_buffer->mAudioData, cpy); 591 device->hidden->current_buffer = NULL; 592 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); // requeue for capturing more data later. 593 return cpy; 594} 595 596static void COREAUDIO_FlushRecording(SDL_AudioDevice *device) 597{ 598 AudioQueueBufferRef current_buffer = device->hidden->current_buffer; 599 if (current_buffer != NULL) { // also gets called at shutdown, when no buffer is available. 600 // just requeue the current buffer without reading from it, so it can be refilled with new data later. 601 device->hidden->current_buffer = NULL; 602 AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); 603 } 604} 605 606static void RecordingBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, 607 const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, 608 const AudioStreamPacketDescription *inPacketDescs) 609{ 610 SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; 611 SDL_assert(inAQ == device->hidden->audioQueue); 612 SDL_assert(inBuffer != NULL); // ...right? 613 SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending 614 device->hidden->current_buffer = inBuffer; 615 SDL_RecordingAudioThreadIterate(device); 616 617 // buffer is unexpectedly here? We're probably dying, but try to requeue this buffer anyhow. 618 if (device->hidden->current_buffer != NULL) { 619 SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0); 620 COREAUDIO_FlushRecording(device); // just flush it manually, which will requeue it. 621 } 622} 623 624static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) 625{ 626 if (!device->hidden) { 627 return; 628 } 629 630 // dispose of the audio queue before waiting on the thread, or it might stall for a long time! 631 if (device->hidden->audioQueue) { 632 AudioQueueFlush(device->hidden->audioQueue); 633 AudioQueueStop(device->hidden->audioQueue, 0); 634 AudioQueueDispose(device->hidden->audioQueue, 0); 635 } 636 637 if (device->hidden->thread) { 638 SDL_assert(SDL_GetAtomicInt(&device->shutdown) != 0); // should have been set by SDL_audio.c 639 SDL_WaitThread(device->hidden->thread, NULL); 640 } 641 642 #ifndef MACOSX_COREAUDIO 643 UpdateAudioSession(device, false, true); 644 #endif 645 646 if (device->hidden->ready_semaphore) { 647 SDL_DestroySemaphore(device->hidden->ready_semaphore); 648 } 649 650 // AudioQueueDispose() frees the actual buffer objects. 651 SDL_free(device->hidden->audioBuffer); 652 SDL_free(device->hidden->thread_error); 653 SDL_free(device->hidden); 654} 655 656#ifdef MACOSX_COREAUDIO 657static bool PrepareDevice(SDL_AudioDevice *device) 658{ 659 SDL_assert(device->handle != NULL); // this meant "system default" in SDL2, but doesn't anymore 660 661 const SDLCoreAudioHandle *handle = (const SDLCoreAudioHandle *) device->handle; 662 const AudioDeviceID devid = handle->devid; 663 OSStatus result = noErr; 664 UInt32 size = 0; 665 666 AudioObjectPropertyAddress addr = { 667 0, 668 kAudioObjectPropertyScopeGlobal, 669 kAudioObjectPropertyElementMain 670 }; 671 672 UInt32 alive = 0; 673 size = sizeof(alive); 674 addr.mSelector = kAudioDevicePropertyDeviceIsAlive; 675 addr.mScope = device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; 676 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); 677 CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); 678 if (!alive) { 679 return SDL_SetError("CoreAudio: requested device exists, but isn't alive."); 680 } 681 682 // some devices don't support this property, so errors are fine here. 683 pid_t pid = 0; 684 size = sizeof(pid); 685 addr.mSelector = kAudioDevicePropertyHogMode; 686 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); 687 if ((result == noErr) && (pid != -1)) { 688 return SDL_SetError("CoreAudio: requested device is being hogged."); 689 } 690 691 device->hidden->deviceID = devid; 692 693 return true; 694} 695 696static bool AssignDeviceToAudioQueue(SDL_AudioDevice *device) 697{ 698 const AudioObjectPropertyAddress prop = { 699 kAudioDevicePropertyDeviceUID, 700 device->recording ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, 701 kAudioObjectPropertyElementMain 702 }; 703 704 OSStatus result; 705 CFStringRef devuid; 706 UInt32 devuidsize = sizeof(devuid); 707 result = AudioObjectGetPropertyData(device->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); 708 CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); 709 result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); 710 CFRelease(devuid); // Release devuid; we're done with it and AudioQueueSetProperty should have retained if it wants to keep it. 711 CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); 712 return true; 713} 714#endif 715 716static bool PrepareAudioQueue(SDL_AudioDevice *device) 717{ 718 const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; 719 const bool recording = device->recording; 720 OSStatus result; 721 722 SDL_assert(CFRunLoopGetCurrent() != NULL); 723 724 if (recording) { 725 result = AudioQueueNewInput(strdesc, RecordingBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); 726 CHECK_RESULT("AudioQueueNewInput"); 727 } else { 728 result = AudioQueueNewOutput(strdesc, PlaybackBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); 729 CHECK_RESULT("AudioQueueNewOutput"); 730 } 731 732 #ifdef MACOSX_COREAUDIO 733 if (!AssignDeviceToAudioQueue(device)) { 734 return false; 735 } 736 #endif 737 738 SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct. 739 740 // Set the channel layout for the audio queue 741 AudioChannelLayout layout; 742 SDL_zero(layout); 743 switch (device->spec.channels) { 744 case 1: 745 // a standard mono stream 746 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; 747 break; 748 case 2: 749 // a standard stereo stream (L R) - implied playback 750 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; 751 break; 752 case 3: 753 // L R LFE 754 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4; 755 break; 756 case 4: 757 // front left, front right, back left, back right 758 layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic; 759 break; 760 case 5: 761 // L R LFE Ls Rs 762 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_6; 763 break; 764 case 6: 765 // L R C LFE Ls Rs 766 layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_12; 767 break; 768 case 7: 769 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { 770 // L R C LFE Cs Ls Rs 771 layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_6_1; 772 } else { 773 // L R C LFE Ls Rs Cs 774 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A; 775 776 // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_6_1_A 777 static const int swizzle_map[7] = { 778 0, 1, 2, 3, 6, 4, 5 779 }; 780 device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map)); 781 if (!device->chmap) { 782 return false; 783 } 784 } 785 break; 786 case 8: 787 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { 788 // L R C LFE Rls Rrs Ls Rs 789 layout.mChannelLayoutTag = kAudioChannelLayoutTag_WAVE_7_1; 790 } else { 791 // L R C LFE Ls Rs Rls Rrs 792 layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_C; 793 794 // Convert from SDL channel layout to kAudioChannelLayoutTag_MPEG_7_1_C 795 static const int swizzle_map[8] = { 796 0, 1, 2, 3, 6, 7, 4, 5 797 }; 798 device->chmap = SDL_ChannelMapDup(swizzle_map, SDL_arraysize(swizzle_map)); 799 if (!device->chmap) { 800 return false; 801 } 802 } 803 break; 804 default: 805 return SDL_SetError("Unsupported audio channels"); 806 } 807 if (layout.mChannelLayoutTag != 0) { 808 result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout)); 809 CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)"); 810 } 811 812 // Make sure we can feed the device a minimum amount of time 813 double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; 814 #ifdef SDL_PLATFORM_IOS 815 if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { 816 // Older iOS hardware, use 40 ms as a minimum time 817 MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; 818 } 819 #endif 820 821 // we use THREE audio buffers by default, unlike most things that would 822 // choose two alternating buffers, because it helps with issues on 823 // Bluetooth headsets when recording and playing at the same time. 824 // See conversation in #8192 for details. 825 int numAudioBuffers = 3; 826 const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0; 827 if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { // use more buffers if we have a VERY small sample set. 828 numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); 829 } 830 831 device->hidden->numAudioBuffers = numAudioBuffers; 832 device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef)); 833 if (device->hidden->audioBuffer == NULL) { 834 return false; 835 } 836 837 #if DEBUG_COREAUDIO 838 SDL_Log("COREAUDIO: numAudioBuffers == %d", numAudioBuffers); 839 #endif 840 841 for (int i = 0; i < numAudioBuffers; i++) { 842 result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->buffer_size, &device->hidden->audioBuffer[i]); 843 CHECK_RESULT("AudioQueueAllocateBuffer"); 844 SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); 845 device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity; 846 // !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? 847 result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); 848 CHECK_RESULT("AudioQueueEnqueueBuffer"); 849 } 850 851 result = AudioQueueStart(device->hidden->audioQueue, NULL); 852 CHECK_RESULT("AudioQueueStart"); 853 854 return true; // We're running! 855} 856 857static int AudioQueueThreadEntry(void *arg) 858{ 859 SDL_AudioDevice *device = (SDL_AudioDevice *)arg; 860 861 if (device->recording) { 862 SDL_RecordingAudioThreadSetup(device); 863 } else { 864 SDL_PlaybackAudioThreadSetup(device); 865 } 866 867 if (!PrepareAudioQueue(device)) { 868 device->hidden->thread_error = SDL_strdup(SDL_GetError()); 869 SDL_SignalSemaphore(device->hidden->ready_semaphore); 870 return 0; 871 } 872 873 // init was successful, alert parent thread and start running... 874 SDL_SignalSemaphore(device->hidden->ready_semaphore); 875 876 // This would be WaitDevice/WaitRecordingDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate. 877 while (!SDL_GetAtomicInt(&device->shutdown)) { 878 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); 879 } 880 881 if (device->recording) { 882 SDL_RecordingAudioThreadShutdown(device); 883 } else { 884 // Drain off any pending playback. 885 const CFTimeInterval secs = (((CFTimeInterval)device->sample_frames) / ((CFTimeInterval)device->spec.freq)) * 2.0; 886 CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); 887 SDL_PlaybackAudioThreadShutdown(device); 888 } 889 890 return 0; 891} 892 893static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device) 894{ 895 // Initialize all variables that we clean on shutdown 896 device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); 897 if (device->hidden == NULL) { 898 return false; 899 } 900 901 #ifndef MACOSX_COREAUDIO 902 if (!UpdateAudioSession(device, true, true)) { 903 return false; 904 } 905 906 // Stop CoreAudio from doing expensive audio rate conversion 907 @autoreleasepool { 908 AVAudioSession *session = [AVAudioSession sharedInstance]; 909 [session setPreferredSampleRate:device->spec.freq error:nil]; 910 device->spec.freq = (int)session.sampleRate; 911 #ifdef SDL_PLATFORM_TVOS 912 if (device->recording) { 913 [session setPreferredInputNumberOfChannels:device->spec.channels error:nil]; 914 device->spec.channels = (int)session.preferredInputNumberOfChannels; 915 } else { 916 [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil]; 917 device->spec.channels = (int)session.preferredOutputNumberOfChannels; 918 } 919 #else 920 // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS 921 #endif // SDL_PLATFORM_TVOS 922 } 923 #endif 924 925 // Setup a AudioStreamBasicDescription with the requested format 926 AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; 927 strdesc->mFormatID = kAudioFormatLinearPCM; 928 strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; 929 strdesc->mChannelsPerFrame = device->spec.channels; 930 strdesc->mSampleRate = device->spec.freq; 931 strdesc->mFramesPerPacket = 1; 932 933 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); 934 SDL_AudioFormat test_format; 935 while ((test_format = *(closefmts++)) != 0) { 936 // CoreAudio handles most of SDL's formats natively. 937 switch (test_format) { 938 case SDL_AUDIO_U8: 939 case SDL_AUDIO_S8: 940 case SDL_AUDIO_S16LE: 941 case SDL_AUDIO_S16BE: 942 case SDL_AUDIO_S32LE: 943 case SDL_AUDIO_S32BE: 944 case SDL_AUDIO_F32LE: 945 case SDL_AUDIO_F32BE: 946 break; 947 948 default: 949 continue; 950 } 951 break; 952 } 953 954 if (!test_format) { // shouldn't happen, but just in case... 955 return SDL_SetError("%s: Unsupported audio format", "coreaudio"); 956 } 957 device->spec.format = test_format; 958 strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(test_format); 959 if (SDL_AUDIO_ISBIGENDIAN(test_format)) { 960 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; 961 } 962 963 if (SDL_AUDIO_ISFLOAT(test_format)) { 964 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat; 965 } else if (SDL_AUDIO_ISSIGNED(test_format)) { 966 strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; 967 } 968 969 strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8; 970 strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; 971 972#ifdef MACOSX_COREAUDIO 973 if (!PrepareDevice(device)) { 974 return false; 975 } 976#endif 977 978 // This has to init in a new thread so it can get its own CFRunLoop. :/ 979 device->hidden->ready_semaphore = SDL_CreateSemaphore(0); 980 if (!device->hidden->ready_semaphore) { 981 return false; // oh well. 982 } 983 984 char threadname[64]; 985 SDL_GetAudioThreadName(device, threadname, sizeof(threadname)); 986 device->hidden->thread = SDL_CreateThread(AudioQueueThreadEntry, threadname, device); 987 if (!device->hidden->thread) { 988 return false; 989 } 990 991 SDL_WaitSemaphore(device->hidden->ready_semaphore); 992 SDL_DestroySemaphore(device->hidden->ready_semaphore); 993 device->hidden->ready_semaphore = NULL; 994 995 if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) { 996 SDL_WaitThread(device->hidden->thread, NULL); 997 device->hidden->thread = NULL; 998 return SDL_SetError("%s", device->hidden->thread_error); 999 } 1000 1001 return (device->hidden->thread != NULL); 1002} 1003 1004static void COREAUDIO_DeinitializeStart(void) 1005{ 1006#ifdef MACOSX_COREAUDIO 1007 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); 1008 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_playback_device_address, DefaultPlaybackDeviceChangedNotification, NULL); 1009 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_recording_device_address, DefaultRecordingDeviceChangedNotification, NULL); 1010#endif 1011} 1012 1013static bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) 1014{ 1015 impl->OpenDevice = COREAUDIO_OpenDevice; 1016 impl->PlayDevice = COREAUDIO_PlayDevice; 1017 impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; 1018 impl->RecordDevice = COREAUDIO_RecordDevice; 1019 impl->FlushRecording = COREAUDIO_FlushRecording; 1020 impl->CloseDevice = COREAUDIO_CloseDevice; 1021 impl->DeinitializeStart = COREAUDIO_DeinitializeStart; 1022 1023#ifdef MACOSX_COREAUDIO 1024 impl->DetectDevices = COREAUDIO_DetectDevices; 1025 impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle; 1026#else 1027 impl->OnlyHasDefaultPlaybackDevice = true; 1028 impl->OnlyHasDefaultRecordingDevice = true; 1029#endif 1030 1031 impl->ProvidesOwnCallbackThread = true; 1032 impl->HasRecordingSupport = true; 1033 1034 return true; 1035} 1036 1037AudioBootStrap COREAUDIO_bootstrap = { 1038 "coreaudio", "CoreAudio", COREAUDIO_Init, false, false 1039}; 1040 1041#endif // SDL_AUDIO_DRIVER_COREAUDIO 1042
[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.