src/audio/coreaudio/SDL_coreaudio.c
author Sam Lantinga
Sat, 06 Oct 2012 12:16:32 -0700
changeset 6566 dd7e57847ea9
parent 6413 701a0c0d70d0
child 6628 7994e6979876
permissions -rw-r--r--
Add flags to the vidmode debug output
     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             /* !!! FIXME: how does iOS free this? */
   344             #if MACOSX_COREAUDIO
   345             CloseComponent(this->hidden->audioUnit);
   346             #endif
   347 
   348             this->hidden->audioUnitOpened = 0;
   349         }
   350         SDL_free(this->hidden->buffer);
   351         SDL_free(this->hidden);
   352         this->hidden = NULL;
   353     }
   354 }
   355 
   356 
   357 static int
   358 prepare_audiounit(_THIS, const char *devname, int iscapture,
   359                   const AudioStreamBasicDescription * strdesc)
   360 {
   361     OSStatus result = noErr;
   362     AURenderCallbackStruct callback;
   363 #if MACOSX_COREAUDIO
   364     ComponentDescription desc;
   365     Component comp = NULL;
   366 #else
   367     AudioComponentDescription desc;
   368     AudioComponent comp = NULL;
   369 #endif
   370     const AudioUnitElement output_bus = 0;
   371     const AudioUnitElement input_bus = 1;
   372     const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
   373     const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
   374                                   kAudioUnitScope_Input);
   375 
   376 #if MACOSX_COREAUDIO
   377     if (!find_device_by_name(this, devname, iscapture)) {
   378         SDL_SetError("Couldn't find requested CoreAudio device");
   379         return 0;
   380     }
   381 #endif
   382     
   383     SDL_zero(desc);
   384     desc.componentType = kAudioUnitType_Output;
   385     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   386 
   387 #if MACOSX_COREAUDIO
   388     desc.componentSubType = kAudioUnitSubType_DefaultOutput;
   389     comp = FindNextComponent(NULL, &desc);
   390 #else
   391     desc.componentSubType = kAudioUnitSubType_RemoteIO;
   392     comp = AudioComponentFindNext(NULL, &desc);
   393 #endif
   394 
   395     if (comp == NULL) {
   396         SDL_SetError("Couldn't find requested CoreAudio component");
   397         return 0;
   398     }
   399 
   400     /* Open & initialize the audio unit */
   401 #if MACOSX_COREAUDIO
   402     result = OpenAComponent(comp, &this->hidden->audioUnit);
   403     CHECK_RESULT("OpenAComponent");
   404 #else
   405     /*
   406        AudioComponentInstanceNew only available on iPhone OS 2.0 and Mac OS X 10.6
   407        We can't use OpenAComponent on iPhone because it is not present
   408      */
   409     result = AudioComponentInstanceNew(comp, &this->hidden->audioUnit);
   410     CHECK_RESULT("AudioComponentInstanceNew");
   411 #endif
   412 
   413     this->hidden->audioUnitOpened = 1;
   414 
   415 #if MACOSX_COREAUDIO
   416     result = AudioUnitSetProperty(this->hidden->audioUnit,
   417                                   kAudioOutputUnitProperty_CurrentDevice,
   418                                   kAudioUnitScope_Global, 0,
   419                                   &this->hidden->deviceID,
   420                                   sizeof(AudioDeviceID));
   421     CHECK_RESULT
   422         ("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
   423 #endif
   424 
   425     /* Set the data format of the audio unit. */
   426     result = AudioUnitSetProperty(this->hidden->audioUnit,
   427                                   kAudioUnitProperty_StreamFormat,
   428                                   scope, bus, strdesc, sizeof(*strdesc));
   429     CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
   430 
   431     /* Set the audio callback */
   432     SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct));
   433     callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
   434     callback.inputProcRefCon = this;
   435     result = AudioUnitSetProperty(this->hidden->audioUnit,
   436                                   kAudioUnitProperty_SetRenderCallback,
   437                                   scope, bus, &callback, sizeof(callback));
   438     CHECK_RESULT
   439         ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)");
   440 
   441     /* Calculate the final parameters for this audio specification */
   442     SDL_CalculateAudioSpec(&this->spec);
   443 
   444     /* Allocate a sample buffer */
   445     this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
   446     this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   447 
   448     result = AudioUnitInitialize(this->hidden->audioUnit);
   449     CHECK_RESULT("AudioUnitInitialize");
   450 
   451     /* Finally, start processing of the audio unit */
   452     result = AudioOutputUnitStart(this->hidden->audioUnit);
   453     CHECK_RESULT("AudioOutputUnitStart");
   454 
   455     /* We're running! */
   456     return 1;
   457 }
   458 
   459 
   460 static int
   461 COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
   462 {
   463     AudioStreamBasicDescription strdesc;
   464     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   465     int valid_datatype = 0;
   466 
   467     /* Initialize all variables that we clean on shutdown */
   468     this->hidden = (struct SDL_PrivateAudioData *)
   469         SDL_malloc((sizeof *this->hidden));
   470     if (this->hidden == NULL) {
   471         SDL_OutOfMemory();
   472         return (0);
   473     }
   474     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   475 
   476     /* Setup a AudioStreamBasicDescription with the requested format */
   477     SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
   478     strdesc.mFormatID = kAudioFormatLinearPCM;
   479     strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
   480     strdesc.mChannelsPerFrame = this->spec.channels;
   481     strdesc.mSampleRate = this->spec.freq;
   482     strdesc.mFramesPerPacket = 1;
   483 
   484     while ((!valid_datatype) && (test_format)) {
   485         this->spec.format = test_format;
   486         /* Just a list of valid SDL formats, so people don't pass junk here. */
   487         switch (test_format) {
   488         case AUDIO_U8:
   489         case AUDIO_S8:
   490         case AUDIO_U16LSB:
   491         case AUDIO_S16LSB:
   492         case AUDIO_U16MSB:
   493         case AUDIO_S16MSB:
   494         case AUDIO_S32LSB:
   495         case AUDIO_S32MSB:
   496         case AUDIO_F32LSB:
   497         case AUDIO_F32MSB:
   498             valid_datatype = 1;
   499             strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
   500             if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
   501                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
   502 
   503             if (SDL_AUDIO_ISFLOAT(this->spec.format))
   504                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
   505             else if (SDL_AUDIO_ISSIGNED(this->spec.format))
   506                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
   507             break;
   508         }
   509     }
   510 
   511     if (!valid_datatype) {      /* shouldn't happen, but just in case... */
   512         COREAUDIO_CloseDevice(this);
   513         SDL_SetError("Unsupported audio format");
   514         return 0;
   515     }
   516 
   517     strdesc.mBytesPerFrame =
   518         strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
   519     strdesc.mBytesPerPacket =
   520         strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
   521 
   522     if (!prepare_audiounit(this, devname, iscapture, &strdesc)) {
   523         COREAUDIO_CloseDevice(this);
   524         return 0;               /* prepare_audiounit() will call SDL_SetError()... */
   525     }
   526 
   527     return 1;                   /* good to go. */
   528 }
   529 
   530 static int
   531 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
   532 {
   533     /* Set the function pointers */
   534     impl->OpenDevice = COREAUDIO_OpenDevice;
   535     impl->CloseDevice = COREAUDIO_CloseDevice;
   536 
   537 #if MACOSX_COREAUDIO
   538     impl->DetectDevices = COREAUDIO_DetectDevices;
   539 #else
   540     impl->OnlyHasDefaultOutputDevice = 1;
   541 #endif
   542     
   543     impl->ProvidesOwnCallbackThread = 1;
   544 
   545     return 1;   /* this audio target is available. */
   546 }
   547 
   548 AudioBootStrap COREAUDIO_bootstrap = {
   549     "coreaudio", "CoreAudio", COREAUDIO_Init, 0
   550 };
   551 
   552 /* vi: set ts=4 sw=4 expandtab: */