src/audio/coreaudio/SDL_coreaudio.c
author Sam Lantinga
Fri, 02 Nov 2012 09:28:40 -0700
changeset 6634 b14b66ed5718
parent 6628 7994e6979876
child 6885 700f1b25f77f
permissions -rw-r--r--
Fixed bug 1632 - iOS CoreAudio doesn't close

C.W. Betts 2012-10-28 19:42:01 PDT

I noticed when looking through the CoreAudio code of SDL 2.0 that there was a
fix me wondering how iOS closed the audio system. While working on my own audio
code on PlayerPRO, I discovered that Carbon's component code was replaced in
the audio subsystem with Audio Component Services.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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_config.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 
    30 static 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
    40 typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data);
    41 
    42 static void
    43 addToDevList(const char *name, AudioDeviceID devId, void *data)
    44 {
    45     SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
    46     addfn(name);
    47 }
    48 
    49 typedef struct
    50 {
    51     const char *findname;
    52     AudioDeviceID devId;
    53     int found;
    54 } FindDevIdData;
    55 
    56 static void
    57 findDevId(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 
    68 static void
    69 build_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 
   175 static void
   176 COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
   177 {
   178     build_device_list(iscapture, addToDevList, addfn);
   179 }
   180 
   181 static int
   182 find_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 */
   246 static OSStatus
   247 outputCallback(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_mutexP(this->mixer_lock);
   284                 (*this->spec.callback)(this->spec.userdata,
   285                             this->hidden->buffer, this->hidden->bufferSize);
   286                 SDL_mutexV(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 
   304 static OSStatus
   305 inputCallback(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 
   317 static void
   318 COREAUDIO_CloseDevice(_THIS)
   319 {
   320     if (this->hidden != NULL) {
   321         if (this->hidden->audioUnitOpened) {
   322             OSStatus result = noErr;
   323             AURenderCallbackStruct callback;
   324             const AudioUnitElement output_bus = 0;
   325             const AudioUnitElement input_bus = 1;
   326             const int iscapture = this->iscapture;
   327             const AudioUnitElement bus =
   328                 ((iscapture) ? input_bus : output_bus);
   329             const AudioUnitScope scope =
   330                 ((iscapture) ? kAudioUnitScope_Output :
   331                  kAudioUnitScope_Input);
   332 
   333             /* stop processing the audio unit */
   334             result = AudioOutputUnitStop(this->hidden->audioUnit);
   335 
   336             /* Remove the input callback */
   337             SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
   338             result = AudioUnitSetProperty(this->hidden->audioUnit,
   339                                           kAudioUnitProperty_SetRenderCallback,
   340                                           scope, bus, &callback,
   341                                           sizeof(callback));
   342 
   343             #if MACOSX_COREAUDIO
   344             CloseComponent(this->hidden->audioUnit);
   345             #else
   346             AudioComponentInstanceDispose(this->hidden->audioUnit);
   347             #endif
   348 
   349             this->hidden->audioUnitOpened = 0;
   350         }
   351         SDL_free(this->hidden->buffer);
   352         SDL_free(this->hidden);
   353         this->hidden = NULL;
   354     }
   355 }
   356 
   357 
   358 static int
   359 prepare_audiounit(_THIS, const char *devname, int iscapture,
   360                   const AudioStreamBasicDescription * strdesc)
   361 {
   362     OSStatus result = noErr;
   363     AURenderCallbackStruct callback;
   364 #if MACOSX_COREAUDIO
   365     ComponentDescription desc;
   366     Component comp = NULL;
   367 #else
   368     AudioComponentDescription desc;
   369     AudioComponent comp = NULL;
   370 #endif
   371     const AudioUnitElement output_bus = 0;
   372     const AudioUnitElement input_bus = 1;
   373     const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
   374     const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
   375                                   kAudioUnitScope_Input);
   376 
   377 #if MACOSX_COREAUDIO
   378     if (!find_device_by_name(this, devname, iscapture)) {
   379         SDL_SetError("Couldn't find requested CoreAudio device");
   380         return 0;
   381     }
   382 #endif
   383     
   384     SDL_zero(desc);
   385     desc.componentType = kAudioUnitType_Output;
   386     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   387 
   388 #if MACOSX_COREAUDIO
   389     desc.componentSubType = kAudioUnitSubType_DefaultOutput;
   390     comp = FindNextComponent(NULL, &desc);
   391 #else
   392     desc.componentSubType = kAudioUnitSubType_RemoteIO;
   393     comp = AudioComponentFindNext(NULL, &desc);
   394 #endif
   395 
   396     if (comp == NULL) {
   397         SDL_SetError("Couldn't find requested CoreAudio component");
   398         return 0;
   399     }
   400 
   401     /* Open & initialize the audio unit */
   402 #if MACOSX_COREAUDIO
   403     result = OpenAComponent(comp, &this->hidden->audioUnit);
   404     CHECK_RESULT("OpenAComponent");
   405 #else
   406     /*
   407        AudioComponentInstanceNew only available on iPhone OS 2.0 and Mac OS X 10.6
   408        We can't use OpenAComponent on iPhone because it is not present
   409      */
   410     result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit);
   411     CHECK_RESULT("AudioComponentInstanceNew");
   412 #endif
   413 
   414     this->hidden->audioUnitOpened = 1;
   415 
   416 #if MACOSX_COREAUDIO
   417     result = AudioUnitSetProperty(this->hidden->audioUnit,
   418                                   kAudioOutputUnitProperty_CurrentDevice,
   419                                   kAudioUnitScope_Global, 0,
   420                                   &this->hidden->deviceID,
   421                                   sizeof(AudioDeviceID));
   422     CHECK_RESULT
   423         ("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
   424 #endif
   425 
   426     /* Set the data format of the audio unit. */
   427     result = AudioUnitSetProperty(this->hidden->audioUnit,
   428                                   kAudioUnitProperty_StreamFormat,
   429                                   scope, bus, strdesc, sizeof(*strdesc));
   430     CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
   431 
   432     /* Set the audio callback */
   433     SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
   434     callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
   435     callback.inputProcRefCon = this;
   436     result = AudioUnitSetProperty(this->hidden->audioUnit,
   437                                   kAudioUnitProperty_SetRenderCallback,
   438                                   scope, bus, &callback, sizeof(callback));
   439     CHECK_RESULT
   440         ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)");
   441 
   442     /* Calculate the final parameters for this audio specification */
   443     SDL_CalculateAudioSpec(&this->spec);
   444 
   445     /* Allocate a sample buffer */
   446     this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
   447     this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   448 
   449     result = AudioUnitInitialize(this->hidden->audioUnit);
   450     CHECK_RESULT("AudioUnitInitialize");
   451 
   452     /* Finally, start processing of the audio unit */
   453     result = AudioOutputUnitStart(this->hidden->audioUnit);
   454     CHECK_RESULT("AudioOutputUnitStart");
   455 
   456     /* We're running! */
   457     return 1;
   458 }
   459 
   460 
   461 static int
   462 COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
   463 {
   464     AudioStreamBasicDescription strdesc;
   465     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   466     int valid_datatype = 0;
   467 
   468     /* Initialize all variables that we clean on shutdown */
   469     this->hidden = (struct SDL_PrivateAudioData *)
   470         SDL_malloc((sizeof *this->hidden));
   471     if (this->hidden == NULL) {
   472         SDL_OutOfMemory();
   473         return (0);
   474     }
   475     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   476 
   477     /* Setup a AudioStreamBasicDescription with the requested format */
   478     SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
   479     strdesc.mFormatID = kAudioFormatLinearPCM;
   480     strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
   481     strdesc.mChannelsPerFrame = this->spec.channels;
   482     strdesc.mSampleRate = this->spec.freq;
   483     strdesc.mFramesPerPacket = 1;
   484 
   485     while ((!valid_datatype) && (test_format)) {
   486         this->spec.format = test_format;
   487         /* Just a list of valid SDL formats, so people don't pass junk here. */
   488         switch (test_format) {
   489         case AUDIO_U8:
   490         case AUDIO_S8:
   491         case AUDIO_U16LSB:
   492         case AUDIO_S16LSB:
   493         case AUDIO_U16MSB:
   494         case AUDIO_S16MSB:
   495         case AUDIO_S32LSB:
   496         case AUDIO_S32MSB:
   497         case AUDIO_F32LSB:
   498         case AUDIO_F32MSB:
   499             valid_datatype = 1;
   500             strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
   501             if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
   502                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
   503 
   504             if (SDL_AUDIO_ISFLOAT(this->spec.format))
   505                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
   506             else if (SDL_AUDIO_ISSIGNED(this->spec.format))
   507                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
   508             break;
   509         }
   510     }
   511 
   512     if (!valid_datatype) {      /* shouldn't happen, but just in case... */
   513         COREAUDIO_CloseDevice(this);
   514         SDL_SetError("Unsupported audio format");
   515         return 0;
   516     }
   517 
   518     strdesc.mBytesPerFrame =
   519         strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
   520     strdesc.mBytesPerPacket =
   521         strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
   522 
   523     if (!prepare_audiounit(this, devname, iscapture, &strdesc)) {
   524         COREAUDIO_CloseDevice(this);
   525         return 0;               /* prepare_audiounit() will call SDL_SetError()... */
   526     }
   527 
   528     return 1;                   /* good to go. */
   529 }
   530 
   531 static int
   532 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
   533 {
   534     /* Set the function pointers */
   535     impl->OpenDevice = COREAUDIO_OpenDevice;
   536     impl->CloseDevice = COREAUDIO_CloseDevice;
   537 
   538 #if MACOSX_COREAUDIO
   539     impl->DetectDevices = COREAUDIO_DetectDevices;
   540 #else
   541     impl->OnlyHasDefaultOutputDevice = 1;
   542 
   543     /* Set category to ambient sound so that other music continues playing.
   544        You can change this at runtime in your own code if you need different
   545        behavior.  If this is common, we can add an SDL hint for this.
   546     */
   547     AudioSessionInitialize(NULL, NULL, NULL, nil);
   548     UInt32 category = kAudioSessionCategory_AmbientSound;
   549     AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category);
   550 #endif
   551 
   552     impl->ProvidesOwnCallbackThread = 1;
   553 
   554     return 1;   /* this audio target is available. */
   555 }
   556 
   557 AudioBootStrap COREAUDIO_bootstrap = {
   558     "coreaudio", "CoreAudio", COREAUDIO_Init, 0
   559 };
   560 
   561 /* vi: set ts=4 sw=4 expandtab: */