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