Merge branch 'master' into next
[paraslash.git] / alsa_write.c
index 456c5178981f201aad9d048ca63079d3133952df..679962e0e00f276700138bb83517e7fbd7ab8bb7 100644 (file)
@@ -12,6 +12,7 @@
  * based on the vplay program by Michael Beck.
  */
 
+#include <regex.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <alsa/asoundlib.h>
@@ -35,10 +36,10 @@ struct private_alsa_write_data {
        snd_pcm_t *handle;
        /** Determined and set by alsa_open(). */
        int bytes_per_frame;
-       /** Don't write anything until this time. */
-       struct timeval next_chunk;
        /** The approximate maximum buffer duration in us. */
        unsigned buffer_time;
+       /* Number of frames that fit into the buffer. */
+       unsigned buffer_frames;
        /**
         * The samplerate given by command line option or the decoder
         * of the writer node group.
@@ -116,9 +117,11 @@ static int alsa_init(struct private_alsa_write_data *pad,
                * pad->channels / 8;
        if (pad->bytes_per_frame <= 0)
                return -E_PHYSICAL_WIDTH;
-       PARA_INFO_LOG("bytes per frame: %zu\n", pad->bytes_per_frame);
+       PARA_INFO_LOG("bytes per frame: %d\n", pad->bytes_per_frame);
        if (snd_pcm_nonblock(pad->handle, 1))
                PARA_ERROR_LOG("failed to set nonblock mode\n");
+       pad->buffer_frames = 1000 * pad->buffer_time / pad->samplerate;
+       PARA_INFO_LOG("max buffered frames: %d\n", pad->buffer_frames);
        return 1;
 }
 
@@ -139,7 +142,6 @@ static int alsa_open(struct writer_node *wn)
        else
                pad->channels = conf->channels_arg;
        PARA_INFO_LOG("%d channel(s), %dHz\n", pad->channels, pad->samplerate);
-       tv_add(now, &(struct timeval){0, 100 * 1000}, &pad->next_chunk);
        return 1;
 }
 
@@ -147,17 +149,34 @@ static int alsa_write_pre_select(struct sched *s, struct writer_node *wn)
 {
        struct private_alsa_write_data *pad = wn->private_data;
        struct writer_node_group *wng = wn->wng;
-       struct timeval diff;
+       struct timeval tv;
+       snd_pcm_sframes_t avail, underrun;
 
-       if (!*wng->loaded)
+       if (!pad->handle)
                return 1;
-       if (tv_diff(now, &pad->next_chunk, &diff) < 0) {
-               if (tv_diff(&s->timeout, &diff, NULL) > 0)
-                       s->timeout = diff;
-       } else {
-               s->timeout.tv_sec = 0;
-               s->timeout.tv_usec = 1;
-       }
+       if (*wng->loaded - wn->written < pad->bytes_per_frame)
+               return 1;
+       /*
+        * Data is available to be written to the alsa handle.  Compute number
+        * of milliseconds until next buffer underrun would occur.
+        *
+        * snd_pcm_avail_update() updates the current available count of
+        * samples for writing.  It is a light method to obtain current stream
+        * position, because it does not require the user <-> kernel context
+        * switch, but the value is less accurate, because ring buffer pointers
+        * are updated in kernel drivers only when an interrupt occurs.
+        */
+       avail = snd_pcm_avail_update(pad->handle);
+       if (avail < 0)
+               avail = 0;
+       underrun = (pad->buffer_frames - avail) * pad->buffer_time
+               / pad->buffer_frames / 1000;
+       if (underrun < 50)
+               underrun = 50;
+       underrun -= 50;
+       ms2tv(underrun, &tv);
+       if (tv_diff(&s->timeout, &tv, NULL) > 0)
+               s->timeout = tv;
        return 1;
 }
 
@@ -166,19 +185,16 @@ static int alsa_write_post_select(__a_unused struct sched *s,
 {
        struct private_alsa_write_data *pad = wn->private_data;
        struct writer_node_group *wng = wn->wng;
-       size_t frames, bytes = *wng->loaded - wn->written;
+       size_t bytes = *wng->loaded - wn->written;
        unsigned char *data = (unsigned char*)*wng->bufp + wn->written;
-       struct timeval tv;
-       snd_pcm_sframes_t ret;
+       snd_pcm_sframes_t ret, frames, avail;
 
        if (*wng->input_error < 0 && (!pad->handle || bytes < pad->bytes_per_frame)) {
                wn->written = *wng->loaded;
                return *wng->input_error;
        }
        if (!bytes) /* no data available */
-               goto out;
-       if (tv_diff(now, &pad->next_chunk, NULL) < 0)
-               goto out;
+               return 0;
        if (!pad->handle) {
                int err = alsa_init(pad, wn->conf);
                if (err < 0)
@@ -186,30 +202,24 @@ static int alsa_write_post_select(__a_unused struct sched *s,
        }
        frames = bytes / pad->bytes_per_frame;
        if (!frames) /* less than a single frame available */
-               goto out;
+               return 0;
+       avail = snd_pcm_avail_update(pad->handle);
+       if (avail <= 0)
+               return 0;
+       frames = PARA_MIN(frames, avail);
        ret = snd_pcm_writei(pad->handle, data, frames);
-       if (ret == -EPIPE) {
-               PARA_WARNING_LOG("EPIPE\n");
-               snd_pcm_prepare(pad->handle);
+       if (ret >= 0) {
+               wn->written += ret * pad->bytes_per_frame;
                return 1;
        }
-       if (ret < 0 && ret != -EAGAIN) {
-               PARA_WARNING_LOG("alsa error (%zu frames, ret = %d\n",
-                       frames, (int)ret);
-               return -E_ALSA_WRITE;
+       PARA_WARNING_LOG("%s\n", snd_strerror(-ret));
+       if (ret == -EPIPE) {
+               snd_pcm_prepare(pad->handle);
+               return 0;
        }
        if (ret == -EAGAIN)
-               PARA_DEBUG_LOG("EAGAIN\n");
-       else
-               wn->written += ret * pad->bytes_per_frame;
-       if (ret == frames) /* we wrote everything, try again immediately */
-               pad->next_chunk = *now;
-       else { /* wait until 50% buffer space is available */
-               ms2tv(pad->buffer_time / 2000, &tv);
-               tv_add(now, &tv, &pad->next_chunk);
-       }
-out:
-       return 1;
+               return 0;
+       return -E_ALSA_WRITE;
 }
 
 static void alsa_close(struct writer_node *wn)
@@ -264,4 +274,5 @@ void alsa_write_init(struct writer *w)
                .short_help = alsa_write_args_info_help,
                .detailed_help = alsa_write_args_info_detailed_help
        };
+       alsa_cmdline_parser_free(&dummy);
 }