ref: e60dbd620fdf1ca426d1ff12b212aef45be4d3c2
dir: /src/coreaudio.c/
/* AudioCore sound handler
 *
 * Copyright 2008 Chris Bagwell And Sundry Contributors
 */
#include "sox_i.h"
#include <CoreAudio/CoreAudio.h>
#include <pthread.h>
#define Buffactor 4
typedef struct {
  AudioDeviceID adid;
  pthread_mutex_t mutex;
  pthread_cond_t cond;
  int device_started;
  size_t bufsize;
  size_t bufrd;
  size_t bufwr;
  size_t bufrdavail;
  float *buf;
} priv_t;
static OSStatus PlaybackIOProc(AudioDeviceID inDevice UNUSED,
                               const AudioTimeStamp *inNow UNUSED,
                               const AudioBufferList *inInputData UNUSED,
                               const AudioTimeStamp *inInputTime UNUSED,
                               AudioBufferList *outOutputData,
                               const AudioTimeStamp *inOutputTime UNUSED,
                               void *inClientData)
{
    priv_t *ac = (priv_t*)((sox_format_t*)inClientData)->priv;
    AudioBuffer *buf;
    size_t copylen, avail;
    pthread_mutex_lock(&ac->mutex);
    for(buf = outOutputData->mBuffers;
        buf != outOutputData->mBuffers + outOutputData->mNumberBuffers;
        buf++){
        copylen = buf->mDataByteSize / sizeof(float);
        if(copylen > ac->bufrdavail)
            copylen = ac->bufrdavail;
        avail = ac->bufsize - ac->bufrd;
        if(buf->mData == NULL){
            /*do nothing-hardware can't play audio*/
        }else if(copylen > avail){
            memcpy(buf->mData, ac->buf + ac->bufrd, avail * sizeof(float));
            memcpy((float*)buf->mData + avail, ac->buf, (copylen - avail) * sizeof(float));
        }else{
            memcpy(buf->mData, ac->buf + ac->bufrd, copylen * sizeof(float));
        }
        buf->mDataByteSize = copylen * sizeof(float);
        ac->bufrd += copylen;
        if(ac->bufrd >= ac->bufsize)
            ac->bufrd -= ac->bufsize;
        ac->bufrdavail -= copylen;
    }
    pthread_cond_signal(&ac->cond);
    pthread_mutex_unlock(&ac->mutex);
    return kAudioHardwareNoError;
}
static OSStatus RecIOProc(AudioDeviceID inDevice UNUSED,
                          const AudioTimeStamp *inNow UNUSED,
                          const AudioBufferList *inInputData,
                          const AudioTimeStamp *inInputTime UNUSED,
                          AudioBufferList *outOutputData UNUSED,
                          const AudioTimeStamp *inOutputTime UNUSED,
                          void *inClientData)
{
    priv_t *ac = (priv_t *)((sox_format_t*)inClientData)->priv;
    AudioBuffer const *buf;
    size_t nfree, copylen, avail;
    pthread_mutex_lock(&ac->mutex);
    for(buf = inInputData->mBuffers;
        buf != inInputData->mBuffers + inInputData->mNumberBuffers;
        buf++){
        if(buf->mData == NULL)
            continue;
        copylen = buf->mDataByteSize / sizeof(float);
        nfree = ac->bufsize - ac->bufrdavail - 1;
        if(nfree == 0)
            lsx_warn("coreaudio: unhandled buffer overrun.  Data discarded.");
        if(copylen > nfree)
            copylen = nfree;
        avail = ac->bufsize - ac->bufwr;
        if(copylen > avail){
            memcpy(ac->buf + ac->bufwr, buf->mData, avail * sizeof(float));
            memcpy(ac->buf, (float*)buf->mData + avail, (copylen - avail) * sizeof(float));
        }else{
            memcpy(ac->buf + ac->bufwr, buf->mData, copylen * sizeof(float));
        }
        ac->bufwr += copylen;
        if(ac->bufwr >= ac->bufsize)
            ac->bufwr -= ac->bufsize;
        ac->bufrdavail += copylen;
    }
    pthread_cond_signal(&ac->cond);
    pthread_mutex_unlock(&ac->mutex);
    return kAudioHardwareNoError;
}
static int setup(sox_format_t *ft, int is_input)
{
  priv_t *ac = (priv_t *)ft->priv;
  OSStatus status;
  UInt32 property_size;
  struct AudioStreamBasicDescription stream_desc;
  int32_t buf_size;
  int rc;
  if (strncmp(ft->filename, "default", (size_t)7) == 0)
  {
      property_size = sizeof(ac->adid);
      if (is_input)
          status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &property_size, &ac->adid);
      else
          status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &property_size, &ac->adid);
  }
  else
  {
      Boolean is_writable;
      status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &property_size, &is_writable);
      if (status == noErr)
      {
          int device_count = property_size/sizeof(AudioDeviceID);
          AudioDeviceID *devices;
          devices = malloc(property_size);
              status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &property_size, devices);
          if (status == noErr)
          {
              int i;
              for (i = 0; i < device_count; i++)
              {
                  char name[256];
                  status = AudioDeviceGetProperty(devices[i],0,false,kAudioDevicePropertyDeviceName,&property_size,&name);
                  lsx_report("Found Audio Device \"%s\"\n",name);
                  /* String returned from OS is truncated so only compare
                   * as much as returned.
                   */
                  if (strncmp(name,ft->filename,strlen(name)) == 0)
                  {
                      ac->adid = devices[i];
                      break;
                  }
              }
          }
          free(devices);
      }
  }
  if (status || ac->adid == kAudioDeviceUnknown)
  {
    lsx_fail_errno(ft, SOX_EPERM, "can not open audio device");
    return SOX_EOF;
  }
  /* Query device to get initial values */
  property_size = sizeof(struct AudioStreamBasicDescription);
  status = AudioDeviceGetProperty(ac->adid, 0, is_input,
                                  kAudioDevicePropertyStreamFormat,
                                  &property_size, &stream_desc);
  if (status)
  {
    lsx_fail_errno(ft, SOX_EPERM, "can not get audio device properties");
    return SOX_EOF;
  }
  if (!(stream_desc.mFormatFlags & kLinearPCMFormatFlagIsFloat))
  {
    lsx_fail_errno(ft, SOX_EPERM, "audio device does not accept floats");
    return SOX_EOF;
  }
  /* OS X effectively only supports these values. */
  ft->signal.channels = 2;
  ft->signal.rate = 44100;
  ft->encoding.bits_per_sample = 32;
  /* TODO: My limited experience with hardware can only get floats working
   * withh a fixed sample rate and stereo.  I know that is a limitiation of
   * audio device I have so this may not be standard operating orders.
   * If some hardware supports setting sample rates and channel counts
   * then should do that over resampling and mixing.
   */
