shithub: sox

Download patch

ref: 06873db08ba79afeb31d5d490d24fb2f043d5d15
parent: 6ada3918c43f59d76d2e0a5e29c2564c15778a0a
author: robs <robs>
date: Sun Jan 4 10:17:19 EST 2009

rewrite alsa; support 24-bit

--- a/ChangeLog
+++ b/ChangeLog
@@ -41,6 +41,8 @@
 
 File formats:
 
+  o 24-bit support for ALSA driver.  (robs)
+  o Warn if ALSA under/overrun.  (robs)
   o Slight improvement to A-law/u-law conversion accuracy: round LSB
     instead of truncating.  (robs)
   o New `--ignore-length' option to ignore length in input file
--- a/src/alsa.c
+++ b/src/alsa.c
@@ -1,678 +1,276 @@
-/* ALSA sound handler
+/* libSoX device driver: ALSA   (c) 2006-9 SoX contributors
  *
- * Copyright 1997-2005 Jimen Ching And Sundry Contributors
- * This source code is freely redistributable and may be used for
- * any purpose.  This copyright notice must be maintained.
- * Jimen Ching And Sundry Contributors are not
- * responsible for the consequences of using this software.
+ * 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_t *pcm_handle;
-  char *buf;
-  size_t buf_size;
-  snd_pcm_uframes_t period_size;
-  snd_pcm_uframes_t frames_this_period;
+  snd_pcm_uframes_t  buf_len, period;
+  snd_pcm_t          * pcm;
+  char               * buf;
+  int                format;
 } priv_t;
 
-static int get_format(sox_format_t * ft, snd_pcm_format_mask_t *fmask, int *fmt)
-{
-    if (ft->encoding.bits_per_sample != 16)
-    {
-        lsx_report("trying for word samples.");
-        ft->encoding.bits_per_sample = 16;
-    }
+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]
 
-    if (ft->encoding.encoding != SOX_ENCODING_SIGN2 &&
-        ft->encoding.encoding != SOX_ENCODING_UNSIGNED)
-    {
-        if (ft->encoding.bits_per_sample == 16)
-        {
-          if (ft->encoding.encoding != SOX_ENCODING_UNKNOWN)
-            lsx_report("driver supports only signed and unsigned samples.  Changing to signed.");
-            ft->encoding.encoding = SOX_ENCODING_SIGN2;
-        }
-        else
-        {
-          if (ft->encoding.encoding != SOX_ENCODING_UNKNOWN)
-            lsx_report("driver supports only signed and unsigned samples.  Changing to unsigned.");
-            ft->encoding.encoding = SOX_ENCODING_UNSIGNED;
-        }
-    }
-
-    /* Some hardware only wants to work with 8-bit or 16-bit data */
-    if (ft->encoding.bits_per_sample == 8)
-    {
-        if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U8)) &&
-            !(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S8)))
-        {
-            lsx_report("driver doesn't supported byte samples.  Changing to words.");
-            ft->encoding.bits_per_sample = 16;
-        }
-    }
-    else if (ft->encoding.bits_per_sample == 16)
-    {
-        if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16)) &&
-            !(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16)))
-        {
-            lsx_report("driver doesn't supported word samples.  Changing to bytes.");
-            ft->encoding.bits_per_sample = 8;
-        }
-    }
-    else
-    {
-        if ((snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16)) ||
-            (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16)))
-        {
-            lsx_report("driver doesn't supported %u-bit samples.  Changing to 16-bit.", ft->encoding.bits_per_sample);
-            ft->encoding.bits_per_sample = 16;
-        }
-        else
-        {
-            lsx_report("driver doesn't supported %u-bit samples.  Changing to 8-bit.", ft->encoding.bits_per_sample);
-            ft->encoding.bits_per_sample = 8;
-        }
-    }
-
-    if (ft->encoding.bits_per_sample == 8) {
-        switch (ft->encoding.encoding)
-        {
-            case SOX_ENCODING_SIGN2:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S8)))
-                {
-                    lsx_report("driver doesn't supported signed byte samples.  Changing to unsigned bytes.");
-                    ft->encoding.encoding = SOX_ENCODING_UNSIGNED;
-                }
-                break;
-            case SOX_ENCODING_UNSIGNED:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U8)))
-                {
-                    lsx_report("driver doesn't supported unsigned byte samples.  Changing to signed bytes.");
-                    ft->encoding.encoding = SOX_ENCODING_SIGN2;
-                }
-                break;
-            default:
-                    break;
-        }
-        switch (ft->encoding.encoding)
-        {
-            case SOX_ENCODING_SIGN2:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S8)))
-                {
-                    lsx_fail_errno(ft,SOX_EFMT,"ALSA driver does not support signed byte samples");
-                    return SOX_EOF;
-                }
-                *fmt = SND_PCM_FORMAT_S8;
-                break;
-            case SOX_ENCODING_UNSIGNED:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U8)))
-                {
-                    lsx_fail_errno(ft,SOX_EFMT,"ALSA driver does not support unsigned byte samples");
-                    return SOX_EOF;
-                }
-                *fmt = SND_PCM_FORMAT_U8;
-                break;
-            default:
-                    break;
-        }
-    }
-    else if (ft->encoding.bits_per_sample == 16) {
-        switch (ft->encoding.encoding)
-        {
-            case SOX_ENCODING_SIGN2:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16)))
-                {
-                    lsx_report("driver does not support signed word samples.  Changing to unsigned words.");
-                    ft->encoding.encoding = SOX_ENCODING_UNSIGNED;
-                }
-                break;
-            case SOX_ENCODING_UNSIGNED:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16)))
-                {
-                    lsx_report("driver does not support unsigned word samples.  Changing to signed words.");
-                    ft->encoding.encoding = SOX_ENCODING_SIGN2;
-                }
-                break;
-            default:
-                    break;
-        }
-        switch (ft->encoding.encoding)
-        {
-            case SOX_ENCODING_SIGN2:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_S16)))
-                {
-                    lsx_fail_errno(ft,SOX_EFMT,"ALSA driver does not support signed word samples");
-                    return SOX_EOF;
-                }
-                *fmt = SND_PCM_FORMAT_S16;
-                break;
-            case SOX_ENCODING_UNSIGNED:
-                if (!(snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_U16)))
-                {
-                    lsx_fail_errno(ft,SOX_EFMT,"ALSA driver does not support unsigned word samples");
-                    return SOX_EOF;
-                }
-                *fmt = SND_PCM_FORMAT_U16;
-                break;
-            default:
-                    break;
-        }
-    }
-    else {
-        lsx_fail_errno(ft,SOX_EFMT,"ALSA driver does not support %s %u-bit output",
-                      sox_encodings_info[(unsigned char)ft->encoding.encoding].desc, ft->encoding.bits_per_sample);
-        return SOX_EOF;
-    }
-    return 0;
-}
+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;
+}
 
-static int setup(sox_format_t * ft, snd_pcm_stream_t mode)
+#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)
 {
-    int fmt = SND_PCM_FORMAT_S16;
-    int err;
-    priv_t * alsa = (priv_t *)ft->priv;
-    snd_pcm_hw_params_t *hw_params = NULL;
-    unsigned int min_rate, max_rate;
-    unsigned int min_chan, max_chan;
-    unsigned int rate;
-    snd_pcm_uframes_t buffer_size, buffer_size_min, buffer_size_max;
-    snd_pcm_uframes_t period_size, period_size_min, period_size_max;
-    int dir;
-    snd_pcm_format_mask_t *fmask = NULL;
-    sox_signalinfo_t client_signal = ft->signal;
+  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;
 
-    lsx_set_signal_defaults(&ft->signal);
-
-    if ((err = snd_pcm_open(&(alsa->pcm_handle), ft->filename,
-                            mode, 0)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "cannot open audio device");
-        goto open_error;
-    }
-
-    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_ENOMEM,
-                      "cannot allocate hardware parameter structure");
-        goto open_error;
-    }
-
-    if ((err = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM,
-                      "cannot initialize hardware parameter structure");
-        goto open_error;
-    }
-
-#if SND_LIB_VERSION >= 0x010009
-    /* Turn off software resampling */
-    err = snd_pcm_hw_params_set_rate_resample(alsa->pcm_handle, hw_params, 0);
-    if (err < 0) {
-        lsx_fail_errno(ft, SOX_EPERM, "Resampling setup failed for playback");
-        goto open_error;
-    }
+  _(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));
 
-    if ((err = snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params,
-                                            SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM,
-                      "cannot set access type");
-        goto open_error;
-    }
+  _(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;
 
-    snd_pcm_hw_params_get_channels_min(hw_params, &min_chan);
-    snd_pcm_hw_params_get_channels_max(hw_params, &max_chan);
-    if (ft->signal.channels == 0)
-        ft->signal.channels = min_chan;
-    else
-        if (ft->signal.channels > max_chan)
-            ft->signal.channels = max_chan;
-        else if (ft->signal.channels < min_chan)
-            ft->signal.channels = min_chan;
+  n = ft->signal.rate;                              /* Set rate: */
+  _(snd_pcm_hw_params_set_rate_near, (p->pcm, params, &n, 0));
+  ft->signal.rate = n;
 
-    if (snd_pcm_format_mask_malloc(&fmask) < 0)
-        goto open_error;
-    snd_pcm_hw_params_get_format_mask(hw_params, fmask);
+  n = ft->signal.channels;                          /* Set channels: */
+  _(snd_pcm_hw_params_set_channels_near, (p->pcm, params, &n));
+  ft->signal.channels = n;
 
-    if (get_format(ft, fmask, &fmt) < 0)
-        goto open_error;
+  /* 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_format_mask_free(fmask);
-    fmask = NULL;
+  _(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;
 
-    if ((err = snd_pcm_hw_params_set_format(alsa->pcm_handle,
-                                            hw_params, fmt)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "cannot set sample format");
-        goto open_error;
-    }
-
-    snd_pcm_hw_params_get_rate_min(hw_params, &min_rate, &dir);
-    snd_pcm_hw_params_get_rate_max(hw_params, &max_rate, &dir);
-
-    rate = range_limit(ft->signal.rate, min_rate, max_rate);
-    if (rate != ft->signal.rate)
-    {
-      if (client_signal.rate != 0)
-        lsx_report("hardware does not support sample rate %g; changing to %i.", ft->signal.rate, rate);
-        ft->signal.rate = rate;
-    }
-    dir = 0;
-    if ((err = snd_pcm_hw_params_set_rate_near(alsa->pcm_handle,
-                                               hw_params,
-                                               &rate,
-                                               &dir)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "cannot set sample rate");
-        goto open_error;
-    }
-    snd_pcm_hw_params_get_rate(hw_params,
-                               &rate,
-                               &dir);
-
-    if (rate != ft->signal.rate)
-    {
-        lsx_report("Could not set exact rate of %g.  Approximating with %i",
-                ft->signal.rate, rate);
-    }
-
-    snd_pcm_hw_params_get_rate(hw_params, &rate, &dir);
-
-    if ((err = snd_pcm_hw_params_set_channels(alsa->pcm_handle,
-                                              hw_params,
-                                              ft->signal.channels)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "cannot set channel count");
-        goto open_error;
-    }
-
-    /* Have a much larger buffer than sox_globals.bufsiz to avoid underruns */
-    buffer_size = sox_globals.bufsiz * 8 / (ft->encoding.bits_per_sample >> 3) / ft->signal.channels;
-
-    if (snd_pcm_hw_params_get_buffer_size_min(hw_params, &buffer_size_min) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "Error getting min buffer size.");
-        goto open_error;
-    }
-
-    if (snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "Error getting max buffer size.");
-        goto open_error;
-    }
-
-    dir = 0;
-    if (snd_pcm_hw_params_get_period_size_min(hw_params,
-                                              &period_size_min, &dir) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "Error getting min period size.");
-        goto open_error;
-    }
-
-    dir = 0;
-    if (snd_pcm_hw_params_get_period_size_max(hw_params,
-                                              &period_size_max, &dir) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "Error getting max buffer size.");
-        goto open_error;
-    }
-
-    if (buffer_size_max < buffer_size)
-        buffer_size = buffer_size_max;
-    else if (buffer_size_min > buffer_size)
-        buffer_size = buffer_size_min;
-
-    period_size = buffer_size / 8;
-    buffer_size = period_size * 8;
-
-    dir = 0;
-    if (snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params,
-                                               &period_size, &dir) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "Error setting periods.");
-        goto open_error;
-    }
-    snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir);
-
-    dir = 0;
-    if (snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params,
-                                               &buffer_size) < 0) {
-        lsx_fail_errno(ft, SOX_EPERM, "Error setting buffer size.");
-        goto open_error;
-    }
-
-    snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
-    if (period_size * 2 > buffer_size)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "Buffer too small. Could not use.");
-        goto open_error;
-    }
-
-    if ((err = snd_pcm_hw_params(alsa->pcm_handle, hw_params)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "cannot set parameters");
-        goto open_error;
-    }
-
-    snd_pcm_hw_params_free(hw_params);
-    hw_params = NULL;
-
-    if ((err = snd_pcm_prepare(alsa->pcm_handle)) < 0)
-    {
-        lsx_fail_errno(ft, SOX_EPERM, "cannot prepare audio interface for use");
-        goto open_error;
-    }
-
-    alsa->buf_size = buffer_size * (ft->encoding.bits_per_sample >> 3) * ft->signal.channels;
-    alsa->period_size = period_size;
-    alsa->frames_this_period = 0;
-    alsa->buf = lsx_malloc(alsa->buf_size);
-
-    return (SOX_SUCCESS);
-
-open_error:
-    if (fmask)
-        snd_pcm_format_mask_free(fmask);
-    if (hw_params)
-        snd_pcm_hw_params_free(hw_params);
-
-    return SOX_EOF;
-
+error:
+  if (mask) snd_pcm_format_mask_free(mask);
+  if (params) snd_pcm_hw_params_free(params);
+  return SOX_EOF;
 }
 
-/*
- *   Over/underrun and suspend recovery
- */
-static int xrun_recovery(snd_pcm_t *handle, int err)
+static int recover(sox_format_t * ft, snd_pcm_t * pcm, int err)
 {
-    if (err == -EPIPE)
-    {   /* over/under-run */
-        err = snd_pcm_prepare(handle);
-        if (err < 0)
-            lsx_warn("Can't recover from over/underrun, prepare failed: %s", snd_strerror(err));
-        return 0;
-    }
-    else
-    {
-        if (err == -ESTRPIPE)
-        {
-            /* wait until the suspend flag is released */
-            while ((err = snd_pcm_resume(handle)) == -EAGAIN)
-                sleep(1);
-            if (err < 0)
-            {
-                err = snd_pcm_prepare(handle);
-                if (err < 0)
-                    lsx_warn("Can't recovery from suspend, prepare failed: %s", snd_strerror(err));
-            }
-            return 0;
-        }
-    }
-    return 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;
 }
 
-/*
- * Do anything required before you start reading samples.
- * Read file header.
- *      Find out sampling rate,
- *      size and encoding of samples,
- *      mono/stereo/quad.
- */
-static int startread(sox_format_t * ft)
+static size_t read_(sox_format_t * ft, sox_sample_t * buf, size_t len)
 {
-    return setup(ft, SND_PCM_STREAM_CAPTURE);
-}
+  priv_t             * p = (priv_t *)ft->priv;
+  snd_pcm_sframes_t  i, n;
+  size_t             done;
 
-static void ub_read_buf(sox_sample_t *buf1, char const * buf2, size_t len, sox_bool swap UNUSED, size_t * clips UNUSED)
-{
-    while (len--)
-        *buf1++ = SOX_UNSIGNED_8BIT_TO_SAMPLE(*((unsigned char *)buf2++),);
-}
+  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, n) < 0)
+        return 0;
+    } while (n <= 0);
 
-static void sb_read_buf(sox_sample_t *buf1, char const * buf2, size_t len, sox_bool swap UNUSED, size_t * clips UNUSED)
-{
-    while (len--)
-        *buf1++ = SOX_SIGNED_8BIT_TO_SAMPLE(*((int8_t *)buf2++),);
-}
-
-static void uw_read_buf(sox_sample_t *buf1, char const * buf2, size_t len, sox_bool swap, size_t * clips UNUSED)
-{
-    while (len--)
-    {
-        uint16_t datum = *((uint16_t *)buf2);
-        buf2++; buf2++;
-        if (swap)
-            datum = lsx_swapw(datum);
-
-        *buf1++ = SOX_UNSIGNED_16BIT_TO_SAMPLE(datum,);
-    }
-}
-
-static void sw_read_buf(sox_sample_t *buf1, char const * buf2, size_t len, sox_bool swap, size_t * clips UNUSED)
-{
-    while (len--)
-    {
-        int16_t datum = *((int16_t *)buf2);
-        buf2++; buf2++;
-        if (swap)
-            datum = lsx_swapw(datum);
-
-        *buf1++ = SOX_SIGNED_16BIT_TO_SAMPLE(datum,);
-    }
-}
-
-static size_t read_samples(sox_format_t * ft, sox_sample_t *buf, size_t nsamp)
-{
-    priv_t * alsa = (priv_t *)ft->priv;
-    void (*read_buf)(sox_sample_t *, char const *, size_t, sox_bool, size_t *) = 0;
-    size_t len;
-
-    switch(ft->encoding.bits_per_sample) {
-      case 8:
-        switch(ft->encoding.encoding) {
-          case SOX_ENCODING_SIGN2:    read_buf = sb_read_buf; break;
-          case SOX_ENCODING_UNSIGNED: read_buf = ub_read_buf; break;
-          default:
-            lsx_fail_errno(ft,SOX_EFMT,"Do not support this encoding for this data size");
-            return 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 16:
-        switch(ft->encoding.encoding) {
-          case SOX_ENCODING_SIGN2:    read_buf = sw_read_buf; break;
-          case SOX_ENCODING_UNSIGNED: read_buf = uw_read_buf; break;
-          default:
-            lsx_fail_errno(ft,SOX_EFMT,"Do not support this encoding for this data size");
-            return 0;
-        }
+      }
+      case SND_PCM_FORMAT_U8: {
+        uint8_t * buf1 = (uint8_t *)p->buf;
+        while (i--) *buf++ = SOX_UNSIGNED_8BIT_TO_SAMPLE(*buf1++,);
         break;
-      default:
-        lsx_fail_errno(ft,SOX_EFMT,"Do not support this data size for this handler");
+      }
+      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;
+}
 
-    /* Prevent overflow */
-    if (nsamp > alsa->buf_size/(ft->encoding.bits_per_sample >> 3))
-      nsamp = (alsa->buf_size/(ft->encoding.bits_per_sample >> 3));
+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;
 
-    len = 0;
-    while (len < nsamp) {
-      long n = snd_pcm_readi(alsa->pcm_handle, alsa->buf,
-          (nsamp - len)/ft->signal.channels); /* ALSA takes "frame" counts. */
-      if (n < 0) {
-        if (xrun_recovery(alsa->pcm_handle, (int)n) < 0) {
-          lsx_fail_errno(ft, SOX_EPERM, "ALSA read error");
-          return 0;
-        }
-      } else {
-        n *= ft->signal.channels;
-        read_buf(buf + len, alsa->buf, (size_t)n, ft->encoding.reverse_bytes, &ft->clips);
-        len += n;
+  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;
+      }
     }
-    return len;
+    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, actual) < 0)
+        return 0;
+    } while (actual < 0);
+  }
+  return len;
 }
 
-static int stopread(sox_format_t * ft)
+static int stop(sox_format_t * ft)
 {
-    priv_t * alsa = (priv_t *)ft->priv;
-
-    snd_pcm_close(alsa->pcm_handle);
-
-    free(alsa->buf);
-
-    return SOX_SUCCESS;
+  priv_t * p = (priv_t *)ft->priv;
+  snd_pcm_close(p->pcm);
+  free(p->buf);
+  return SOX_SUCCESS;
 }
 
-static int startwrite(sox_format_t * ft)
+static int stop_write(sox_format_t * ft)
 {
-    return setup(ft, SND_PCM_STREAM_PLAYBACK);
-}
-
-static void sox_ub_write_buf(char* buf1, sox_sample_t const * buf2, size_t len, sox_bool swap UNUSED, size_t * clips)
-{
-    SOX_SAMPLE_LOCALS;
-    while (len--)
-        *(uint8_t *)buf1++ = SOX_SAMPLE_TO_UNSIGNED_8BIT(*buf2++, *clips);
-}
-
-static void sox_sb_write_buf(char *buf1, sox_sample_t const * buf2, size_t len, sox_bool swap UNUSED, size_t * clips)
-{
-    SOX_SAMPLE_LOCALS;
-    while (len--)
-        *(int8_t *)buf1++ = SOX_SAMPLE_TO_SIGNED_8BIT(*buf2++, *clips);
-}
-
-static void sox_uw_write_buf(char *buf1, sox_sample_t const * buf2, size_t len, sox_bool swap, size_t * clips)
-{
-    while (len--)
-    {
-        SOX_SAMPLE_LOCALS;
-        uint16_t datum = SOX_SAMPLE_TO_UNSIGNED_16BIT(*buf2++, *clips);
-        if (swap)
-            datum = lsx_swapw(datum);
-        *(uint16_t *)buf1 = datum;
-        buf1++; buf1++;
-    }
-}
-
-static void sox_sw_write_buf(char *buf1, sox_sample_t const * buf2, size_t len, sox_bool swap, size_t * clips)
-{
-    while (len--)
-    {
-        SOX_SAMPLE_LOCALS;
-        int16_t datum = SOX_SAMPLE_TO_SIGNED_16BIT(*buf2++, *clips);
-        if (swap)
-            datum = lsx_swapw(datum);
-        *(int16_t *)buf1 = datum;
-        buf1++; buf1++;
-    }
-}
-
-static size_t write_samples(sox_format_t * ft, const sox_sample_t *buf, size_t nsamp)
-{
-    size_t osamp, done;
-    priv_t * alsa = (priv_t *)ft->priv;
-    void (*write_buf)(char *, const sox_sample_t *, size_t, sox_bool, size_t *) = 0;
-
-    switch(ft->encoding.bits_per_sample) {
-        case 8:
-            switch (ft->encoding.encoding)
-            {
-                case SOX_ENCODING_SIGN2:
-                    write_buf = sox_sb_write_buf;
-                    break;
-                case SOX_ENCODING_UNSIGNED:
-                    write_buf = sox_ub_write_buf;
-                    break;
-                default:
-                    lsx_fail_errno(ft,SOX_EFMT,"this encoding is not supported for this data size");
-                    return 0;
-            }
-            break;
-        case 16:
-            switch (ft->encoding.encoding)
-            {
-                case SOX_ENCODING_SIGN2:
-                    write_buf = sox_sw_write_buf;
-                    break;
-                case SOX_ENCODING_UNSIGNED:
-                    write_buf = sox_uw_write_buf;
-                    break;
-                default:
-                    lsx_fail_errno(ft,SOX_EFMT,"this encoding is not supported for this data size");
-                    return 0;
-            }
-            break;
-        default:
-            lsx_fail_errno(ft,SOX_EFMT,"this data size is not supported by this handler");
-            return 0;
-    }
-
-    for (done = 0; done < nsamp; done += osamp) {
-      int err;
-      size_t len;
-
-      osamp = min(nsamp - done, alsa->buf_size / (ft->encoding.bits_per_sample >> 3));
-      write_buf(alsa->buf, buf, osamp, ft->encoding.reverse_bytes, &ft->clips);
-      buf += osamp;
-
-      for (len = 0; len < osamp;) {
-        err = snd_pcm_writei(alsa->pcm_handle,
-                             alsa->buf + (len * (ft->encoding.bits_per_sample >> 3)),
-                             (osamp - len) / ft->signal.channels);
-        if (errno == EAGAIN) /* Happens naturally; don't report it */
-          errno = 0;
-        if (err < 0) {
-          if (xrun_recovery(alsa->pcm_handle, err) < 0) {
-            lsx_fail_errno(ft, SOX_EPERM, "ALSA write error");
-            return 0;
-          }
-        } else
-          len += err * ft->signal.channels;
-      }
-    }
-
-    /* keep track of how many frames have been played this period, so we know
-     * how many frames of silence to append at the end of playback */
-    alsa->frames_this_period = (alsa->frames_this_period + nsamp / ft->signal.channels) % alsa->period_size;
-    return nsamp;
-}
-
-
-static int stopwrite(sox_format_t * ft)
-{
-  priv_t * alsa = (priv_t *)ft->priv;
-
-  /* Pad to hardware period: */
-  size_t npad = (alsa->period_size - alsa->frames_this_period) * ft->signal.channels;
+  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 */
-  write_samples(ft, buf, npad);
-  free(buf);
 
-  snd_pcm_drain(alsa->pcm_handle);
-  snd_pcm_close(alsa->pcm_handle);
-  free(alsa->buf);
-  return SOX_SUCCESS;
+  if (npad != n)                      /* pad to hardware period: */
+    write_(ft, buf, npad);
+  free(buf);
+  snd_pcm_drain(p->pcm);
+  return stop(ft);
 }
 
 SOX_FORMAT_HANDLER(alsa)
 {
-  static char const * const names[] = { "alsa", NULL };
+  static char const * const names[] = {"alsa", NULL};
   static unsigned const write_encodings[] = {
-    SOX_ENCODING_SIGN2, 16, 8, 0,
-    SOX_ENCODING_UNSIGNED, 16, 8, 0,
-    0};
+    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,
-    startread, read_samples, stopread,
-    startwrite, write_samples, stopwrite,
+    setup, read_, stop, setup, write_, stop_write,
     NULL, write_encodings, NULL, sizeof(priv_t)
   };
   return &handler;
--- a/src/ao.c
+++ b/src/ao.c
@@ -35,7 +35,6 @@
 {
   priv_t * ao = (priv_t *)ft->priv;
 
-  lsx_set_signal_defaults(&ft->signal);
   ao->buf_size = sox_globals.bufsiz - (sox_globals.bufsiz % (ft->encoding.bits_per_sample >> 3));
   ao->buf_size *= (ft->encoding.bits_per_sample >> 3);
   ao->buf = lsx_malloc(ao->buf_size);
--- a/src/formats.c
+++ b/src/formats.c
@@ -477,6 +477,9 @@
   else sox_init_encodinginfo(&ft->encoding);
   set_endiannesses(ft);
 
+  if ((ft->handler.flags & SOX_FILE_DEVICE) && !(ft->handler.flags & SOX_FILE_PHONY))
+    lsx_set_signal_defaults(ft);
+
   ft->priv = lsx_calloc(1, ft->handler.priv_size);
   /* Read and write starters can change their formats. */
   if (ft->handler.startread && (*ft->handler.startread)(ft) != SOX_SUCCESS) {
@@ -485,15 +488,24 @@
   }
 
   /* Fill in some defaults: */
-  if (!ft->signal.precision)
+  if (sox_precision(ft->encoding.encoding, ft->encoding.bits_per_sample))
     ft->signal.precision = sox_precision(ft->encoding.encoding, ft->encoding.bits_per_sample);
   if (!(ft->handler.flags & SOX_FILE_PHONY) && !ft->signal.channels)
     ft->signal.channels = 1;
 
-  if (sox_checkformat(ft) == SOX_SUCCESS)
-    return ft;
-  lsx_fail("bad input format for %s `%s': %s", type, ft->filename, ft->sox_errstr);
+  if (sox_checkformat(ft) != SOX_SUCCESS) {
+    lsx_fail("bad input format for %s `%s': %s", type, ft->filename, ft->sox_errstr);
+    goto error;
+  }
 
