shithub: sox

ref: a30de9ea3744f99cbc73148ea98dbcd4f048ff6f
dir: /src/alsa.c/

View raw version
/* libSoX device driver: ALSA   (c) 2006-9 SoX contributors
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "sox_i.h"
#include <alsa/asoundlib.h>

typedef struct {
  snd_pcm_uframes_t  buf_len, period;
  snd_pcm_t          * pcm;
  char               * buf;
  int                format;
} priv_t;

static const int bytes_size[] = {1, 2, 4}, encs[][3] = {
  {SND_PCM_FORMAT_S8, SND_PCM_FORMAT_S16, SND_PCM_FORMAT_S24},
  {SND_PCM_FORMAT_U8, SND_PCM_FORMAT_U16, SND_PCM_FORMAT_U24}};
#define NBYTES bytes_size[(ft->encoding.bits_per_sample >> 3) - 1]

static int select_format(sox_encoding_t * e, unsigned * bits, snd_pcm_format_mask_t const * mask, int * format)
{
  unsigned does[3], i, j, k = (*bits >> 3) - 1;
  
  if (k > 2 || (*e != SOX_ENCODING_SIGN2 && *e != SOX_ENCODING_UNSIGNED))
    return -1;
  for (i = 0; i < 3; ++i) for (does[i] = 0, j = 0; j < 2; ++j)
    does[i] |= snd_pcm_format_mask_test(mask, encs[j][i]);
  if (!does[k])
    k = 1;
  while (!does[k]) if ((k = (k + 1 ) % 3) == 1)
    return -1;
  *bits = (k + 1) << 3;

  if (*e == SOX_ENCODING_SIGN2 && !snd_pcm_format_mask_test(mask, encs[0][k]))
    *e = SOX_ENCODING_UNSIGNED;
  *format = encs[*e == SOX_ENCODING_UNSIGNED][k];
  return 0;
}

#define _(x,y) do {if ((err = x y) < 0) {lsx_fail_errno(ft, SOX_EPERM, #x " error: %s", snd_strerror(err)); goto error;} } while (0)
static int setup(sox_format_t * ft)
{
  priv_t                 * p = (priv_t *)ft->priv;
  snd_pcm_hw_params_t    * params = NULL;
  snd_pcm_format_mask_t  * mask = NULL;
  snd_pcm_uframes_t      min, max;
  unsigned               n;
  int                    err;

  _(snd_pcm_open, (&p->pcm, ft->filename, ft->mode == 'r'? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0));
  _(snd_pcm_hw_params_malloc, (&params));
  _(snd_pcm_hw_params_any, (p->pcm, params));
#if SND_LIB_VERSION >= 0x010009               /* Disable alsa-lib resampling: */
  _(snd_pcm_hw_params_set_rate_resample, (p->pcm, params, 0));
#endif
  _(snd_pcm_hw_params_set_access, (p->pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED));

  _(snd_pcm_format_mask_malloc, (&mask));           /* Set format: */
  snd_pcm_hw_params_get_format_mask(params, mask);
  _(select_format, (&ft->encoding.encoding, &ft->encoding.bits_per_sample, mask, &p->format));
  _(snd_pcm_hw_params_set_format, (p->pcm, params, p->format));
  snd_pcm_format_mask_free(mask), mask = NULL;

  n = ft->signal.rate;                              /* Set rate: */
  _(snd_pcm_hw_params_set_rate_near, (p->pcm, params, &n, 0));
  ft->signal.rate = n;

  n = ft->signal.channels;                          /* Set channels: */
  _(snd_pcm_hw_params_set_channels_near, (p->pcm, params, &n));
  ft->signal.channels = n;

  /* Set buf_len > > sox_globals.bufsiz for no underrun: */
  p->buf_len = sox_globals.bufsiz * 8 / NBYTES / ft->signal.channels;
  _(snd_pcm_hw_params_get_buffer_size_min, (params, &min));
  _(snd_pcm_hw_params_get_buffer_size_max, (params, &max));
  p->period = range_limit(p->buf_len, min, max) / 8;
  p->buf_len = p->period * 8;
  _(snd_pcm_hw_params_set_period_size_near, (p->pcm, params, &p->period, 0));
  _(snd_pcm_hw_params_set_buffer_size_near, (p->pcm, params, &p->buf_len));
  if (p->period * 2 > p->buf_len) {
    lsx_fail_errno(ft, SOX_EPERM, "buffer too small");
    goto error;
  }

  _(snd_pcm_hw_params, (p->pcm, params));           /* Configure ALSA */
  snd_pcm_hw_params_free(params), params = NULL;
  _(snd_pcm_prepare, (p->pcm));
  p->buf_len *= ft->signal.channels;                /* No longer in `frames' */
  p->buf = lsx_malloc(p->buf_len * NBYTES);
  return SOX_SUCCESS;

