2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../../SDL_internal.h"
23 #if SDL_AUDIO_DRIVER_COREAUDIO
25 #include "SDL_audio.h"
26 #include "../SDL_audio_c.h"
27 #include "../SDL_sysaudio.h"
28 #include "SDL_coreaudio.h"
29 #include "SDL_assert.h"
31 #define DEBUG_COREAUDIO 0
33 static void COREAUDIO_CloseDevice(_THIS);
35 #define CHECK_RESULT(msg) \
36 if (result != noErr) { \
37 COREAUDIO_CloseDevice(this); \
38 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
43 static const AudioObjectPropertyAddress devlist_address = {
44 kAudioHardwarePropertyDevices,
45 kAudioObjectPropertyScopeGlobal,
46 kAudioObjectPropertyElementMaster
49 typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
51 typedef struct AudioDeviceList
55 struct AudioDeviceList *next;
58 static AudioDeviceList *output_devs = NULL;
59 static AudioDeviceList *capture_devs = NULL;
62 add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
64 AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
69 item->alive = SDL_TRUE;
70 item->next = iscapture ? capture_devs : output_devs;
81 addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
83 if (add_to_internal_dev_list(iscapture, devId)) {
84 SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
89 build_device_list(int iscapture, addDevFn addfn, void *addfndata)
91 OSStatus result = noErr;
93 AudioDeviceID *devs = NULL;
97 result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
98 &devlist_address, 0, NULL, &size);
99 if (result != kAudioHardwareNoError)
102 devs = (AudioDeviceID *) alloca(size);
106 result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
107 &devlist_address, 0, NULL, &size, devs);
108 if (result != kAudioHardwareNoError)
111 max = size / sizeof (AudioDeviceID);
112 for (i = 0; i < max; i++) {
113 CFStringRef cfstr = NULL;
115 AudioDeviceID dev = devs[i];
116 AudioBufferList *buflist = NULL;
119 const AudioObjectPropertyAddress addr = {
120 kAudioDevicePropertyStreamConfiguration,
121 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
122 kAudioObjectPropertyElementMaster
125 const AudioObjectPropertyAddress nameaddr = {
126 kAudioObjectPropertyName,
127 iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
128 kAudioObjectPropertyElementMaster
131 result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
135 buflist = (AudioBufferList *) SDL_malloc(size);
139 result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
142 if (result == noErr) {
144 for (j = 0; j < buflist->mNumberBuffers; j++) {
145 if (buflist->mBuffers[j].mNumberChannels > 0) {
158 size = sizeof (CFStringRef);
159 result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
160 if (result != kAudioHardwareNoError)
163 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
164 kCFStringEncodingUTF8);
166 ptr = (char *) SDL_malloc(len + 1);
167 usable = ((ptr != NULL) &&
169 (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));
175 /* Some devices have whitespace at the end...trim it. */
176 while ((len > 0) && (ptr[len - 1] == ' ')) {
186 printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
187 ((iscapture) ? "capture" : "output"),
188 (int) *devCount, ptr, (int) dev);
190 addfn(ptr, iscapture, dev, addfndata);
192 SDL_free(ptr); /* addfn() would have copied the string. */
197 free_audio_device_list(AudioDeviceList **list)
199 AudioDeviceList *item = *list;
201 AudioDeviceList *next = item->next;
209 COREAUDIO_DetectDevices(void)
211 build_device_list(SDL_TRUE, addToDevList, NULL);
212 build_device_list(SDL_FALSE, addToDevList, NULL);
216 build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
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;
227 add_to_internal_dev_list(iscapture, devId); /* new device, add it. */
228 SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
232 reprocess_device_list(const int iscapture, AudioDeviceList **list)
234 AudioDeviceList *item;
235 AudioDeviceList *prev = NULL;
236 for (item = *list; item != NULL; item = item->next) {
237 item->alive = SDL_FALSE;
240 build_device_list(iscapture, build_device_change_list, list);
242 /* free items in the list that aren't still alive. */
244 while (item != NULL) {
245 AudioDeviceList *next = item->next;
249 SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid));
251 prev->next = item->next;
261 /* this is called when the system's list of available audio devices changes. */
263 device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
265 reprocess_device_list(SDL_TRUE, &capture_devs);
266 reprocess_device_list(SDL_FALSE, &output_devs);
271 /* The CoreAudio callback */
273 outputCallback(void *inRefCon,
274 AudioUnitRenderActionFlags * ioActionFlags,
275 const AudioTimeStamp * inTimeStamp,
276 UInt32 inBusNumber, UInt32 inNumberFrames,
277 AudioBufferList * ioData)
279 SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
281 UInt32 remaining, len;
285 /* Only do anything if audio is enabled and not paused */
286 if (!this->enabled || this->paused) {
287 for (i = 0; i < ioData->mNumberBuffers; i++) {
288 abuf = &ioData->mBuffers[i];
289 SDL_memset(abuf->mData, this->spec.silence, abuf->mDataByteSize);
294 /* No SDL conversion should be needed here, ever, since we accept
295 any input format in OpenAudio, and leave the conversion to CoreAudio.
298 SDL_assert(!this->convert.needed);
299 SDL_assert(this->spec.channels == ioData->mNumberChannels);
302 for (i = 0; i < ioData->mNumberBuffers; i++) {
303 abuf = &ioData->mBuffers[i];
304 remaining = abuf->mDataByteSize;
306 while (remaining > 0) {
307 if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
308 /* Generate the data */
309 SDL_LockMutex(this->mixer_lock);
310 (*this->spec.callback)(this->spec.userdata,
311 this->hidden->buffer, this->hidden->bufferSize);
312 SDL_UnlockMutex(this->mixer_lock);
313 this->hidden->bufferOffset = 0;
316 len = this->hidden->bufferSize - this->hidden->bufferOffset;
319 SDL_memcpy(ptr, (char *)this->hidden->buffer +
320 this->hidden->bufferOffset, len);
321 ptr = (char *)ptr + len;
323 this->hidden->bufferOffset += len;
331 inputCallback(void *inRefCon,
332 AudioUnitRenderActionFlags * ioActionFlags,
333 const AudioTimeStamp * inTimeStamp,
334 UInt32 inBusNumber, UInt32 inNumberFrames,
335 AudioBufferList * ioData)
337 /* err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer); */
338 /* !!! FIXME: write me! */
344 static const AudioObjectPropertyAddress alive_address =
346 kAudioDevicePropertyDeviceIsAlive,
347 kAudioObjectPropertyScopeGlobal,
348 kAudioObjectPropertyElementMaster
352 device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
354 SDL_AudioDevice *this = (SDL_AudioDevice *) data;
355 SDL_bool dead = SDL_FALSE;
357 UInt32 size = sizeof (isAlive);
360 if (!this->enabled) {
361 return 0; /* already known to be dead. */
364 error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
365 0, NULL, &size, &isAlive);
367 if (error == kAudioHardwareBadDeviceError) {
368 dead = SDL_TRUE; /* device was unplugged. */
369 } else if ((error == kAudioHardwareNoError) && (!isAlive)) {
370 dead = SDL_TRUE; /* device died in some other way. */
374 SDL_OpenedAudioDeviceDisconnected(this);
382 COREAUDIO_CloseDevice(_THIS)
384 if (this->hidden != NULL) {
385 if (this->hidden->audioUnitOpened) {
387 /* Unregister our disconnect callback. */
388 AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
391 AURenderCallbackStruct callback;
392 const AudioUnitElement output_bus = 0;
393 const AudioUnitElement input_bus = 1;
394 const int iscapture = this->iscapture;
395 const AudioUnitElement bus =
396 ((iscapture) ? input_bus : output_bus);
397 const AudioUnitScope scope =
398 ((iscapture) ? kAudioUnitScope_Output :
399 kAudioUnitScope_Input);
401 /* stop processing the audio unit */
402 AudioOutputUnitStop(this->hidden->audioUnit);
404 /* Remove the input callback */
405 SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
406 AudioUnitSetProperty(this->hidden->audioUnit,
407 kAudioUnitProperty_SetRenderCallback,
408 scope, bus, &callback, sizeof(callback));
411 CloseComponent(this->hidden->audioUnit);
413 AudioComponentInstanceDispose(this->hidden->audioUnit);
416 this->hidden->audioUnitOpened = 0;
418 SDL_free(this->hidden->buffer);
419 SDL_free(this->hidden);
426 prepare_device(_THIS, void *handle, int iscapture)
428 AudioDeviceID devid = (AudioDeviceID) ((size_t) handle);
429 OSStatus result = noErr;
434 AudioObjectPropertyAddress addr = {
436 kAudioObjectPropertyScopeGlobal,
437 kAudioObjectPropertyElementMaster
440 if (handle == NULL) {
441 size = sizeof (AudioDeviceID);
443 ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
444 kAudioHardwarePropertyDefaultOutputDevice);
445 result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
446 0, NULL, &size, &devid);
447 CHECK_RESULT("AudioHardwareGetProperty (default device)");
450 addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
451 addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
452 kAudioDevicePropertyScopeOutput;
454 size = sizeof (alive);
455 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
457 ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
460 SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
464 addr.mSelector = kAudioDevicePropertyHogMode;
466 result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
468 /* some devices don't support this property, so errors are fine here. */
469 if ((result == noErr) && (pid != -1)) {
470 SDL_SetError("CoreAudio: requested device is being hogged.");
474 this->hidden->deviceID = devid;
480 prepare_audiounit(_THIS, void *handle, int iscapture,
481 const AudioStreamBasicDescription * strdesc)
483 OSStatus result = noErr;
484 AURenderCallbackStruct callback;
486 ComponentDescription desc;
487 Component comp = NULL;
489 AudioComponentDescription desc;
490 AudioComponent comp = NULL;
492 const AudioUnitElement output_bus = 0;
493 const AudioUnitElement input_bus = 1;
494 const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
495 const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
496 kAudioUnitScope_Input);
499 if (!prepare_device(this, handle, iscapture)) {
505 desc.componentType = kAudioUnitType_Output;
506 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
509 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
510 comp = FindNextComponent(NULL, &desc);
512 desc.componentSubType = kAudioUnitSubType_RemoteIO;
513 comp = AudioComponentFindNext(NULL, &desc);
517 SDL_SetError("Couldn't find requested CoreAudio component");
521 /* Open & initialize the audio unit */
523 result = OpenAComponent(comp, &this->hidden->audioUnit);
524 CHECK_RESULT("OpenAComponent");
527 AudioComponentInstanceNew only available on iPhone OS 2.0 and Mac OS X 10.6
528 We can't use OpenAComponent on iPhone because it is not present
530 result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit);
531 CHECK_RESULT("AudioComponentInstanceNew");
534 this->hidden->audioUnitOpened = 1;
537 result = AudioUnitSetProperty(this->hidden->audioUnit,
538 kAudioOutputUnitProperty_CurrentDevice,
539 kAudioUnitScope_Global, 0,
540 &this->hidden->deviceID,
541 sizeof(AudioDeviceID));
543 ("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
546 /* Set the data format of the audio unit. */
547 result = AudioUnitSetProperty(this->hidden->audioUnit,
548 kAudioUnitProperty_StreamFormat,
549 scope, bus, strdesc, sizeof(*strdesc));
550 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
552 /* Set the audio callback */
553 SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
554 callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
555 callback.inputProcRefCon = this;
556 result = AudioUnitSetProperty(this->hidden->audioUnit,
557 kAudioUnitProperty_SetRenderCallback,
558 scope, bus, &callback, sizeof(callback));
560 ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)");
562 /* Calculate the final parameters for this audio specification */
563 SDL_CalculateAudioSpec(&this->spec);
565 /* Allocate a sample buffer */
566 this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
567 this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
569 result = AudioUnitInitialize(this->hidden->audioUnit);
570 CHECK_RESULT("AudioUnitInitialize");
572 /* Finally, start processing of the audio unit */
573 result = AudioOutputUnitStart(this->hidden->audioUnit);
574 CHECK_RESULT("AudioOutputUnitStart");
577 /* Fire a callback if the device stops being "alive" (disconnected, etc). */
578 AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
587 COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
589 AudioStreamBasicDescription strdesc;
590 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
591 int valid_datatype = 0;
593 /* Initialize all variables that we clean on shutdown */
594 this->hidden = (struct SDL_PrivateAudioData *)
595 SDL_malloc((sizeof *this->hidden));
596 if (this->hidden == NULL) {
597 return SDL_OutOfMemory();
599 SDL_memset(this->hidden, 0, (sizeof *this->hidden));
601 /* Setup a AudioStreamBasicDescription with the requested format */
602 SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
603 strdesc.mFormatID = kAudioFormatLinearPCM;
604 strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
605 strdesc.mChannelsPerFrame = this->spec.channels;
606 strdesc.mSampleRate = this->spec.freq;
607 strdesc.mFramesPerPacket = 1;
609 while ((!valid_datatype) && (test_format)) {
610 this->spec.format = test_format;
611 /* Just a list of valid SDL formats, so people don't pass junk here. */
612 switch (test_format) {
624 strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
625 if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
626 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
628 if (SDL_AUDIO_ISFLOAT(this->spec.format))
629 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
630 else if (SDL_AUDIO_ISSIGNED(this->spec.format))
631 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
636 if (!valid_datatype) { /* shouldn't happen, but just in case... */
637 COREAUDIO_CloseDevice(this);
638 return SDL_SetError("Unsupported audio format");
641 strdesc.mBytesPerFrame =
642 strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
643 strdesc.mBytesPerPacket =
644 strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
646 if (!prepare_audiounit(this, handle, iscapture, &strdesc)) {
647 COREAUDIO_CloseDevice(this);
648 return -1; /* prepare_audiounit() will call SDL_SetError()... */
651 return 0; /* good to go. */
655 COREAUDIO_Deinitialize(void)
658 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
659 free_audio_device_list(&capture_devs);
660 free_audio_device_list(&output_devs);
665 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
667 /* Set the function pointers */
668 impl->OpenDevice = COREAUDIO_OpenDevice;
669 impl->CloseDevice = COREAUDIO_CloseDevice;
670 impl->Deinitialize = COREAUDIO_Deinitialize;
673 impl->DetectDevices = COREAUDIO_DetectDevices;
674 AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
676 impl->OnlyHasDefaultOutputDevice = 1;
678 /* Set category to ambient sound so that other music continues playing.
679 You can change this at runtime in your own code if you need different
680 behavior. If this is common, we can add an SDL hint for this.
682 AudioSessionInitialize(NULL, NULL, NULL, nil);
683 UInt32 category = kAudioSessionCategory_AmbientSound;
684 AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category);
687 impl->ProvidesOwnCallbackThread = 1;
689 return 1; /* this audio target is available. */
692 AudioBootStrap COREAUDIO_bootstrap = {
693 "coreaudio", "CoreAudio", COREAUDIO_Init, 0
696 #endif /* SDL_AUDIO_DRIVER_COREAUDIO */
698 /* vi: set ts=4 sw=4 expandtab: */