SDL_coreaudio.c (18309B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 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#include "SDL_audio.h" 23#include "../SDL_audio_c.h" 24#include "../SDL_sysaudio.h" 25#include "SDL_coreaudio.h" 26#include "SDL_assert.h" 27 28#define DEBUG_COREAUDIO 0 29 30static void COREAUDIO_CloseDevice(_THIS); 31 32#define CHECK_RESULT(msg) \ 33 if (result != noErr) { \ 34 COREAUDIO_CloseDevice(this); \ 35 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ 36 return 0; \ 37 } 38 39#if MACOSX_COREAUDIO 40typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data); 41 42static void 43addToDevList(const char *name, AudioDeviceID devId, void *data) 44{ 45 SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data; 46 addfn(name); 47} 48 49typedef struct 50{ 51 const char *findname; 52 AudioDeviceID devId; 53 int found; 54} FindDevIdData; 55 56static void 57findDevId(const char *name, AudioDeviceID devId, void *_data) 58{ 59 FindDevIdData *data = (FindDevIdData *) _data; 60 if (!data->found) { 61 if (SDL_strcmp(name, data->findname) == 0) { 62 data->found = 1; 63 data->devId = devId; 64 } 65 } 66} 67 68static void 69build_device_list(int iscapture, addDevFn addfn, void *addfndata) 70{ 71 OSStatus result = noErr; 72 UInt32 size = 0; 73 AudioDeviceID *devs = NULL; 74 UInt32 i = 0; 75 UInt32 max = 0; 76 77 AudioObjectPropertyAddress addr = { 78 kAudioHardwarePropertyDevices, 79 kAudioObjectPropertyScopeGlobal, 80 kAudioObjectPropertyElementMaster 81 }; 82 83 result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 84 0, NULL, &size); 85 if (result != kAudioHardwareNoError) 86 return; 87 88 devs = (AudioDeviceID *) alloca(size); 89 if (devs == NULL) 90 return; 91 92 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 93 0, NULL, &size, devs); 94 if (result != kAudioHardwareNoError) 95 return; 96 97 max = size / sizeof (AudioDeviceID); 98 for (i = 0; i < max; i++) { 99 CFStringRef cfstr = NULL; 100 char *ptr = NULL; 101 AudioDeviceID dev = devs[i]; 102 AudioBufferList *buflist = NULL; 103 int usable = 0; 104 CFIndex len = 0; 105 106 addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : 107 kAudioDevicePropertyScopeOutput; 108 addr.mSelector = kAudioDevicePropertyStreamConfiguration; 109 110 result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); 111 if (result != noErr) 112 continue; 113 114 buflist = (AudioBufferList *) SDL_malloc(size); 115 if (buflist == NULL) 116 continue; 117 118 result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, 119 &size, buflist); 120 121 if (result == noErr) { 122 UInt32 j; 123 for (j = 0; j < buflist->mNumberBuffers; j++) { 124 if (buflist->mBuffers[j].mNumberChannels > 0) { 125 usable = 1; 126 break; 127 } 128 } 129 } 130 131 SDL_free(buflist); 132 133 if (!usable) 134 continue; 135 136 addr.mSelector = kAudioObjectPropertyName; 137 size = sizeof (CFStringRef); 138 result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, &cfstr); 139 if (result != kAudioHardwareNoError) 140 continue; 141 142 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), 143 kCFStringEncodingUTF8); 144 145 ptr = (char *) SDL_malloc(len + 1); 146 usable = ((ptr != NULL) && 147 (CFStringGetCString 148 (cfstr, ptr, len + 1, kCFStringEncodingUTF8))); 149 150 CFRelease(cfstr); 151 152 if (usable) { 153 len = strlen(ptr); 154 /* Some devices have whitespace at the end...trim it. */ 155 while ((len > 0) && (ptr[len - 1] == ' ')) { 156 len--; 157 } 158 usable = (len > 0); 159 } 160 161 if (usable) { 162 ptr[len] = '\0'; 163 164#if DEBUG_COREAUDIO 165 printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", 166 ((iscapture) ? "capture" : "output"), 167 (int) *devCount, ptr, (int) dev); 168#endif 169 addfn(ptr, dev, addfndata); 170 } 171 SDL_free(ptr); /* addfn() would have copied the string. */ 172 } 173} 174 175static void 176COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) 177{ 178 build_device_list(iscapture, addToDevList, addfn); 179} 180 181static int 182find_device_by_name(_THIS, const char *devname, int iscapture) 183{ 184 AudioDeviceID devid = 0; 185 OSStatus result = noErr; 186 UInt32 size = 0; 187 UInt32 alive = 0; 188 pid_t pid = 0; 189 190 AudioObjectPropertyAddress addr = { 191 0, 192 kAudioObjectPropertyScopeGlobal, 193 kAudioObjectPropertyElementMaster 194 }; 195 196 if (devname == NULL) { 197 size = sizeof (AudioDeviceID); 198 addr.mSelector = 199 ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice : 200 kAudioHardwarePropertyDefaultOutputDevice); 201 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 202 0, NULL, &size, &devid); 203 CHECK_RESULT("AudioHardwareGetProperty (default device)"); 204 } else { 205 FindDevIdData data; 206 SDL_zero(data); 207 data.findname = devname; 208 build_device_list(iscapture, findDevId, &data); 209 if (!data.found) { 210 SDL_SetError("CoreAudio: No such audio device."); 211 return 0; 212 } 213 devid = data.devId; 214 } 215 216 addr.mSelector = kAudioDevicePropertyDeviceIsAlive; 217 addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : 218 kAudioDevicePropertyScopeOutput; 219 220 size = sizeof (alive); 221 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); 222 CHECK_RESULT 223 ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); 224 225 if (!alive) { 226 SDL_SetError("CoreAudio: requested device exists, but isn't alive."); 227 return 0; 228 } 229 230 addr.mSelector = kAudioDevicePropertyHogMode; 231 size = sizeof (pid); 232 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); 233 234 /* some devices don't support this property, so errors are fine here. */ 235 if ((result == noErr) && (pid != -1)) { 236 SDL_SetError("CoreAudio: requested device is being hogged."); 237 return 0; 238 } 239 240 this->hidden->deviceID = devid; 241 return 1; 242} 243#endif 244 245/* The CoreAudio callback */ 246static OSStatus 247outputCallback(void *inRefCon, 248 AudioUnitRenderActionFlags * ioActionFlags, 249 const AudioTimeStamp * inTimeStamp, 250 UInt32 inBusNumber, UInt32 inNumberFrames, 251 AudioBufferList * ioData) 252{ 253 SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon; 254 AudioBuffer *abuf; 255 UInt32 remaining, len; 256 void *ptr; 257 UInt32 i; 258 259 /* Only do anything if audio is enabled and not paused */ 260 if (!this->enabled || this->paused) { 261 for (i = 0; i < ioData->mNumberBuffers; i++) { 262 abuf = &ioData->mBuffers[i]; 263 SDL_memset(abuf->mData, this->spec.silence, abuf->mDataByteSize); 264 } 265 return 0; 266 } 267 268 /* No SDL conversion should be needed here, ever, since we accept 269 any input format in OpenAudio, and leave the conversion to CoreAudio. 270 */ 271 /* 272 SDL_assert(!this->convert.needed); 273 SDL_assert(this->spec.channels == ioData->mNumberChannels); 274 */ 275 276 for (i = 0; i < ioData->mNumberBuffers; i++) { 277 abuf = &ioData->mBuffers[i]; 278 remaining = abuf->mDataByteSize; 279 ptr = abuf->mData; 280 while (remaining > 0) { 281 if (this->hidden->bufferOffset >= this->hidden->bufferSize) { 282 /* Generate the data */ 283 SDL_LockMutex(this->mixer_lock); 284 (*this->spec.callback)(this->spec.userdata, 285 this->hidden->buffer, this->hidden->bufferSize); 286 SDL_UnlockMutex(this->mixer_lock); 287 this->hidden->bufferOffset = 0; 288 } 289 290 len = this->hidden->bufferSize - this->hidden->bufferOffset; 291 if (len > remaining) 292 len = remaining; 293 SDL_memcpy(ptr, (char *)this->hidden->buffer + 294 this->hidden->bufferOffset, len); 295 ptr = (char *)ptr + len; 296 remaining -= len; 297 this->hidden->bufferOffset += len; 298 } 299 } 300 301 return 0; 302} 303 304static OSStatus 305inputCallback(void *inRefCon, 306 AudioUnitRenderActionFlags * ioActionFlags, 307 const AudioTimeStamp * inTimeStamp, 308 UInt32 inBusNumber, UInt32 inNumberFrames, 309 AudioBufferList * ioData) 310{ 311 /* err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer); */ 312 /* !!! FIXME: write me! */ 313 return noErr; 314} 315 316 317static void 318COREAUDIO_CloseDevice(_THIS) 319{ 320 if (this->hidden != NULL) { 321 if (this->hidden->audioUnitOpened) { 322 AURenderCallbackStruct callback; 323 const AudioUnitElement output_bus = 0; 324 const AudioUnitElement input_bus = 1; 325 const int iscapture = this->iscapture; 326 const AudioUnitElement bus = 327 ((iscapture) ? input_bus : output_bus); 328 const AudioUnitScope scope = 329 ((iscapture) ? kAudioUnitScope_Output : 330 kAudioUnitScope_Input); 331 332 /* stop processing the audio unit */ 333 AudioOutputUnitStop(this->hidden->audioUnit); 334 335 /* Remove the input callback */ 336 SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); 337 AudioUnitSetProperty(this->hidden->audioUnit, 338 kAudioUnitProperty_SetRenderCallback, 339 scope, bus, &callback, sizeof(callback)); 340 341 #if MACOSX_COREAUDIO 342 CloseComponent(this->hidden->audioUnit); 343 #else 344 AudioComponentInstanceDispose(this->hidden->audioUnit); 345 #endif 346 347 this->hidden->audioUnitOpened = 0; 348 } 349 SDL_free(this->hidden->buffer); 350 SDL_free(this->hidden); 351 this->hidden = NULL; 352 } 353} 354 355 356static int 357prepare_audiounit(_THIS, const char *devname, int iscapture, 358 const AudioStreamBasicDescription * strdesc) 359{ 360 OSStatus result = noErr; 361 AURenderCallbackStruct callback; 362#if MACOSX_COREAUDIO 363 ComponentDescription desc; 364 Component comp = NULL; 365#else 366 AudioComponentDescription desc; 367 AudioComponent comp = NULL; 368#endif 369 const AudioUnitElement output_bus = 0; 370 const AudioUnitElement input_bus = 1; 371 const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus); 372 const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output : 373 kAudioUnitScope_Input); 374 375#if MACOSX_COREAUDIO 376 if (!find_device_by_name(this, devname, iscapture)) { 377 SDL_SetError("Couldn't find requested CoreAudio device"); 378 return 0; 379 } 380#endif 381 382 SDL_zero(desc); 383 desc.componentType = kAudioUnitType_Output; 384 desc.componentManufacturer = kAudioUnitManufacturer_Apple; 385 386#if MACOSX_COREAUDIO 387 desc.componentSubType = kAudioUnitSubType_DefaultOutput; 388 comp = FindNextComponent(NULL, &desc); 389#else 390 desc.componentSubType = kAudioUnitSubType_RemoteIO; 391 comp = AudioComponentFindNext(NULL, &desc); 392#endif 393 394 if (comp == NULL) { 395 SDL_SetError("Couldn't find requested CoreAudio component"); 396 return 0; 397 } 398 399 /* Open & initialize the audio unit */ 400#if MACOSX_COREAUDIO 401 result = OpenAComponent(comp, &this->hidden->audioUnit); 402 CHECK_RESULT("OpenAComponent"); 403#else 404 /* 405 AudioComponentInstanceNew only available on iPhone OS 2.0 and Mac OS X 10.6 406 We can't use OpenAComponent on iPhone because it is not present 407 */ 408 result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit); 409 CHECK_RESULT("AudioComponentInstanceNew"); 410#endif 411 412 this->hidden->audioUnitOpened = 1; 413 414#if MACOSX_COREAUDIO 415 result = AudioUnitSetProperty(this->hidden->audioUnit, 416 kAudioOutputUnitProperty_CurrentDevice, 417 kAudioUnitScope_Global, 0, 418 &this->hidden->deviceID, 419 sizeof(AudioDeviceID)); 420 CHECK_RESULT 421 ("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)"); 422#endif 423 424 /* Set the data format of the audio unit. */ 425 result = AudioUnitSetProperty(this->hidden->audioUnit, 426 kAudioUnitProperty_StreamFormat, 427 scope, bus, strdesc, sizeof(*strdesc)); 428 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)"); 429 430 /* Set the audio callback */ 431 SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); 432 callback.inputProc = ((iscapture) ? inputCallback : outputCallback); 433 callback.inputProcRefCon = this; 434 result = AudioUnitSetProperty(this->hidden->audioUnit, 435 kAudioUnitProperty_SetRenderCallback, 436 scope, bus, &callback, sizeof(callback)); 437 CHECK_RESULT 438 ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)"); 439 440 /* Calculate the final parameters for this audio specification */ 441 SDL_CalculateAudioSpec(&this->spec); 442 443 /* Allocate a sample buffer */ 444 this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size; 445 this->hidden->buffer = SDL_malloc(this->hidden->bufferSize); 446 447 result = AudioUnitInitialize(this->hidden->audioUnit); 448 CHECK_RESULT("AudioUnitInitialize"); 449 450 /* Finally, start processing of the audio unit */ 451 result = AudioOutputUnitStart(this->hidden->audioUnit); 452 CHECK_RESULT("AudioOutputUnitStart"); 453 454 /* We're running! */ 455 return 1; 456} 457 458 459static int 460COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture) 461{ 462 AudioStreamBasicDescription strdesc; 463 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 464 int valid_datatype = 0; 465 466 /* Initialize all variables that we clean on shutdown */ 467 this->hidden = (struct SDL_PrivateAudioData *) 468 SDL_malloc((sizeof *this->hidden)); 469 if (this->hidden == NULL) { 470 return SDL_OutOfMemory(); 471 } 472 SDL_memset(this->hidden, 0, (sizeof *this->hidden)); 473 474 /* Setup a AudioStreamBasicDescription with the requested format */ 475 SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription)); 476 strdesc.mFormatID = kAudioFormatLinearPCM; 477 strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked; 478 strdesc.mChannelsPerFrame = this->spec.channels; 479 strdesc.mSampleRate = this->spec.freq; 480 strdesc.mFramesPerPacket = 1; 481 482 while ((!valid_datatype) && (test_format)) { 483 this->spec.format = test_format; 484 /* Just a list of valid SDL formats, so people don't pass junk here. */ 485 switch (test_format) { 486 case AUDIO_U8: 487 case AUDIO_S8: 488 case AUDIO_U16LSB: 489 case AUDIO_S16LSB: 490 case AUDIO_U16MSB: 491 case AUDIO_S16MSB: 492 case AUDIO_S32LSB: 493 case AUDIO_S32MSB: 494 case AUDIO_F32LSB: 495 case AUDIO_F32MSB: 496 valid_datatype = 1; 497 strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format); 498 if (SDL_AUDIO_ISBIGENDIAN(this->spec.format)) 499 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; 500 501 if (SDL_AUDIO_ISFLOAT(this->spec.format)) 502 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat; 503 else if (SDL_AUDIO_ISSIGNED(this->spec.format)) 504 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; 505 break; 506 } 507 } 508 509 if (!valid_datatype) { /* shouldn't happen, but just in case... */ 510 COREAUDIO_CloseDevice(this); 511 return SDL_SetError("Unsupported audio format"); 512 } 513 514 strdesc.mBytesPerFrame = 515 strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8; 516 strdesc.mBytesPerPacket = 517 strdesc.mBytesPerFrame * strdesc.mFramesPerPacket; 518 519 if (!prepare_audiounit(this, devname, iscapture, &strdesc)) { 520 COREAUDIO_CloseDevice(this); 521 return -1; /* prepare_audiounit() will call SDL_SetError()... */ 522 } 523 524 return 0; /* good to go. */ 525} 526 527static int 528COREAUDIO_Init(SDL_AudioDriverImpl * impl) 529{ 530 /* Set the function pointers */ 531 impl->OpenDevice = COREAUDIO_OpenDevice; 532 impl->CloseDevice = COREAUDIO_CloseDevice; 533 534#if MACOSX_COREAUDIO 535 impl->DetectDevices = COREAUDIO_DetectDevices; 536#else 537 impl->OnlyHasDefaultOutputDevice = 1; 538 539 /* Set category to ambient sound so that other music continues playing. 540 You can change this at runtime in your own code if you need different 541 behavior. If this is common, we can add an SDL hint for this. 542 */ 543 AudioSessionInitialize(NULL, NULL, NULL, nil); 544 UInt32 category = kAudioSessionCategory_AmbientSound; 545 AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category); 546#endif 547 548 impl->ProvidesOwnCallbackThread = 1; 549 550 return 1; /* this audio target is available. */ 551} 552 553AudioBootStrap COREAUDIO_bootstrap = { 554 "coreaudio", "CoreAudio", COREAUDIO_Init, 0 555}; 556 557/* vi: set ts=4 sw=4 expandtab: */