alsa: Work around poll fd problems.
authorAndre Noll <maan@systemlinux.org>
Thu, 13 Jun 2013 08:53:55 +0000 (10:53 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 8 Mar 2014 13:45:20 +0000 (14:45 +0100)
In its ->pre_select method the alsa writer adds the alsa poll file
descriptor to the set of fds to be monitored during the next select
call. When this fd is ready for I/O, select() returns and ->post_select
is called. Currently ->post_select checks if the alsa poll fd is set
and returns early if it is not.

The problem is that on some hardware/kernel/alsa-lib combinations,
the poll fd is never marked as ready for I/O. This happens at least
on the raspberry pi with linux-3.10.10 and alsa-lib-1.0.27. The result
is no audio at all.

We address this problem in two ways:

* In alsa_init() we remember the buffer duration for later use in
->pre_select where we ask the scheduler to impose half of the duration
as an upper bound on the select timeout.

* In ->post_select, if there is input data available, we don't
check any more whether the poll fd is ready but try to write
unconditionally. On EAGAIN, we read from the poll fd to discard any
pending events.

alsa_write.c

index e75fa23..358b634 100644 (file)
@@ -50,6 +50,8 @@ struct private_alsa_write_data {
        snd_pcm_format_t sample_format;
        /* The number of channels, again determined like \a sample_rate. */
        unsigned channels;
+       /* time until buffer underrun occurs, in microseconds */
+       unsigned buffer_time;
        struct timeval drain_barrier;
        /* File descriptor for select(). */
        int poll_fd;
@@ -77,7 +79,6 @@ static int alsa_init(struct private_alsa_write_data *pad,
        snd_pcm_uframes_t start_threshold, stop_threshold;
        snd_pcm_uframes_t buffer_size, period_size;
        snd_output_t *output_log;
-       unsigned buffer_time;
        int ret;
        const char *msg;
        unsigned period_time;
@@ -114,18 +115,18 @@ static int alsa_init(struct private_alsa_write_data *pad,
        if (ret < 0)
                goto fail;
        msg = "unable to get buffer time";
-       ret = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time,
+       ret = snd_pcm_hw_params_get_buffer_time_max(hwparams, &pad->buffer_time,
                NULL);
-       if (ret < 0 || buffer_time == 0)
+       if (ret < 0 || pad->buffer_time == 0)
                goto fail;
        /* buffer at most 500 milliseconds */
-       buffer_time = PARA_MIN(buffer_time, 500U * 1000U);
+       pad->buffer_time = PARA_MIN(pad->buffer_time, 500U * 1000U);
        msg = "could not set buffer time";
        ret = snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams,
-               &buffer_time, NULL);
+               &pad->buffer_time, NULL);
        if (ret < 0)
                goto fail;
-       period_time = buffer_time / 4;
+       period_time = pad->buffer_time / 4;
        msg = "could not set period time";
        ret = snd_pcm_hw_params_set_period_time_near(pad->handle, hwparams,
                &period_time, 0);
@@ -221,6 +222,8 @@ static void alsa_write_pre_select(struct sched *s, struct task *t)
                sched_request_barrier_or_min_delay(&pad->drain_barrier, s);
                return;
        }
+       /* wait at most 50% of the buffer time */
+       sched_request_timeout_ms(pad->buffer_time / 2 / 1000, s);
        ret = snd_pcm_poll_descriptors(pad->handle, &pfd, 1);
        if (ret < 0) {
                PARA_ERROR_LOG("could not get alsa poll fd: %s\n",
@@ -309,21 +312,13 @@ again:
                wn->min_iqs = pad->bytes_per_frame;
                goto again;
        }
-       if (pad->poll_fd < 0 || !FD_ISSET(pad->poll_fd, &s->rfds))
-               return 0;
        frames = bytes / pad->bytes_per_frame;
        frames = snd_pcm_writei(pad->handle, data, frames);
        if (frames == 0 || frames == -EAGAIN) {
-               /*
-                * The alsa poll fd was ready for IO but we failed to write to
-                * the alsa handle. This means another event is pending. We
-                * don't care about that but we have to dispatch the event in
-                * order to avoid a busy loop. So we simply read from the poll
-                * fd.
-                */
                char buf[100];
-               if (read(pad->poll_fd, buf, 100))
-                       do_nothing;
+               if (pad->poll_fd >= 0 && FD_ISSET(pad->poll_fd, &s->rfds))
+                       if (read(pad->poll_fd, buf, 100))
+                               do_nothing;
                return 0;
        }
        if (frames > 0) {