shithub: sox

Download patch

ref: ffe6801b3990fbe1fab930c19afd7f1582aa9c5c
parent: 3ee08beb133a2ac7d239f5f89c7651eb617517c9
author: Ulrich Klauer <ulrich@chirlu.de>
date: Fri Jan 6 18:06:54 EST 2012

alsa: make select_format() extensible

Rewrite select_format() of the ALSA format handler to be (less
concise, but) more readable and extensible. Specifically, allow
more formats than exactly one signed-int/unsigned-int pair per
sample size, and sample sizes that are not divisible by 8.

--- a/src/alsa.c
+++ b/src/alsa.c
@@ -1,4 +1,4 @@
-/* libSoX device driver: ALSA   (c) 2006-9 SoX contributors
+/* libSoX device driver: ALSA   (c) 2006-2012 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
@@ -22,51 +22,79 @@
   snd_pcm_uframes_t  buf_len, period;
   snd_pcm_t          * pcm;
   char               * buf;
-  int                format;
+  unsigned int       format;
 } priv_t;
 
-#define NBYTES bytes_size[(ft->encoding.bits_per_sample >> 3) - 1]
-#define NSIZES array_length(bytes_size)
+static const
+  struct {
+    unsigned int bits;
+    enum _snd_pcm_format alsa_fmt;
+    unsigned int bytes; /* occupied in the buffer per sample */
+    sox_encoding_t enc;
+  } formats[] = {
+    /* order by # of bits; within that, preferred first */
+    {  8, SND_PCM_FORMAT_S8, 1, SOX_ENCODING_SIGN2 },
+    {  8, SND_PCM_FORMAT_U8, 1, SOX_ENCODING_UNSIGNED },
+    { 16, SND_PCM_FORMAT_S16, 2, SOX_ENCODING_SIGN2 },
+    { 16, SND_PCM_FORMAT_U16, 2, SOX_ENCODING_UNSIGNED },
+    { 24, SND_PCM_FORMAT_S24, 4, SOX_ENCODING_SIGN2 },
+    { 24, SND_PCM_FORMAT_U24, 4, SOX_ENCODING_UNSIGNED },
+    { 32, SND_PCM_FORMAT_S32, 4, SOX_ENCODING_SIGN2 },
+    { 32, SND_PCM_FORMAT_U32, 4, SOX_ENCODING_UNSIGNED },
+    {  0, 0, 0, SOX_ENCODING_UNKNOWN } /* end of list */
+  };
 
