Merge branch 'master' into next
[paraslash.git] / alsa_write.c
index ae8bed6..679962e 100644 (file)
@@ -36,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.
@@ -120,6 +120,8 @@ static int alsa_init(struct private_alsa_write_data *pad,
        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;
 }
 
@@ -140,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;
 }
 
@@ -148,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;
 }
 
@@ -169,7 +187,6 @@ static int alsa_write_post_select(__a_unused struct sched *s,
        struct writer_node_group *wng = wn->wng;
        size_t bytes = *wng->loaded - wn->written;
        unsigned char *data = (unsigned char*)*wng->bufp + wn->written;
-       struct timeval tv;
        snd_pcm_sframes_t ret, frames, avail;
 
        if (*wng->input_error < 0 && (!pad->handle || bytes < pad->bytes_per_frame)) {
@@ -178,8 +195,6 @@ static int alsa_write_post_select(__a_unused struct sched *s,
        }
        if (!bytes) /* no data available */
                return 0;
-       if (tv_diff(now, &pad->next_chunk, NULL) < 0)
-               return 0;
        if (!pad->handle) {
                int err = alsa_init(pad, wn->conf);
                if (err < 0)
@@ -190,26 +205,21 @@ static int alsa_write_post_select(__a_unused struct sched *s,
                return 0;
        avail = snd_pcm_avail_update(pad->handle);
        if (avail <= 0)
-               goto delay;
+               return 0;
        frames = PARA_MIN(frames, avail);
        ret = snd_pcm_writei(pad->handle, data, frames);
-       if (ret < 0) {
-               PARA_WARNING_LOG("%s\n", snd_strerror(-ret));
-               if (ret == -EPIPE) {
-                       snd_pcm_prepare(pad->handle);
-                       return 0;
-               }
-               if (ret == -EAGAIN)
-                       goto delay;
-               return -E_ALSA_WRITE;
+       if (ret >= 0) {
+               wn->written += ret * pad->bytes_per_frame;
+               return 1;
        }
-       wn->written += ret * pad->bytes_per_frame;
-       return 1;
-delay:
-       /* wait until 50% buffer space is available */
-       ms2tv(pad->buffer_time / 2000, &tv);
-       tv_add(now, &tv, &pad->next_chunk);
-       return 0;
+       PARA_WARNING_LOG("%s\n", snd_strerror(-ret));
+       if (ret == -EPIPE) {
+               snd_pcm_prepare(pad->handle);
+               return 0;
+       }
+       if (ret == -EAGAIN)
+               return 0;
+       return -E_ALSA_WRITE;
 }
 
 static void alsa_close(struct writer_node *wn)