shithub: sox

Download patch

ref: 69aa432c2d828aaedfa1b3d62a118e29ec469943
parent: 4976f1410cc69ae475fdaedc574a404ed606dafd
author: cbagwell <cbagwell>
date: Sat Sep 17 16:52:34 EDT 2005

Add underrun recovery to alsa driver.

--- a/src/alsa.c
+++ b/src/alsa.c
@@ -64,7 +64,7 @@
     if ((err = snd_pcm_open(&(alsa->pcm_handle), ft->filename, 
                             mode, 0)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot open audio device\n");
+        st_fail_errno(ft, ST_EPERM, "cannot open audio device");
         return ST_EOF;
     }
 
@@ -71,7 +71,7 @@
     if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) 
     {
         st_fail_errno(ft, ST_ENOMEM, 
-                      "cannot allocate hardware parameter structure\n");
+                      "cannot allocate hardware parameter structure");
         return ST_EOF;
     }
 
@@ -78,7 +78,7 @@
     if ((err = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params)) < 0) 
     {
         st_fail_errno(ft, ST_EPERM,
-                      "cannot initialize hardware parameter structure\n");
+                      "cannot initialize hardware parameter structure");
         return ST_EOF;
     }
 
@@ -86,7 +86,7 @@
                                             SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
     {
         st_fail_errno(ft, ST_EPERM,
-                      "cannot set access type\n");
+                      "cannot set access type");
         return ST_EOF;
     }
 
@@ -111,7 +111,7 @@
     if ((err = snd_pcm_hw_params_set_format(alsa->pcm_handle, 
                                             hw_params, fmt)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set sample format\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set sample format");
         return ST_EOF;
     }
 
@@ -126,7 +126,7 @@
             rate = max_rate;
     if (rate != ft->info.rate)
     {
-        st_warn("alsa: Hardware does not support %d.  Forcing sample rate to %d.\n", ft->info.rate, rate);
+        st_warn("alsa: Hardware does not support %d.  Forcing sample rate to %d.", ft->info.rate, rate);
     }
 
     dir = 0;
@@ -135,12 +135,12 @@
                                                &rate,
                                                &dir)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set sample rate\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set sample rate");
         return ST_EOF;
     }
     if (rate != ft->info.rate)
     {
-        st_warn("Could not set exact rate of %d.  Approximating with %d\n",
+        st_warn("Could not set exact rate of %d.  Approximating with %d",
                 ft->info.rate, rate);
     }
 
@@ -150,15 +150,14 @@
                                               hw_params, 
                                               ft->info.channels)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set channel count\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set channel count");
         return ST_EOF;
     }
 
-#if 0
     /* Set number of periods. Periods used to be called fragments. */ 
     if (snd_pcm_hw_params_set_periods(alsa->pcm_handle, hw_params, 2, 0) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "Error setting periods.\n");
+        st_fail_errno(ft, ST_EPERM, "Error setting periods.");
         return ST_EOF;
     }
 
@@ -165,14 +164,13 @@
     /* Set buffer size (in frames). The resulting latency is given by */
     /* latency = periodsize * periods / (rate * bytes_per_frame)     */
     if (snd_pcm_hw_params_set_buffer_size(alsa->pcm_handle, hw_params, (ST_BUFSIZ * 8)>>2) < 0) {
-      st_fail_errno(ft, ST_EPERM, "Error setting buffersize.\n");
+      st_fail_errno(ft, ST_EPERM, "Error setting buffersize.");
       return ST_EOF;
     }