-static const int bytes_size[] = {1, 2, 4, 4}, formats[][NSIZES] = {
-    {SND_PCM_FORMAT_S8, SND_PCM_FORMAT_S16, SND_PCM_FORMAT_S24,
-         SND_PCM_FORMAT_S32},
-    {SND_PCM_FORMAT_U8, SND_PCM_FORMAT_U16, SND_PCM_FORMAT_U24,
-          SND_PCM_FORMAT_U32}};
-
 static int select_format(
     sox_encoding_t              * encoding_,
     unsigned                    * nbits_,
     snd_pcm_format_mask_t const * mask,
-    int                         * format)
+    unsigned int                * format)
 {
-  unsigned can_do[NSIZES], i, j, index = (*nbits_ >> 3) - 1, nbits;
-  sox_encoding_t encoding = *encoding_;
+  unsigned int from = 0, to; /* NB: "to" actually points one after the last */
+  int cand = -1;
 
-  for (i = 0; i < NSIZES; ++i) for (can_do[i] = 0, j = 0; j < 2; ++j)
-    can_do[i] |= snd_pcm_format_mask_test(mask, formats[j][i]);
+  while (formats[from].bits < *nbits_ && formats[from].bits != 0)
+    from++;  /* find the first entry with at least *nbits_ bits */
+  for (to = from; formats[to].bits != 0; to++) ;  /* find end of list */
 
-  if (index >= NSIZES ||
-      (encoding != SOX_ENCODING_SIGN2 && encoding != SOX_ENCODING_UNSIGNED)){
-    encoding = SOX_ENCODING_SIGN2;
-    index = 2;
+  while (to > 0) {
+    unsigned int i, bits_next = 0;
+    for (i = from; i < to; i++) {
+      lsx_debug_most("select_format: trying #%u", i);
+      if (snd_pcm_format_mask_test(mask, formats[i].alsa_fmt)) {
+        if (formats[i].enc == *encoding_) {
+          cand = i;
+          break; /* found a match */
+        } else if (cand == -1) /* don't overwrite a candidate that
+                                       was earlier in the list */
+          cand = i; /* will work, but encoding differs */
+      }
+    }
+    if (cand != -1)
+      break;
+    /* no candidate found yet; now try formats with less bits: */
+    to = from;
+    if (from > 0)
+      bits_next = formats[from-1].bits;
+    while (from && formats[from-1].bits == bits_next)
+      from--; /* go back to the first entry with bits_next bits */
   }
-  while (!can_do[index]) if (++index == NSIZES)           /* Search up */
-    for (--index; !can_do[index];) if (--index >= NSIZES) /* then down */
-      return -1;
-  nbits = (index + 1) << 3;
 
-  if (encoding == SOX_ENCODING_SIGN2 &&
-      !snd_pcm_format_mask_test(mask, formats[0][index]))
-    encoding = SOX_ENCODING_UNSIGNED;
+  if (cand == -1) {
+    lsx_debug("select_format: no suitable ALSA format found");
+    return -1;
+  }
 
-  if (*nbits_ != nbits || *encoding_ != encoding) {
+  if (*nbits_ != formats[cand].bits || *encoding_ != formats[cand].enc) {
     lsx_warn("can't encode %u-bit %s", *nbits_,
         sox_encodings_info[*encoding_].desc);
-    *nbits_ = nbits;
-    *encoding_ = encoding;
+    *nbits_ = formats[cand].bits;
+    *encoding_ = formats[cand].enc;
   }
-  *format = formats[encoding == SOX_ENCODING_UNSIGNED][index];
+  lsx_debug("selecting format %d: %s (%s)", cand,
+      snd_pcm_format_name(formats[cand].alsa_fmt),
+      snd_pcm_format_description(formats[cand].alsa_fmt));
+  *format = cand;
   return 0;
 }
 
@@ -91,7 +119,7 @@
   _(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_hw_params_set_format, (p->pcm, params, formats[p->format].alsa_fmt));
   snd_pcm_format_mask_free(mask), mask = NULL;
 
   n = ft->signal.rate;                              /* Set rate: */
@@ -109,7 +137,8 @@
            snd_strerror(err));
 
   /* Set buf_len > > sox_globals.bufsiz for no underrun: */
-  p->buf_len = sox_globals.bufsiz * 8 / NBYTES / ft->signal.channels;
+  p->buf_len = sox_globals.bufsiz * 8 / formats[p->format].bytes /
+      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;
@@ -125,7 +154,7 @@
   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);
+  p->buf = lsx_malloc(p->buf_len * formats[p->format].bytes);
   return SOX_SUCCESS;
 
 error:
@@ -164,7 +193,7 @@
     } while (n <= 0);
 
     i = n *= ft->signal.channels;
-    switch (p->format) {
+    switch (formats[p->format].alsa_fmt) {
       case SND_PCM_FORMAT_S8: {
         int8_t * buf1 = (int8_t *)p->buf;
         while (i--) *buf++ = SOX_SIGNED_8BIT_TO_SAMPLE(*buf1++,);
@@ -211,7 +240,7 @@
         while (i--) *buf++ = SOX_UNSIGNED_32BIT_TO_SAMPLE(*buf1++,);
         break;
       }
-     default: lsx_fail_errno(ft, SOX_EFMT, "invalid format");
+      default: lsx_fail_errno(ft, SOX_EFMT, "invalid format");
         return 0;
     }
   }
@@ -227,7 +256,7 @@
 
   for (done = 0; done < len; done += n) {
     i = n = min(len - done, p->buf_len);
-    switch (p->format) {
+    switch (formats[p->format].alsa_fmt) {
       case SND_PCM_FORMAT_S8: {
         int8_t * buf1 = (int8_t *)p->buf;
         while (i--) *buf1++ = SOX_SAMPLE_TO_SIGNED_8BIT(*buf++, ft->clips);
@@ -274,10 +303,13 @@
         while (i--) *buf1++ = SOX_SAMPLE_TO_UNSIGNED_32BIT(*buf++, ft->clips);
         break;
       }
+      default: lsx_fail_errno(ft, SOX_EFMT, "invalid format");
+        return 0;
     }
     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);
+      actual = snd_pcm_writei(p->pcm,
+          p->buf + i * formats[p->format].bytes,
+          (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)