Merge branch 't/alsa_improvements'
authorAndre Noll <maan@systemlinux.org>
Sat, 3 May 2014 17:56:01 +0000 (19:56 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 3 May 2014 18:03:51 +0000 (20:03 +0200)
Was cooking for almost two months.

* t/alsa_improvements:
  alsa: Remove pointless initialization.
  alsa_mix: Allow non-positive mixer values.
  alsa: New writer option: --buffer-time.
  alsa: Work around poll fd problems.
  alsa: Set period time.
  alsa: Also dump hardware params.
  alsa: Improve help text of --device.
  alsa: Improve documentation of struct private_alsa_write_data.
  alsa: Don't set t->error in ->pre_select().

1  2 
NEWS
alsa_mix.c
alsa_write.c
m4/gengetopt/alsa_write.m4

diff --combined NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,35 -1,14 +1,41 @@@
  NEWS
  ====
  
 ----------------------------------------------
 -0.5.2 (to be announced) "orthogonal interior"
 ----------------------------------------------
++-------------------------------------------------
++0.5.3 (to be released) "symbolic synchronization"
++-------------------------------------------------
 -      - The new sync synchronizes playback between multiple clients.
++      - Various alsa-related fixes, mostly for the raspberry pi.
++
 +----------------------------------------
 +0.5.2 (2014-04-11) "orthogonal interior"
 +----------------------------------------
 +
 +The new sync filter, the AES_CTR128 stream cipher and the overhauled
 +network code are the highlights of this release. It also includes a
 +fair number of smaller fixes and improvements not mentioned here.
 +
 +      - The new sync filter synchronizes playback between multiple
 +        clients.
 +      - Connections between para_server and para_client are now
 +        encrypted by means of AES rather than RC4 if both sides
 +        support it. RC4 is still available as a fallback. This
 +        feature is fully transparent, i.e. no command line options
 +        are necessary, and a client linked against openssl can
 +        speak with a server linked against libgcrypt and vice versa.
        - Major cleanup of the networking subsystem.
 +      - Improvements to para_fade: the new set mode, multi-channel
 +        initial volumes, better error logging.
 +      - The man pages of para_audiod, para_filter, para_recv, and
 +        para_write contain the relevant options for receivers, filters,
 +        writers. This broke in 0.5.0.
 +      - ogg/vorbis latency improvements.
 +      - Improved user manual.
        - Minor fixes to avoid clang warnings.
  
 +Downloads: ./releases/paraslash-0.5.2.tar.bz2 (tarball),
 +./releases/paraslash-0.5.2.tar.bz2.asc (signature)
 +
  ------------------------------------------
  0.5.1 (2013-12-20) "temporary implication"
  ------------------------------------------
@@@ -53,9 -32,6 +59,9 @@@ of the build system
        - Many small bugs in the build system have been identified
          and fixed.
  
 +Downloads: ./releases/paraslash-0.5.1.tar.bz2 (tarball),
 +./releases/paraslash-0.5.1.tar.bz2.asc (signature)
 +
  ----------------------------------------
  0.5.0 (2013-08-23) "invertible validity"
  ----------------------------------------
diff --combined alsa_mix.c
@@@ -1,5 -1,5 +1,5 @@@
  /*
 - * Copyright (C) 2012-2013 Andre Noll <maan@systemlinux.org>
 + * Copyright (C) 2012-2014 Andre Noll <maan@systemlinux.org>
   *
   * Licensed under the GPL v2. For licencing details see COPYING.
   */
@@@ -141,19 -141,19 +141,19 @@@ static int alsa_mix_set_channel(struct 
                PARA_NOTICE_LOG("unable to find simple control '%s',%i\n",
                        snd_mixer_selem_id_get_name(sid),
                        snd_mixer_selem_id_get_index(sid));
 -              return -E_ALSA_MIX_BAD_ELEM;
 +              return -E_BAD_CHANNEL;
        }
        ret = snd_mixer_selem_get_playback_volume_range(h->elem,
                &h->pmin, &h->pmax);
        if (ret < 0) {
                PARA_NOTICE_LOG("unable to get %s range (%s): %s\n",
                        mixer_channel, h->card, snd_strerror(ret));
 -              return -E_ALSA_MIX_BAD_ELEM;
 +              return -E_ALSA_MIX_RANGE;
        }
-       if (h->pmin < 0 || h->pmax < 0 || h->pmin >= h->pmax) {
+       if (h->pmin >= h->pmax) {
                PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n",
                        mixer_channel, h->pmin, h->pmax, h->card);
 -              return -E_ALSA_MIX_BAD_ELEM;
 +              return -E_ALSA_MIX_RANGE;
        }
        return 1;
  }
diff --combined alsa_write.c
@@@ -1,5 -1,5 +1,5 @@@
  /*
 - * Copyright (C) 2005-2013 Andre Noll <maan@systemlinux.org>
 + * Copyright (C) 2005-2014 Andre Noll <maan@systemlinux.org>
   *
   * Licensed under the GPL v2. For licencing details see COPYING.
   */