error:
  if (mask) snd_pcm_format_mask_free(mask);
  if (params) snd_pcm_hw_params_free(params);
  return SOX_EOF;
}

static int recover(sox_format_t * ft, snd_pcm_t * pcm, int err)
{
  if (err == -EPIPE)
    lsx_warn("%s-run", ft->mode == 'r'? "over" : "under");
  else if (err != -ESTRPIPE)
    lsx_warn("%s", snd_strerror(err));
  else while ((err = snd_pcm_resume(pcm)) == -EAGAIN) {
    lsx_report("suspended");
    sleep(1);                  /* Wait until the suspend flag is released */
  }
  if (err < 0 && (err = snd_pcm_prepare(pcm)) < 0)
    lsx_fail_errno(ft, SOX_EPERM, "%s", snd_strerror(err));
  return err;
}

static size_t read_(sox_format_t * ft, sox_sample_t * buf, size_t len)
{
  priv_t             * p = (priv_t *)ft->priv;
  snd_pcm_sframes_t  i, n;
  size_t             done;

  len = min(len, p->buf_len);
  for (done = 0; done < len; done += n) {
    do {
      n = snd_pcm_readi(p->pcm, p->buf, (len - done) / ft->signal.channels);
      if (n < 0 && recover(ft, p->pcm, (int)n) < 0)
        return 0;
    } while (n <= 0);

    i = n *= ft->signal.channels;
    switch (p->format) {
      case SND_PCM_FORMAT_S8: {
        int8_t * buf1 = (int8_t *)p->buf;
        while (i--) *buf++ = SOX_SIGNED_8BIT_TO_SAMPLE(*buf1++,);
        break;
      }
      case SND_PCM_FORMAT_U8: {
        uint8_t * buf1 = (uint8_t *)p->buf;
        while (i--) *buf++ = SOX_UNSIGNED_8BIT_TO_SAMPLE(*buf1++,);
        break;
      }
      case SND_PCM_FORMAT_S16: {
        int16_t * buf1 = (int16_t *)p->buf;
        if (ft->encoding.reverse_bytes) while (i--)
          *buf++ = SOX_SIGNED_16BIT_TO_SAMPLE(lsx_swapw(*buf1++),);
        else
          while (i--) *buf++ = SOX_SIGNED_16BIT_TO_SAMPLE(*buf1++,);
        break;
      }
      case SND_PCM_FORMAT_U16: {
        uint16_t * buf1 = (uint16_t *)p->buf;
        if (ft->encoding.reverse_bytes) while (i--)
          *buf++ = SOX_UNSIGNED_16BIT_TO_SAMPLE(lsx_swapw(*buf1++),);
        else
          while (i--) *buf++ = SOX_UNSIGNED_16BIT_TO_SAMPLE(*buf1++,);
        break;
      }
      case SND_PCM_FORMAT_S24: {
        int24_t * buf1 = (int24_t *)p->buf;
        while (i--) *buf++ = SOX_SIGNED_24BIT_TO_SAMPLE(*buf1++,);
        break;
      }
      case SND_PCM_FORMAT_U24: {
        uint24_t * buf1 = (uint24_t *)p->buf;
        while (i--) *buf++ = SOX_UNSIGNED_24BIT_TO_SAMPLE(*buf1++,);
        break;
      }
      default: lsx_fail_errno(ft, SOX_EFMT, "invalid format");
        return 0;
    }
  }
  return len;
}