-#endif
 
     if ((err = snd_pcm_hw_params(alsa->pcm_handle, hw_params)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set parameters\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set parameters");
         return ST_EOF;
     }
 
@@ -180,7 +178,7 @@
 
     if ((err = snd_pcm_prepare(alsa->pcm_handle)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot prepare audio interface for use\n");
+        st_fail_errno(ft, ST_EPERM, "cannot prepare audio interface for use");
         return ST_EOF;
     }
 
@@ -190,6 +188,37 @@
 }
 
 /*
+ *   Underrun and suspend recovery
+ */
+static int xrun_recovery(snd_pcm_t *handle, int err)
+{
+    if (err == -EPIPE) 
+    {    /* under-run */
+        err = snd_pcm_prepare(handle);
+        if (err < 0)
+            st_warn("Can't recovery from overrun, 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)
+                    st_warn("Can't recovery from suspend, prepare failed: %s", snd_strerror(err));
+            }
+        }
+        return 0;
+    }
+    return err;
+}
+
+/*
  * Do anything required before you start reading samples.
  * Read file header.
  *      Find out sampling rate,
@@ -204,6 +233,7 @@
 st_ssize_t st_alsaread(ft_t ft, st_sample_t *buf, st_ssize_t nsamp)
 {
     st_ssize_t len;
+    int err;
     alsa_priv_t alsa = (alsa_priv_t)ft->priv;
     void (*read_buf)(st_sample_t *, char *, st_ssize_t, char) = 0;
 
@@ -251,11 +281,30 @@
     /* Prevent overflow */
     if (nsamp > alsa->buf_size/ft->info.size)
         nsamp = (alsa->buf_size/ft->info.size);
+    len = 0;
 
-    len = snd_pcm_readi(alsa->pcm_handle, alsa->buf, nsamp);
+    while (len < nsamp)
+    {
+        /* ALSA library takes "frame" counts. */
+        err = snd_pcm_readi(alsa->pcm_handle, alsa->buf, 
+                            (nsamp-len)/ft->info.channels);
+        if (err == -EAGAIN)
+            continue;
+        if (err < 0)
+        {
+            if (xrun_recovery(alsa->pcm_handle, err) < 0)
+            {
+                st_fail_errno(ft, ST_EPERM, "ALSA write error");
+                return ST_EOF;
+            }
+        }
+        else
+        {
+            read_buf(buf+len, alsa->buf, err, ft->swap);
+            len += err * ft->info.channels;
+        }
+    }
 
-    read_buf(buf, alsa->buf, len, ft->swap);
-
     return len;
 }
 
@@ -299,7 +348,7 @@
     if ((err = snd_pcm_open(&(alsa->pcm_handle), ft->filename, 
                             SND_PCM_STREAM_PLAYBACK, 0)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot open audio device\n");
+        st_fail_errno(ft, ST_EPERM, "cannot open audio device");
         return ST_EOF;
     }
 
@@ -306,7 +355,7 @@
     if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) 
     {
         st_fail_errno(ft, ST_ENOMEM, 
-                      "cannot allocate hardware parameter structure\n");
+                      "cannot allocate hardware parameter structure");
         return ST_EOF;
     }
 
@@ -313,7 +362,7 @@
     if ((err = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params)) < 0) 
     {
         st_fail_errno(ft, ST_EPERM,
-                      "cannot initialize hardware parameter structure\n");
+                      "cannot initialize hardware parameter structure");
         return ST_EOF;
     }
 
@@ -321,7 +370,7 @@
                                             SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
     {
         st_fail_errno(ft, ST_EPERM,
-                      "cannot set access type\n");
+                      "cannot set access type");
         return ST_EOF;
     }
 
@@ -346,7 +395,7 @@
     if ((err = snd_pcm_hw_params_set_format(alsa->pcm_handle, 
                                             hw_params, fmt)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set sample format\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set sample format");
         return ST_EOF;
     }
 
@@ -361,7 +410,7 @@
             rate = max_rate;
     if (rate != ft->info.rate)
     {
-        st_warn("alsa: Hardware does not support %d.  Forcing sample rate to %d.\n", ft->info.rate, rate);
+        st_warn("alsa: Hardware does not support %d.  Forcing sample rate to %d.", ft->info.rate, rate);
     }
 
     dir = 0;
@@ -370,12 +419,12 @@
                                                &rate,
                                                &dir)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set sample rate\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set sample rate");
         return ST_EOF;
     }
     if (rate != ft->info.rate)
     {
-        st_warn("Could not set exact rate of %d.  Approximating with %d\n",
+        st_warn("Could not set exact rate of %d.  Approximating with %d",
                 ft->info.rate, rate);
     }
 
@@ -385,7 +434,7 @@
                                               hw_params, 
                                               ft->info.channels)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set channel count\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set channel count");
         return ST_EOF;
     }
 
