Alsa timing improvements.
authorAndre Noll <maan@systemlinux.org>
Sat, 29 Aug 2009 20:17:55 +0000 (22:17 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 29 Aug 2009 20:17:55 +0000 (22:17 +0200)
This moves the computation of the select timeout from
alsa_write_post_select() to alsa_write_pre_select(). The code now
computes when the next buffer underrun would occur and uses that
value to set the timeout for the next select call. This decreases
the number of writes to the alsa handle and therefore also the CPU
usage of para_write/para_audiod.

alsa_write.c

index 1e44609..d782894 100644 (file)
@@ -35,10 +35,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.
@@ -119,6 +119,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;
 }
 
@@ -139,7 +141,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 +148,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;
 }
 
@@ -168,7 +186,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)) {
@@ -177,8 +194,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)
@@ -189,26 +204,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)