static size_t write_(sox_format_t * ft, sox_sample_t const * buf, size_t len)
{
  priv_t             * p = (priv_t *)ft->priv;
  size_t             done, i, n;
  snd_pcm_sframes_t  actual;
  SOX_SAMPLE_LOCALS;

  for (done = 0; done < len; done += n) {
    i = n = min(len - done, p->buf_len);
    switch (p->format) {
      case SND_PCM_FORMAT_S8: {
        int8_t * buf1 = (int8_t *)p->buf;
        while (i--) *buf1++ = SOX_SAMPLE_TO_SIGNED_8BIT(*buf++, ft->clips);
        break;
      }
      case SND_PCM_FORMAT_U8: {
        uint8_t * buf1 = (uint8_t *)p->buf;
        while (i--) *buf1++ = SOX_SAMPLE_TO_UNSIGNED_8BIT(*buf++, ft->clips);
        break;
      }
      case SND_PCM_FORMAT_S16: {
        int16_t * buf1 = (int16_t *)p->buf;
        if (ft->encoding.reverse_bytes) while (i--)
          *buf1++ = lsx_swapw(SOX_SAMPLE_TO_SIGNED_16BIT(*buf++, ft->clips));
        else
          while (i--) *buf1++ = SOX_SAMPLE_TO_SIGNED_16BIT(*buf++, ft->clips);
        break;
      }
      case SND_PCM_FORMAT_U16: {
        uint16_t * buf1 = (uint16_t *)p->buf;
        if (ft->encoding.reverse_bytes) while (i--)
          *buf1++ = lsx_swapw(SOX_SAMPLE_TO_UNSIGNED_16BIT(*buf++, ft->clips));
        else
          while (i--) *buf1++ = SOX_SAMPLE_TO_UNSIGNED_16BIT(*buf++, ft->clips);
        break;
      }
      case SND_PCM_FORMAT_S24: {
        int24_t * buf1 = (int24_t *)p->buf;
        while (i--) *buf1++ = SOX_SAMPLE_TO_SIGNED_24BIT(*buf++, ft->clips);
        break;
      }
      case SND_PCM_FORMAT_U24: {
        uint24_t * buf1 = (uint24_t *)p->buf;
        while (i--) *buf1++ = SOX_SAMPLE_TO_UNSIGNED_24BIT(*buf++, ft->clips);
        break;
      }
    }
    for (i = 0; i < n; i += actual * ft->signal.channels) do {
      actual = snd_pcm_writei(
          p->pcm, p->buf + i * NBYTES, (n - i) / ft->signal.channels);
      if (errno == EAGAIN)     /* Happens naturally; don't report it: */
        errno = 0;
      if (actual < 0 && recover(ft, p->pcm, (int)actual) < 0)
        return 0;
    } while (actual < 0);
  }
  return len;
}

static int stop(sox_format_t * ft)
{
  priv_t * p = (priv_t *)ft->priv;
  snd_pcm_close(p->pcm);
  free(p->buf);
  return SOX_SUCCESS;
}

static int stop_write(sox_format_t * ft)
{
  priv_t * p = (priv_t *)ft->priv;
  size_t n = ft->signal.channels * p->period, npad = n - (ft->olength % n);
  sox_sample_t * buf = lsx_calloc(npad, sizeof(*buf)); /* silent samples */

  if (npad != n)                      /* pad to hardware period: */
    write_(ft, buf, npad);
  free(buf);
  snd_pcm_drain(p->pcm);
  return stop(ft);
}

LSX_FORMAT_HANDLER(alsa)
{
  static char const * const names[] = {"alsa", NULL};
  static unsigned const write_encodings[] = {
    SOX_ENCODING_SIGN2, 24, 16, 8, 0, SOX_ENCODING_UNSIGNED, 24, 16, 8, 0, 0};
  static sox_format_handler_t const handler = {SOX_LIB_VERSION_CODE,
    "Advanced Linux Sound Architecture device driver",
    names, SOX_FILE_DEVICE | SOX_FILE_NOSTDIO,
    setup, read_, stop, setup, write_, stop_write,
    NULL, write_encodings, NULL, sizeof(priv_t)
  };
  return &handler;
}