src/audio/macosx/SDL_coreaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Wed, 04 Oct 2006 21:39:33 +0000
branchSDL-ryan-multiple-audio-device
changeset 3799 f424927138ff
parent 3798 c8b3d3d13ed1
child 3806 1485d42cf1a0
permissions -rw-r--r--
Fixed CoreAudio specific device open.
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2006 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Lesser General Public
     7     License as published by the Free Software Foundation; either
     8     version 2.1 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Lesser General Public License for more details.
    14 
    15     You should have received a copy of the GNU Lesser General Public
    16     License along with this library; if not, write to the Free Software
    17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 #include "SDL_config.h"
    23 
    24 #include <CoreAudio/CoreAudio.h>
    25 #include <AudioUnit/AudioUnit.h>
    26 
    27 #include "SDL_audio.h"
    28 #include "../SDL_audio_c.h"
    29 #include "../SDL_sysaudio.h"
    30 #include "SDL_coreaudio.h"
    31 
    32 #define DEBUG_COREAUDIO 0
    33 
    34 typedef struct COREAUDIO_DeviceList
    35 {
    36     AudioDeviceID id;
    37     const char *name;
    38 } COREAUDIO_DeviceList;
    39 
    40 static COREAUDIO_DeviceList *inputDevices = NULL;
    41 static int inputDeviceCount = 0;
    42 static COREAUDIO_DeviceList *outputDevices = NULL;
    43 static int outputDeviceCount = 0;
    44 
    45 static void
    46 free_device_list(COREAUDIO_DeviceList **devices, int *devCount)
    47 {
    48     if (*devices) {
    49         int i = *devCount;
    50         while (i--)
    51             SDL_free((void *) (*devices)[i].name);
    52         SDL_free(*devices);
    53         *devices = NULL;
    54     }
    55     *devCount = 0;
    56 }
    57 
    58 
    59 static void
    60 build_device_list(int iscapture, COREAUDIO_DeviceList **devices, int *devCount)
    61 {
    62     Boolean outWritable = 0;
    63     OSStatus result = noErr;
    64     UInt32 size = 0;
    65     AudioDeviceID *devs = NULL;
    66     UInt32 i = 0;
    67     UInt32 max = 0;
    68 
    69     free_device_list(devices, devCount);
    70 
    71     result = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
    72                                          &size, &outWritable);
    73 
    74     if (result != kAudioHardwareNoError)
    75         return;
    76 
    77     devs = (AudioDeviceID *) alloca(size);
    78     if (devs == NULL)
    79         return;
    80 
    81     max = size / sizeof (AudioDeviceID);
    82     *devices = (COREAUDIO_DeviceList *) SDL_malloc(max * sizeof (**devices));
    83     if (*devices == NULL)
    84         return;
    85 
    86     result = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
    87                                      &size, devs);
    88     if (result != kAudioHardwareNoError)
    89         return;
    90 
    91     for (i = 0; i < max; i++) {
    92         CFStringRef cfstr = NULL;
    93         char *ptr = NULL;
    94         AudioDeviceID dev = devs[i];
    95         AudioBufferList *buflist = NULL;
    96         int usable = 0;
    97         CFIndex len = 0;
    98 
    99         result = AudioDeviceGetPropertyInfo(dev, 0, iscapture,
   100                                       kAudioDevicePropertyStreamConfiguration,
   101                                       &size, &outWritable);
   102         if (result != noErr)
   103             continue;
   104 
   105         buflist = (AudioBufferList *) SDL_malloc(size);
   106         if (buflist == NULL)
   107             continue;
   108 
   109         result = AudioDeviceGetProperty(dev, 0, iscapture,
   110                                       kAudioDevicePropertyStreamConfiguration,
   111                                       &size, buflist);
   112 
   113         if (result == noErr) {
   114             UInt32 j;
   115             for (j = 0; j < buflist->mNumberBuffers; j++) {
   116                 if (buflist->mBuffers[j].mNumberChannels > 0) {
   117                     usable = 1;
   118                     break;
   119                 }
   120             }
   121         }
   122 
   123         SDL_free(buflist);
   124 
   125         if (!usable)
   126             continue;
   127 
   128         size = sizeof (CFStringRef);
   129         result = AudioDeviceGetProperty(dev, 0, iscapture,
   130                                         kAudioObjectPropertyName,
   131                                         &size, &cfstr);
   132 
   133         if (result != kAudioHardwareNoError)
   134             continue;
   135 
   136         len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
   137                                                 kCFStringEncodingUTF8);
   138 
   139         ptr = (char *) SDL_malloc(len + 1);
   140         usable = ( (ptr != NULL) &&
   141                 (CFStringGetCString(cfstr,ptr,len+1,kCFStringEncodingUTF8)) );
   142 
   143         CFRelease(cfstr);
   144 
   145         if (usable) {
   146             len = strlen(ptr);
   147             /* Some devices have whitespace at the end...trim it. */
   148             while ((len > 0) && (ptr[len-1] == ' ')) {
   149                 len--;
   150             }
   151             usable = (len > 0);
   152         }
   153 
   154         if (!usable) {
   155             SDL_free(ptr);
   156         } else {
   157             ptr[len] = '\0';
   158 
   159             #if DEBUG_COREAUDIO
   160             printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
   161                     ((iscapture) ? "capture" : "output"),
   162                     (int) *devCount, ptr, (int) dev);
   163             #endif
   164 
   165             (*devices)[*devCount].id = dev;
   166             (*devices)[*devCount].name = ptr;
   167             (*devCount)++;
   168         }
   169     }
   170 }
   171 
   172 static inline void
   173 build_device_lists(void)
   174 {
   175     build_device_list(0, &outputDevices, &outputDeviceCount);
   176     build_device_list(1, &inputDevices, &inputDeviceCount);
   177 }
   178 
   179 
   180 static inline void
   181 free_device_lists(void)
   182 {
   183     free_device_list(&outputDevices, &outputDeviceCount);
   184     free_device_list(&inputDevices, &inputDeviceCount);
   185 }
   186 
   187 
   188 static int
   189 find_device_id(const char *devname, int iscapture, AudioDeviceID *id)
   190 {
   191     int i = ((iscapture) ? inputDeviceCount : outputDeviceCount);
   192     COREAUDIO_DeviceList *devs = ((iscapture) ? inputDevices : outputDevices);
   193     while (i--) {
   194         if (SDL_strcmp(devname, devs->name) == 0) {
   195             *id = devs->id;
   196             return 1;
   197         }
   198         devs++;
   199     }
   200 
   201     return 0;
   202 }
   203 
   204 
   205 /* Audio driver functions */
   206 
   207 static int COREAUDIO_DetectDevices(int iscapture);
   208 static const char *COREAUDIO_GetDeviceName(int index, int iscapture);
   209 static int COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture);
   210 static void COREAUDIO_WaitDevice(_THIS);
   211 static void COREAUDIO_PlayDevice(_THIS);
   212 static Uint8 *COREAUDIO_GetDeviceBuf(_THIS);
   213 static void COREAUDIO_CloseDevice(_THIS);
   214 static void COREAUDIO_Deinitialize(void);
   215 
   216 /* Audio driver bootstrap functions */
   217 
   218 static int
   219 COREAUDIO_Available(void)
   220 {
   221     return 1;  /* always available on Mac OS X. */
   222 }
   223 
   224 static int
   225 COREAUDIO_Init(SDL_AudioDriverImpl *impl)
   226 {
   227     /* Set the function pointers */
   228     impl->DetectDevices = COREAUDIO_DetectDevices;
   229     impl->GetDeviceName = COREAUDIO_GetDeviceName;
   230     impl->OpenDevice = COREAUDIO_OpenDevice;
   231     impl->CloseDevice = COREAUDIO_CloseDevice;
   232     impl->Deinitialize = COREAUDIO_Deinitialize;
   233     impl->ProvidesOwnCallbackThread = 1;
   234 
   235     build_device_lists();  /* do an initial check for devices... */
   236 
   237     return 1;
   238 }
   239 
   240 AudioBootStrap COREAUDIO_bootstrap = {
   241     "coreaudio", "Mac OS X CoreAudio",
   242     COREAUDIO_Available, COREAUDIO_Init, 0
   243 };
   244 
   245 
   246 static int
   247 COREAUDIO_DetectDevices(int iscapture)
   248 {
   249     if (iscapture) {
   250         build_device_list(1, &inputDevices, &inputDeviceCount);
   251         return inputDeviceCount;
   252     } else {
   253         build_device_list(0, &outputDevices, &outputDeviceCount);
   254         return outputDeviceCount;
   255     }
   256 
   257     return 0;  /* shouldn't ever hit this. */
   258 }
   259 
   260 
   261 static const char *
   262 COREAUDIO_GetDeviceName(int index, int iscapture)
   263 {
   264     if ((iscapture) && (index < inputDeviceCount)) {
   265         return inputDevices[index].name;
   266     } else if ((!iscapture) && (index < outputDeviceCount)) {
   267         return outputDevices[index].name;
   268     }
   269 
   270     SDL_SetError("No such device");
   271     return NULL;
   272 }
   273 
   274 
   275 static void
   276 COREAUDIO_Deinitialize(void)
   277 {
   278     free_device_lists();
   279 }
   280 
   281 
   282 /* The CoreAudio callback */
   283 static OSStatus
   284 outputCallback(void *inRefCon,
   285               AudioUnitRenderActionFlags *ioActionFlags,
   286               const AudioTimeStamp * inTimeStamp,
   287               UInt32 inBusNumber, UInt32 inNumberFrames,
   288               AudioBufferList *ioDataList)
   289 {
   290     SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
   291     AudioBuffer *ioData = &ioDataList->mBuffers[0];
   292     UInt32 remaining, len;
   293     void *ptr;
   294 
   295     /*
   296      * !!! FIXME: I'm not sure if you can ever have more than one
   297      *            buffer, or what this signifies, or what to do with it...
   298      */
   299     if (ioDataList->mNumberBuffers != 1) {
   300         return noErr;
   301     }
   302 
   303     /* Only do anything if audio is enabled and not paused */
   304     if (!this->enabled || this->paused) {
   305         SDL_memset(ioData->mData, this->spec.silence, ioData->mDataByteSize);
   306         return 0;
   307     }
   308 
   309     /* No SDL conversion should be needed here, ever, since we accept
   310        any input format in OpenAudio, and leave the conversion to CoreAudio.
   311      */
   312     /*
   313        assert(!this->convert.needed);
   314        assert(this->spec.channels == ioData->mNumberChannels);
   315      */
   316 
   317     remaining = ioData->mDataByteSize;
   318     ptr = ioData->mData;
   319     while (remaining > 0) {
   320         if (this->hidden->bufferOffset >= this->hidden->bufferSize) {
   321             /* Generate the data */
   322             SDL_memset(this->hidden->buffer, this->spec.silence,
   323                        this->hidden->bufferSize);
   324             SDL_mutexP(this->mixer_lock);
   325             (*this->spec.callback) (this->spec.userdata, this->hidden->buffer,
   326                                     this->hidden->bufferSize);
   327             SDL_mutexV(this->mixer_lock);
   328             this->hidden->bufferOffset = 0;
   329         }
   330 
   331         len = this->hidden->bufferSize - this->hidden->bufferOffset;
   332         if (len > remaining)
   333             len = remaining;
   334         SDL_memcpy(ptr,
   335                     (char *) this->hidden->buffer + this->hidden->bufferOffset,
   336                     len);
   337         ptr = (char *) ptr + len;
   338         remaining -= len;
   339         this->hidden->bufferOffset += len;
   340     }
   341 
   342     return 0;
   343 }
   344 
   345 static OSStatus
   346 inputCallback(void *inRefCon,
   347               AudioUnitRenderActionFlags *ioActionFlags,
   348               const AudioTimeStamp * inTimeStamp,
   349               UInt32 inBusNumber, UInt32 inNumberFrames,
   350               AudioBufferList *ioData)
   351 {
   352     //err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer);
   353     // !!! FIXME: write me!
   354     return noErr;
   355 }
   356 
   357 
   358 static void
   359 COREAUDIO_CloseDevice(_THIS)
   360 {
   361     if (this->hidden != NULL) {
   362         OSStatus result = noErr;
   363         AURenderCallbackStruct callback;
   364         const AudioUnitElement output_bus = 0;
   365         const AudioUnitElement input_bus = 1;
   366         const int iscapture = this->iscapture;
   367         const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
   368         const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
   369                                                     kAudioUnitScope_Input);
   370 
   371         /* stop processing the audio unit */
   372         result = AudioOutputUnitStop(this->hidden->audioUnit);
   373 
   374         /* Remove the input callback */
   375         memset(&callback, '\0', sizeof (AURenderCallbackStruct));
   376         result = AudioUnitSetProperty(this->hidden->audioUnit,
   377                                       kAudioUnitProperty_SetRenderCallback,
   378                                       scope, bus, &callback, sizeof (callback));
   379 
   380         CloseComponent(this->hidden->audioUnit);
   381 
   382         SDL_free(this->hidden->buffer);
   383         SDL_free(this->hidden);
   384         this->hidden = NULL;
   385     }
   386 }
   387 
   388 
   389 #define CHECK_RESULT(msg) \
   390     if (result != noErr) { \
   391         COREAUDIO_CloseDevice(this); \
   392         SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
   393         return 0; \
   394     }
   395 
   396 static int
   397 find_device_by_name(_THIS, const char *devname, int iscapture)
   398 {
   399     AudioDeviceID devid = 0;
   400     OSStatus result = noErr;
   401     UInt32 size = 0;
   402     UInt32 alive = 0;
   403     pid_t pid = 0;
   404 
   405     if (devname == NULL) {
   406         size = sizeof (AudioDeviceID);
   407         const AudioHardwarePropertyID propid =
   408                     ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
   409                                    kAudioHardwarePropertyDefaultOutputDevice);
   410 
   411         result = AudioHardwareGetProperty(propid, &size, &devid);
   412         CHECK_RESULT("AudioHardwareGetProperty (default device)");
   413     } else {
   414         if (!find_device_id(devname, iscapture, &devid)) {
   415             SDL_SetError("CoreAudio: No such audio device.");
   416             return 0;
   417         }
   418     }
   419 
   420     size = sizeof (alive);
   421     result = AudioDeviceGetProperty(devid, 0, iscapture,
   422                                     kAudioDevicePropertyDeviceIsAlive,
   423                                     &size, &alive);
   424     CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
   425 
   426     if (!alive) {
   427         SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
   428         return 0;
   429     }
   430 
   431     size = sizeof (pid);
   432     result = AudioDeviceGetProperty(devid, 0, iscapture,
   433                                     kAudioDevicePropertyHogMode, &size, &pid);
   434 
   435     /* some devices don't support this property, so errors are fine here. */
   436     if ((result == noErr) && (pid != -1)) {
   437         SDL_SetError("CoreAudio: requested device is being hogged.");
   438         return 0;
   439     }
   440 
   441     this->hidden->deviceID = devid;
   442     return 1;
   443 }
   444 
   445 
   446 static int
   447 prepare_audiounit(_THIS, const char *devname, int iscapture,
   448                   const AudioStreamBasicDescription *strdesc)
   449 {
   450     OSStatus result = noErr;
   451     AURenderCallbackStruct callback;
   452     ComponentDescription desc;
   453     Component comp = NULL;
   454     int use_system_device = 0;
   455     UInt32 enableIO = 0;
   456     const AudioUnitElement output_bus = 0;
   457     const AudioUnitElement input_bus = 1;
   458     const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
   459     const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
   460                                                 kAudioUnitScope_Input);
   461 
   462     if (!find_device_by_name(this, devname, iscapture)) {
   463         SDL_SetError("Couldn't find requested CoreAudio device");
   464         return 0;
   465     }
   466 
   467     memset(&desc, '\0', sizeof(ComponentDescription));
   468     desc.componentType = kAudioUnitType_Output;
   469     desc.componentSubType = kAudioUnitSubType_HALOutput;
   470     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   471 
   472     comp = FindNextComponent(NULL, &desc);
   473     if (comp == NULL) {
   474         SDL_SetError("Couldn't find requested CoreAudio component");
   475         return 0;
   476     }
   477 
   478     /* Open & initialize the audio unit */
   479     result = OpenAComponent(comp, &this->hidden->audioUnit);
   480     CHECK_RESULT("OpenAComponent");
   481 
   482     // !!! FIXME: this is wrong?
   483     enableIO = ((iscapture) ? 1 : 0);
   484     result = AudioUnitSetProperty(this->hidden->audioUnit,
   485                                   kAudioOutputUnitProperty_EnableIO,
   486                                   kAudioUnitScope_Input, input_bus,
   487                                   &enableIO, sizeof (enableIO));
   488     CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_EnableIO input)");
   489 
   490     // !!! FIXME: this is wrong?
   491     enableIO = ((iscapture) ? 0 : 1);
   492     result = AudioUnitSetProperty(this->hidden->audioUnit,
   493                                   kAudioOutputUnitProperty_EnableIO,
   494                                   kAudioUnitScope_Output, output_bus,
   495                                   &enableIO, sizeof (enableIO));
   496     CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_EnableIO output)");
   497 
   498     result = AudioUnitSetProperty(this->hidden->audioUnit,
   499                                   kAudioOutputUnitProperty_CurrentDevice,
   500                                   kAudioUnitScope_Global, 0,
   501                                   &this->hidden->deviceID,
   502                                   sizeof (AudioDeviceID));
   503     CHECK_RESULT("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
   504 
   505     /* Set the data format of the audio unit. */
   506     result = AudioUnitSetProperty(this->hidden->audioUnit,
   507                                   kAudioUnitProperty_StreamFormat,
   508                                   scope, bus, strdesc, sizeof (*strdesc));
   509     CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
   510 
   511     /* Set the audio callback */
   512     memset(&callback, '\0', sizeof (AURenderCallbackStruct));
   513     callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
   514     callback.inputProcRefCon = this;
   515     result = AudioUnitSetProperty(this->hidden->audioUnit,
   516                                   kAudioUnitProperty_SetRenderCallback,
   517                                   scope, bus, &callback, sizeof (callback));
   518     CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_SetInputCallback)");
   519 
   520     /* Calculate the final parameters for this audio specification */
   521     SDL_CalculateAudioSpec(&this->spec);
   522 
   523     /* Allocate a sample buffer */
   524     this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
   525     this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
   526 
   527     result = AudioUnitInitialize(this->hidden->audioUnit);
   528     CHECK_RESULT("AudioUnitInitialize");
   529 
   530     /* Finally, start processing of the audio unit */
   531     result = AudioOutputUnitStart(this->hidden->audioUnit);
   532     CHECK_RESULT("AudioOutputUnitStart");
   533 
   534     /* We're running! */
   535     return 1;
   536 }
   537 
   538 
   539 static int
   540 COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
   541 {
   542     AudioStreamBasicDescription strdesc;
   543     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   544     int valid_datatype = 0;
   545 
   546     /* Initialize all variables that we clean on shutdown */
   547     this->hidden = (struct SDL_PrivateAudioData *)
   548                         SDL_malloc((sizeof *this->hidden));
   549     if (this->hidden == NULL) {
   550         SDL_OutOfMemory();
   551         return (0);
   552     }
   553     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   554 
   555     /* Setup a AudioStreamBasicDescription with the requested format */
   556     memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
   557     strdesc.mFormatID = kAudioFormatLinearPCM;
   558     strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
   559     strdesc.mChannelsPerFrame = this->spec.channels;
   560     strdesc.mSampleRate = this->spec.freq;
   561     strdesc.mFramesPerPacket = 1;
   562 
   563     while ((!valid_datatype) && (test_format)) {
   564         this->spec.format = test_format;
   565         /* Just a list of valid SDL formats, so people don't pass junk here. */
   566         switch (test_format) {
   567         case AUDIO_U8:
   568         case AUDIO_S8:
   569         case AUDIO_U16LSB:
   570         case AUDIO_S16LSB:
   571         case AUDIO_U16MSB:
   572         case AUDIO_S16MSB:
   573         case AUDIO_S32LSB:
   574         case AUDIO_S32MSB:
   575         case AUDIO_F32LSB:
   576         case AUDIO_F32MSB:
   577             valid_datatype = 1;
   578             strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
   579             if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
   580                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
   581 
   582             if (SDL_AUDIO_ISFLOAT(this->spec.format))
   583                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
   584             else if (SDL_AUDIO_ISSIGNED(this->spec.format))
   585                 strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
   586             break;
   587         }
   588     }
   589 
   590     if (!valid_datatype) {      /* shouldn't happen, but just in case... */
   591         SDL_SetError("Unsupported audio format");
   592         return 0;
   593     }
   594 
   595     strdesc.mBytesPerFrame =
   596         strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
   597     strdesc.mBytesPerPacket =
   598         strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
   599 
   600     if (!prepare_audiounit(this, devname, iscapture, &strdesc)) {
   601         return 0;  /* prepare_audiounit() will call SDL_SetError()... */
   602     }
   603 
   604     return 1;  /* good to go. */
   605 }
   606 
   607 /* vi: set ts=4 sw=4 expandtab: */