@@@ -34,17 -34,24 +34,24 @@@ struct private_alsa_write_data 
        snd_pcm_t *handle;
        /** Determined and set by alsa_init(). */
        int bytes_per_frame;
-       /**
-        * The sample rate given by command line option or the decoder
-        * of the writer node group.
+       /*
+        * If the sample rate is not given at the command line and no wav
+        * header was detected, the btr exec mechanism is employed to query the
+        * ancestor buffer tree nodes for this information. In a typical setup
+        * the decoder passes the sample rate back to the alsa writer.
+        *
+        *  \sa \ref btr_exec_up().
         */
        unsigned sample_rate;
-       snd_pcm_format_t sample_format;
-       /**
-        * The number of channels, given by command line option or the
-        * decoder of the writer node group.
+       /*
+        * The sample format (8/16 bit, signed/unsigned, little/big endian) is
+        * determined in the same way as the \a sample_rate.
         */
+       snd_pcm_format_t sample_format;
+       /* The number of channels, again determined like \a sample_rate. */
        unsigned channels;
+       /* time until buffer underrun occurs, in milliseconds */
+       unsigned buffer_time;
        struct timeval drain_barrier;
        /* File descriptor for select(). */
        int poll_fd;
@@@ -72,9 -79,9 +79,9 @@@ static int alsa_init(struct private_als
        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;
  
        PARA_INFO_LOG("opening %s\n", conf->device_arg);
        msg = "unable to open pcm";
                &pad->sample_rate, NULL);
        if (ret < 0)
                goto fail;
-       msg = "unable to get buffer time";
-       ret = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time,
-               NULL);
-       if (ret < 0 || buffer_time == 0)
-               goto fail;
-       /* buffer at most 500 milliseconds */
-       buffer_time = PARA_MIN(buffer_time, 500U * 1000U);
+       /* alsa wants microseconds */
+       pad->buffer_time = conf->buffer_time_arg * 1000;
        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;
+       pad->buffer_time /= 1000; /* we prefer milliseconds */
+       period_time = pad->buffer_time * 250; /* buffer time / 4 */
+       msg = "could not set period time";
+       ret = snd_pcm_hw_params_set_period_time_near(pad->handle, hwparams,
+               &period_time, 0);
        if (ret < 0)
                goto fail;
        msg = "unable to install hw params";
        ret = snd_pcm_hw_params(pad->handle, hwparams);
        if (ret < 0)
        if (ret == 0) {
                char *buf, *p;
                size_t sz;
-               PARA_INFO_LOG("dumping alsa configuration\n");
+               PARA_DEBUG_LOG("dumping alsa configuration\n");
                snd_pcm_dump(pad->handle, output_log);
+               snd_pcm_hw_params_dump(hwparams, output_log);
                sz = snd_output_buffer_string(output_log, &buf);
                for (p = buf; p < buf + sz;) {
                        char *q = memchr(p, '\n', buf + sz - p);
                        if (!q)
                                break;
                        *q = '\0';
-                       PARA_INFO_LOG("%s\n", p);
+                       PARA_DEBUG_LOG("%s\n", p);
                        p = q + 1;
                }
                snd_output_close(output_log);
@@@ -207,11 -218,12 +218,12 @@@ static void alsa_write_pre_select(struc
                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, s);
        ret = snd_pcm_poll_descriptors(pad->handle, &pfd, 1);
        if (ret < 0) {
                PARA_ERROR_LOG("could not get alsa poll fd: %s\n",
                        snd_strerror(-ret));
-               t->error = -E_ALSA;
                return;
        }
        pad->poll_fd = pfd.fd;
@@@ -296,21 -308,13 +308,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) {
@@@ -1,6 -1,6 +1,6 @@@
  args "--no-version --no-help"
  
 -purpose "Native ALSA output plugin."
 +purpose "Native ALSA output plugin"
  
  include(header.m4)
  
@@@ -8,11 -8,29 +8,29 @@@
  option "device" d
  #~~~~~~~~~~~~~~~~
  "set PCM device"
- string typestr="device"
- default="default"
+ string typestr = "device"
+ default = "default"
  optional
- details="
-       On systems with dmix, a better choice than the default
-       value might be to use \"plug:swmix\".
+ details = "
+       Check for the presence of a /proc/asound/ directory to see if
+       ALSA is present in your kernel. The file /proc/asound/devices
+       contains all devices ALSA knows about.
  "
+ option "buffer-time" B
+ #~~~~~~~~~~~~~~~~~~~~~
+ "duration of the ALSA buffer"
+ int typestr = "milliseconds"
+ default = "170"
+ optional
+ details = "
+       This is only a hint as ALSA might pick a slightly different
+       time, depending on the sound hardware. The chosen value is
+       shown in debug output as BUFFER_TIME.
+       If synchronization between multiple clients is desired,
+       the same buffer time should be configured for all clients.
+ "
  </qu>