First draft of the wma decoder.
[paraslash.git] / alsa_write.c
index 1e44609383d2e57b357a92cc780d162233075a4a..67ab59ad357127dbc3b065b73f1a250ee91b22b4 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.
@@ -79,18 +80,19 @@ static int alsa_init(struct private_alsa_write_data *pad,
                        pad->channels) < 0)
                return -E_CHANNEL_COUNT;
        if (snd_pcm_hw_params_set_rate_near(pad->handle, hwparams,
-                       &pad->samplerate, 0) < 0)
+                       &pad->samplerate, NULL) < 0)
                return -E_SET_RATE;
-       err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &pad->buffer_time, 0);
+       err = snd_pcm_hw_params_get_buffer_time_max(hwparams,
+               &pad->buffer_time, NULL);
        if (err < 0 || !pad->buffer_time)
                return -E_GET_BUFFER_TIME;
        PARA_INFO_LOG("buffer time: %d\n", pad->buffer_time);
        if (snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams,
-                       &pad->buffer_time, 0) < 0)
+                       &pad->buffer_time, NULL) < 0)
                return -E_SET_BUFFER_TIME;
        if (snd_pcm_hw_params(pad->handle, hwparams) < 0)
                return -E_HW_PARAMS;
-       snd_pcm_hw_params_get_period_size(hwparams, &period_size, 0);
+       snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
        snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
        PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", buffer_size,
                period_size);
@@ -119,6 +121,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 +143,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 +150,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 +188,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 +196,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 +206,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)