#if  0
  stream_desc.mSampleRate = ft->signal.rate;
  stream_desc.mChannelsPerFrame = ft->signal.channels;
  /* Write them back */
  property_size = sizeof(struct AudioStreamBasicDescription);
  status = AudioDeviceSetProperty(ac->adid, NULL, 0, is_input,
                                  kAudioDevicePropertyStreamFormat,
                                  property_size, &stream_desc);
  if (status)
  {
    lsx_fail_errno(ft, SOX_EPERM, "can not set audio device properties");
    return SOX_EOF;
  }
  /* Query device to see if it worked */
  property_size = sizeof(struct AudioStreamBasicDescription);
  status = AudioDeviceGetProperty(ac->adid, 0, is_input,
                                  kAudioDevicePropertyStreamFormat,
                                  &property_size, &stream_desc);
  if (status)
  {
    lsx_fail_errno(ft, SOX_EPERM, "can not get audio device properties");
    return SOX_EOF;
  }
#endif
  if (stream_desc.mChannelsPerFrame != ft->signal.channels)
  {
    lsx_debug("audio device did not accept %d channels. Use %d channels instead.", (int)ft->signal.channels,
              (int)stream_desc.mChannelsPerFrame);
    ft->signal.channels = stream_desc.mChannelsPerFrame;
  }
  if (stream_desc.mSampleRate != ft->signal.rate)
  {
    lsx_debug("audio device did not accept %d sample rate. Use %d instead.", (int)ft->signal.rate,
              (int)stream_desc.mSampleRate);
    ft->signal.rate = stream_desc.mSampleRate;
  }
  ac->bufsize = sox_globals.bufsiz / sizeof(sox_sample_t) * Buffactor;
  ac->bufrd = 0;
  ac->bufwr = 0;
  ac->bufrdavail = 0;
  ac->buf = lsx_malloc(ac->bufsize * sizeof(float));
  buf_size = sox_globals.bufsiz / sizeof(sox_sample_t) * sizeof(float);
  property_size = sizeof(buf_size);
  status = AudioDeviceSetProperty(ac->adid, NULL, 0, is_input,
                                  kAudioDevicePropertyBufferSize,
                                  property_size, &buf_size);
  rc = pthread_mutex_init(&ac->mutex, NULL);
  if (rc)
  {
    lsx_fail_errno(ft, SOX_EPERM, "failed initializing mutex");
    return SOX_EOF;
  }
  rc = pthread_cond_init(&ac->cond, NULL);
  if (rc)
  {
    lsx_fail_errno(ft, SOX_EPERM, "failed initializing condition");
    return SOX_EOF;
  }
  ac->device_started = 0;
  /* Registers callback with the device without activating it. */
  if (is_input)
    status = AudioDeviceAddIOProc(ac->adid, RecIOProc, (void *)ft);
  else
    status = AudioDeviceAddIOProc(ac->adid, PlaybackIOProc, (void *)ft);
  return SOX_SUCCESS;
}
static int startread(sox_format_t *ft)
{
    return setup(ft, 1);
}
static size_t read_samples(sox_format_t *ft, sox_sample_t *buf, size_t nsamp)
{
    priv_t *ac = (priv_t *)ft->priv;
    size_t len;
    SOX_SAMPLE_LOCALS;
    if (!ac->device_started) {
        AudioDeviceStart(ac->adid, RecIOProc);
        ac->device_started = 1;
    }
    pthread_mutex_lock(&ac->mutex);
    /* Wait until input buffer has been filled by device driver */
    while (ac->bufrdavail == 0)
        pthread_cond_wait(&ac->cond, &ac->mutex);
    len = 0;
    while(len < nsamp && ac->bufrdavail > 0){
        buf[len] = SOX_FLOAT_32BIT_TO_SAMPLE(ac->buf[ac->bufrd], ft->clips);
        len++;
        ac->bufrd++;
        if(ac->bufrd == ac->bufsize)
            ac->bufrd = 0;
        ac->bufrdavail--;
    }
    pthread_mutex_unlock(&ac->mutex);
    return len;
}
static int stopread(sox_format_t * ft)
{
  priv_t *ac = (priv_t *)ft->priv;
  AudioDeviceStop(ac->adid, RecIOProc);
  AudioDeviceRemoveIOProc(ac->adid, RecIOProc);
  pthread_cond_destroy(&ac->cond);
  pthread_mutex_destroy(&ac->mutex);
  free(ac->buf);
  return SOX_SUCCESS;
}
static int startwrite(sox_format_t * ft)
{
    return setup(ft, 0);
}
static size_t write_samples(sox_format_t *ft, const sox_sample_t *buf, size_t nsamp)
{
    priv_t *ac = (priv_t *)ft->priv;
    size_t i;
    SOX_SAMPLE_LOCALS;
    pthread_mutex_lock(&ac->mutex);
    /* Wait to start until mutex is locked to help prevent callback
    * getting zero samples.
    */
    if(!ac->device_started){
        if(AudioDeviceStart(ac->adid, PlaybackIOProc)){
            pthread_mutex_unlock(&ac->mutex);
            return SOX_EOF;
        }
        ac->device_started = 1;
    }
    /* globals.bufsize is in samples
    * buf_offset is in bytes
    * buf_size is in bytes
    */
    for(i = 0; i < nsamp; i++){
        while(ac->bufrdavail == ac->bufsize - 1)
            pthread_cond_wait(&ac->cond, &ac->mutex);
        ac->buf[ac->bufwr] = SOX_SAMPLE_TO_FLOAT_32BIT(buf[i], ft->clips);
        ac->bufwr++;
        if(ac->bufwr == ac->bufsize)
            ac->bufwr = 0;
        ac->bufrdavail++;
    }
    pthread_mutex_unlock(&ac->mutex);
    return nsamp;
}
static int stopwrite(sox_format_t * ft)
{
    priv_t *ac = (priv_t *)ft->priv;
    if(ac->device_started){
        pthread_mutex_lock(&ac->mutex);
        while (ac->bufrdavail > 0)
            pthread_cond_wait(&ac->cond, &ac->mutex);
        pthread_mutex_unlock(&ac->mutex);
        AudioDeviceStop(ac->adid, PlaybackIOProc);
    }
    AudioDeviceRemoveIOProc(ac->adid, PlaybackIOProc);
    pthread_cond_destroy(&ac->cond);
    pthread_mutex_destroy(&ac->mutex);
    free(ac->buf);
    return SOX_SUCCESS;
}
LSX_FORMAT_HANDLER(coreaudio)
{
  static char const *const names[] = { "coreaudio", NULL };
  static unsigned const write_encodings[] = {
    SOX_ENCODING_FLOAT, 32, 0,
    0};
  static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
    "Mac AudioCore device driver",
    names, SOX_FILE_DEVICE | SOX_FILE_NOSTDIO,
    startread, read_samples, stopread,
    startwrite, write_samples, stopwrite,
    NULL, write_encodings, NULL, sizeof(priv_t)
  };
  return &handler;
}