+  if (signal) {
+    if (signal->rate && signal->rate != ft->signal.rate)
+      lsx_warn("can't set sample rate %g; using %g", signal->rate, ft->signal.rate);
+    if (signal->channels && signal->channels != ft->signal.channels)
+      lsx_warn("can't set %u channels; using %u", signal->channels, ft->signal.channels);
+  }
+  return ft;
+
 error:
   if (ft->fp && ft->fp != stdin)
     xfclose(ft->fp, ft->io_type);
@@ -806,9 +818,18 @@
     goto error;
   }
 
-  if (sox_checkformat(ft) == SOX_SUCCESS)
-    return ft;
-  lsx_fail("bad format for output file `%s': %s", ft->filename, ft->sox_errstr);
+  if (sox_checkformat(ft) != SOX_SUCCESS) {
+    lsx_fail("bad format for output file `%s': %s", ft->filename, ft->sox_errstr);
+    goto error;
+  }
+
+  if ((ft->handler.flags & SOX_FILE_DEVICE) && signal) {
+    if (signal->rate && signal->rate != ft->signal.rate)
+      lsx_warn("can't set sample rate %g; using %g", signal->rate, ft->signal.rate);
+    if (signal->channels && signal->channels != ft->signal.channels)
+      lsx_warn("can't set %u channels; using %u", signal->channels, ft->signal.channels);
+  }
+  return ft;
 
 error:
   if (ft->fp && ft->fp != stdout)