@@ -393,7 +442,7 @@
     /* Set number of periods. Periods used to be called fragments. */ 
     if (snd_pcm_hw_params_set_periods(alsa->pcm_handle, hw_params, 2, 0) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "Error setting periods.\n");
+        st_fail_errno(ft, ST_EPERM, "Error setting periods.");
         return ST_EOF;
     }
 
@@ -400,7 +449,7 @@
     /* Set buffer size (in frames). The resulting latency is given by */
     /* latency = periodsize * periods / (rate * bytes_per_frame)     */
     if (snd_pcm_hw_params_set_buffer_size(alsa->pcm_handle, hw_params, (ST_BUFSIZ * 2)>>2) < 0) {
-      st_fail_errno(ft, ST_EPERM, "Error setting buffersize.\n");
+      st_fail_errno(ft, ST_EPERM, "Error setting buffersize.");
       return ST_EOF;
     }
 #endif
@@ -407,7 +456,7 @@
 
     if ((err = snd_pcm_hw_params(alsa->pcm_handle, hw_params)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot set parameters\n");
+        st_fail_errno(ft, ST_EPERM, "cannot set parameters");
         return ST_EOF;
     }
 
@@ -415,7 +464,7 @@
 
     if ((err = snd_pcm_prepare(alsa->pcm_handle)) < 0) 
     {
-        st_fail_errno(ft, ST_EPERM, "cannot prepare audio interface for use\n");
+        st_fail_errno(ft, ST_EPERM, "cannot prepare audio interface for use");
         return ST_EOF;
     }
 
@@ -427,6 +476,7 @@
 st_ssize_t st_alsawrite(ft_t ft, st_sample_t *buf, st_ssize_t nsamp)
 {
     st_ssize_t len;
+    int err;
     alsa_priv_t alsa = (alsa_priv_t)ft->priv;
     void (*write_buf)(char *, st_sample_t *, st_ssize_t, char) = 0;
 
@@ -473,12 +523,26 @@
     /* Prevent overflow */
     if (nsamp > alsa->buf_size/ft->info.size)
         nsamp = (alsa->buf_size/ft->info.size);
+    len = 0;
 
     write_buf(alsa->buf, buf, nsamp, ft->swap);
 
-    if ((len = snd_pcm_writei(alsa->pcm_handle, alsa->buf, nsamp)) != nsamp) {
-        snd_pcm_prepare(alsa->pcm_handle);
-        fprintf(stderr, "Report me!  Buffer underrun thats not dealt with\n");
+    while (len < nsamp)
+    {
+        err = snd_pcm_writei(alsa->pcm_handle, alsa->buf, 
+                             (nsamp-len)/ft->info.channels);
+        if (err == -EAGAIN)
+            continue;
+        if (err < 0)
+        {
+            if (xrun_recovery(alsa->pcm_handle, err) < 0)
+            {
+                st_fail_errno(ft, ST_EPERM, "ALSA write error\n");
+                return ST_EOF;
+            }
+        }
+        else
+            len += err * ft->info.channels;
     }
 
     return len;
@@ -516,7 +580,7 @@
     if (ft->info.size != ST_SIZE_WORD &&
         ft->info.size != ST_SIZE_BYTE)
     {
-        st_warn("ALSA driver only supports byte and word samples.  Changing to word.\n");
+        st_warn("ALSA driver only supports byte and word samples.  Changing to word.");
         ft->info.size = ST_SIZE_WORD;
     }
 
@@ -525,12 +589,12 @@
     {
         if (ft->info.size == ST_SIZE_WORD)
         {
-            st_warn("ALSA driver only supports signed and unsigned samples.  Changing to signed.\n");
+            st_warn("ALSA driver only supports signed and unsigned samples.  Changing to signed.");
             ft->info.encoding = ST_ENCODING_SIGN2;
         }
         else
         {
-            st_warn("ALSA driver only supports signed and unsigned samples.  Changing to unsigned.\n");
+            st_warn("ALSA driver only supports signed and unsigned samples.  Changing to unsigned.");
             ft->info.encoding = ST_ENCODING_UNSIGNED;
         }
     }
--- a/src/sox.c
+++ b/src/sox.c
@@ -553,6 +553,11 @@
         ilen = (*file_desc[current_input]->h->read)(file_desc[current_input],
                                                     efftab[0].obuf, 
                                                     (st_ssize_t)ST_BUFSIZ);
+        if (ilen > ST_BUFSIZ)
+        {
+            st_warn("WARNING: Corrupt value of %d!  Assuming 0 bytes read.\n", ilen);
+            ilen = 0;
+        }
 
         /* FIXME: libst needs the feof() and ferror() concepts
          * to see if ST_EOF means a real failure.  Until then we
@@ -562,6 +567,7 @@
             efftab[0].olen = 0;
         else
             efftab[0].olen = ilen;
+        efftab[0].odone = 0;
 
         read_samples += (efftab[0].olen / file_desc[0]->info.channels);
 
@@ -993,7 +999,7 @@
     do {
       /* run entire chain BACKWARDS: pull, don't push.*/
       /* this is because buffering system isn't a nice queueing system */
-      for(e = neffects - 1; e >= input_eff; e--)
+      for(e = neffects - 1; e > input_eff; e--)
       {
           /* flow_effect returns ST_EOF when it will not process
            * any more samples.  This is used to bail out early.
@@ -1019,7 +1025,9 @@
            */
           if (efftab[e].odone < efftab[e].olen)
           {
-              /* fprintf(stderr, "Breaking out of loop to flush buffer\n"); */
+#if 0
+              fprintf(stderr, "Breaking out of loop to flush buffer\n");
+#endif
               break;
           }
       }
@@ -1067,7 +1075,7 @@
        * show no more data.
        */
       havedata = 0;
-      for(e = neffects - 1; e >= input_eff; e--)
+      for(e = neffects - 1; e > input_eff; e--)
       {
           /* If odone and olen are the same then this buffer
            * can be reused.
@@ -1113,7 +1121,12 @@
      * fact to caller.
      */
     if (input_eff > 0)
+    {
+#if 0
+        fprintf(stderr, "Effect return ST_EOF\n");
+#endif
         return ST_EOF;
+    }
 
     return ST_SUCCESS;
 }
@@ -1127,7 +1140,9 @@
     /* I have no input data ? */
     if (efftab[e-1].odone == efftab[e-1].olen)
     {
-        /* fprintf(stderr, "%s no data to pull to me!\n", efftab[e].name); */
+#if 0
+        fprintf(stderr, "%s no data to pull to me!\n", efftab[e].name);
+#endif
         return 0;
     }
 
@@ -1137,8 +1152,10 @@
          */
         idone = efftab[e-1].olen - efftab[e-1].odone;
         odone = ST_BUFSIZ - efftab[e].olen;
-        /* fprintf(stderr, "pre %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); */
-        /* fprintf(stderr, "pre %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); */
+#if 0
+        fprintf(stderr, "pre %s idone=%d, odone=%d\n", efftab[e].name, idone, odone);
+        fprintf(stderr, "pre %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); 
+#endif
 
         effstatus = (* efftab[e].h->flow)(&efftab[e],
                                           &efftab[e-1].obuf[efftab[e-1].odone],
@@ -1150,9 +1167,10 @@
         /* Leave efftab[e].odone were it was since we didn't consume data */
         /*efftab[e].odone = 0;*/
         efftab[e].olen += odone; 
-
-        /* fprintf(stderr, "post %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); */
-        /* fprintf(stderr, "post %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); */
+#if 0
+        fprintf(stderr, "post %s idone=%d, odone=%d\n", efftab[e].name, idone, odone); 
+        fprintf(stderr, "post %s odone1=%d, olen1=%d odone=%d olen=%d\n", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen);
+#endif
 
         done = idone + odone;
     } else {