--- a/src/formats_i.c
+++ b/src/formats_i.c
@@ -39,11 +39,16 @@
   ft->sox_errstr[255] = '\0';
 }
 
-void lsx_set_signal_defaults(sox_signalinfo_t * signal)
+void lsx_set_signal_defaults(sox_format_t * ft)
 {
-  if (!signal->rate     ) signal->rate      = SOX_DEFAULT_RATE;
-  if (!signal->precision) signal->precision = SOX_DEFAULT_PRECISION;
-  if (!signal->channels ) signal->channels  = SOX_DEFAULT_CHANNELS;
+  if (!ft->signal.rate     ) ft->signal.rate      = SOX_DEFAULT_RATE;
+  if (!ft->signal.precision) ft->signal.precision = SOX_DEFAULT_PRECISION;
+  if (!ft->signal.channels ) ft->signal.channels  = SOX_DEFAULT_CHANNELS;
+
+  if (!ft->encoding.bits_per_sample)
+    ft->encoding.bits_per_sample = ft->signal.precision;
+  if (ft->encoding.encoding == SOX_ENCODING_UNKNOWN)
+    ft->encoding.encoding = SOX_ENCODING_SIGN2;
 }
 
 int lsx_check_read_params(sox_format_t * ft, unsigned channels,
--- a/src/oss.c
+++ b/src/oss.c
@@ -45,9 +45,7 @@
     int sampletype, samplesize, dsp_stereo;
     int tmp, rc;
     priv_t *file = (priv_t *)ft->priv;
-    sox_signalinfo_t client_signal = ft->signal;
 
-    lsx_set_signal_defaults(&ft->signal);
     if (ft->encoding.bits_per_sample == 8) {
         sampletype = AFMT_U8;
         samplesize = 8;
@@ -156,11 +154,7 @@
     }
 
     if (tmp != dsp_stereo)
-    {
-      if (client_signal.channels != 0)
-        lsx_warn("Sound card appears to support only %d channels.  Overriding format", tmp+1);
         ft->signal.channels = tmp + 1;
-    }
 
     tmp = ft->signal.rate;
     if (ioctl (fileno(ft->fp), SNDCTL_DSP_SPEED, &tmp) < 0 ||
@@ -174,12 +168,8 @@
          * we can't hear anyways.
          */
         if ((int)ft->signal.rate - tmp > (tmp * .01) ||
-            tmp - (int)ft->signal.rate > (tmp * .01)) {
-          if (client_signal.rate != 0)
-            lsx_warn("Unable to set audio speed to %g (set to %d)",
-                     ft->signal.rate, tmp);
+            tmp - (int)ft->signal.rate > (tmp * .01))
             ft->signal.rate = tmp;
-        }
     }
 
     /* Find out block size to use last because the driver could compute
--- a/src/sox.c
+++ b/src/sox.c
@@ -2631,9 +2631,13 @@
       f->volume = 1.0 / sqrt((double)input_count);
 
     if (sox_mode == sox_rec && !j) {       /* Set the recording parameters: */
-      if (input_count > 1)                 /* from the (just openned) next */
+      if (input_count > 1) {               /* from the (just openned) next */
         f->signal = files[1]->ft->signal;  /* input file, or from the output */
-      else f->signal = files[1]->signal;   /* file (which is not open yet). */
+        f->encoding = files[1]->ft->encoding;
+      } else {
+        f->signal = files[1]->signal;      /* file (which is not open yet). */
+        f->encoding = files[1]->encoding;
+      }
     }
     files[j]->ft = sox_open_read(f->filename, &f->signal, &f->encoding, f->filetype);
     if (!files[j]->ft)
--- a/src/sox_i.h
+++ b/src/sox_i.h
@@ -139,7 +139,7 @@
 size_t lsx_writebuf(sox_format_t * ft, void const *buf, size_t len);
 int lsx_reads(sox_format_t * ft, char *c, size_t len);
 int lsx_writes(sox_format_t * ft, char const * c);
-void lsx_set_signal_defaults(sox_signalinfo_t * signal);
+void lsx_set_signal_defaults(sox_format_t * ft);
 #define lsx_writechars(ft, chars, len) (lsx_writebuf(ft, chars, len) == len? SOX_SUCCESS : SOX_EOF)
 
 size_t lsx_read_3_buf(sox_format_t * ft, uint24_t *buf, size_t len);
--- a/src/sunaudio.c
+++ b/src/sunaudio.c
@@ -188,8 +188,6 @@
 #endif
     char simple_hw=0;
 
-    lsx_set_signal_defaults(&ft->signal);
-
     /* Hard-code for now. */
     file->count = 0;
     file->pos = 0;