Bug 779392 - Improve cubeb_alsa workaround for bug 761274. r=doublec
authorMatthew Gregan <kinetik@flim.org>
Tue, 13 Nov 2012 18:09:10 +1300
changeset 113188 875aceabf236b75d5b6631d39d4a75d56d55a64d
parent 113187 29c26199e717ab78a325ea51634c2ea34f5365bd
child 113189 d8d95ee60a9fd7098f49cd454ead29d70f305632
push id23859
push useremorley@mozilla.com
push dateWed, 14 Nov 2012 14:36:31 +0000
treeherdermozilla-central@87928cd21b40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdoublec
bugs779392, 761274
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 779392 - Improve cubeb_alsa workaround for bug 761274. r=doublec
media/libcubeb/README_MOZILLA
media/libcubeb/src/cubeb_alsa.c
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,11 +1,10 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was 2d7d3e8f2ecabb70d2723f3c86fcb591a84b7f85.
-Plus the single commit f4c927fb1c2dc0a0580d0bca4bd267c34febada4.
-Plus the single commit 1e54b689752a1e853828cb4124985d3160694913.
-Plus the single commit e5837ecbb2389abd7e6635f4339878c5e4c1e1d5.
+The git commit ID used was dfdf4a3c0f813717819f633f8bd3099c3e58ee8a.
+Plus the single commit 1bc23c6e2e4920f38f7aa56ec56ef5e379e18cf7.
+Plus the single commit 6e9ffc59202f95f09e5adb895d10eaedf36a6ea2.
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -13,17 +13,18 @@
 #include <limits.h>
 #include <poll.h>
 #include <unistd.h>
 #include <alsa/asoundlib.h>
 #include "cubeb/cubeb.h"
 
 #define CUBEB_STREAM_MAX 16
 #define CUBEB_WATCHDOG_MS 10000
-#define UNUSED __attribute__ ((__unused__))
+
+#define CUBEB_ALSA_PCM_NAME "default"
 
 #define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin"
 
 /* ALSA is not thread-safe.  snd_pcm_t instances are individually protected
    by the owning cubeb_stream's mutex.  snd_pcm_t creation and destruction
    is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1),
    so those calls must be wrapped in the following mutex. */
 static pthread_mutex_t cubeb_alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -47,16 +48,22 @@ struct cubeb {
 
   /* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */
   int control_fd_read;
   int control_fd_write;
 
   /* Track number of active streams.  This is limited to CUBEB_STREAM_MAX
      due to resource contraints. */
   unsigned int active_streams;
+
+  /* Local configuration with handle_underrun workaround set for PulseAudio
+     ALSA plugin.  Will be NULL if the PA ALSA plugin is not in use or the
+     workaround is not required. */
+  snd_config_t * local_config;
+  int is_pa;
 };
 
 enum stream_state {
   INACTIVE,
   RUNNING,
   DRAINING,
   PROCESSING,
   ERROR
@@ -406,23 +413,175 @@ cubeb_run_thread(void * context)
 
   do {
     r = cubeb_run(ctx);
   } while (r >= 0);
 
   return NULL;
 }
 
+static snd_config_t *
+get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
+{
+  int r;
+  snd_config_t * slave_pcm;
+  snd_config_t * slave_def;
+  snd_config_t * pcm;
+  char const * string;
+  char node_name[64];
+
+  slave_def = NULL;
+
+  r = snd_config_search(root_pcm, "slave", &slave_pcm);
+  if (r < 0) {
+    return NULL;
+  }
+
+  r = snd_config_get_string(slave_pcm, &string);
+  if (r >= 0) {
+    r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def);
+    if (r < 0) {
+      return NULL;
+    }
+  }
+
+  do {
+    r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm);
+    if (r < 0) {
+      break;
+    }
+
+    r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string);
+    if (r < 0) {
+      break;
+    }
+
+    r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
+    if (r < 0 || r > (int) sizeof(node_name)) {
+      break;
+    }
+    r = snd_config_search(lconf, node_name, &pcm);
+    if (r < 0) {
+      break;
+    }
+
+    return pcm;
+  } while (0);
+
+  if (slave_def) {
+    snd_config_delete(slave_def);
+  }
+
+  return NULL;
+}
+
+/* Work around PulseAudio ALSA plugin bug where the PA server forces a
+   higher than requested latency, but the plugin does not update its (and
+   ALSA's) internal state to reflect that, leading to an immediate underrun
+   situation.  Inspired by WINE's make_handle_underrun_config.
+   Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */
+static snd_config_t *
+init_local_config_with_workaround(char const * pcm_name)
+{
+  int r;
+  snd_config_t * lconf;
+  snd_config_t * pcm_node;
+  snd_config_t * node;
+  char const * string;
+  char node_name[64];
+
+  lconf = NULL;
+
+  if (snd_config == NULL) {
+    return NULL;
+  }
+
+  r = snd_config_copy(&lconf, snd_config);
+  if (r < 0) {
+    return NULL;
+  }
+
+  do {
+    r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node);
+    if (r < 0) {
+      break;
+    }
+
+    r = snd_config_get_id(pcm_node, &string);
+    if (r < 0) {
+      break;
+    }
+
+    r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
+    if (r < 0 || r > (int) sizeof(node_name)) {
+      break;
+    }
+    r = snd_config_search(lconf, node_name, &pcm_node);
+    if (r < 0) {
+      break;
+    }
+
+    /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */
+    while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) {
+      pcm_node = node;
+    }
+
+    /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */
+    r = snd_config_search(pcm_node, "type", &node);
+    if (r < 0) {
+      break;
+    }
+
+    r = snd_config_get_string(node, &string);
+    if (r < 0) {
+      break;
+    }
+
+    if (strcmp(string, "pulse") != 0) {
+      break;
+    }
+
+    /* Don't clobber an explicit existing handle_underrun value, set it only
+       if it doesn't already exist. */
+    r = snd_config_search(pcm_node, "handle_underrun", &node);
+    if (r != -ENOENT) {
+      break;
+    }
+
+    /* Disable pcm_pulse's asynchronous underrun handling. */
+    r = snd_config_imake_integer(&node, "handle_underrun", 0);
+    if (r < 0) {
+      break;
+    }
+
+    r = snd_config_add(pcm_node, node);
+    if (r < 0) {
+      break;
+    }
+
+    return lconf;
+  } while (0);
+
+  snd_config_delete(lconf);
+
+  return NULL;
+}
+
+
 static int
-cubeb_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream)
+cubeb_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config)
 {
   int r;
 
   pthread_mutex_lock(&cubeb_alsa_mutex);
-  r = snd_pcm_open(pcm, "default", stream, SND_PCM_NONBLOCK);
+  if (local_config) {
+    r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config);
+  } else {
+    r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK);
+  }
   pthread_mutex_unlock(&cubeb_alsa_mutex);
 
   return r;
 }
 
 static int
 cubeb_locked_pcm_close(snd_pcm_t * pcm)
 {
@@ -466,47 +625,30 @@ cubeb_unregister_stream(cubeb_stream * s
       ctx->streams[i] = NULL;
       break;
     }
   }
   pthread_mutex_unlock(&ctx->mutex);
 }
 
 static void
-silent_error_handler(char const * file UNUSED, int line UNUSED, char const * function UNUSED,
-                     int err UNUSED, char const * fmt UNUSED, ...)
+silent_error_handler(char const * file, int line, char const * function,
+                     int err, char const * fmt, ...)
 {
 }
 
-static int
-pcm_uses_pulseaudio_plugin(snd_pcm_t * pcm)
-{
-  snd_output_t * out;
-  char * buf;
-  size_t bufsz;
-  int r;
-
-  snd_output_buffer_open(&out);
-  snd_pcm_dump(pcm, out);
-  bufsz = snd_output_buffer_string(out, &buf);
-  r = bufsz >= strlen(ALSA_PA_PLUGIN) &&
-      strncmp(buf, ALSA_PA_PLUGIN, strlen(ALSA_PA_PLUGIN)) == 0;
-  snd_output_close(out);
-
-  return r;
-}
-
 int
-cubeb_init(cubeb ** context, char const * context_name UNUSED)
+cubeb_init(cubeb ** context, char const * context_name)
 {
   cubeb * ctx;
   int r;
   int i;
   int fd[2];
   pthread_attr_t attr;
+  snd_pcm_t * dummy;
 
   assert(context);
   *context = NULL;
 
   pthread_mutex_lock(&cubeb_alsa_mutex);
   if (!cubeb_alsa_error_handler_set) {
     snd_lib_error_set_handler(silent_error_handler);
     cubeb_alsa_error_handler_set = 1;
@@ -541,23 +683,48 @@ cubeb_init(cubeb ** context, char const 
   assert(r == 0);
 
   r = pthread_create(&ctx->thread, &attr, cubeb_run_thread, ctx);
   assert(r == 0);
 
   r = pthread_attr_destroy(&attr);
   assert(r == 0);
 
+  /* Open a dummy PCM to force the configuration space to be evaluated so that
+     init_local_config_with_workaround can find and modify the default node. */
+  r = cubeb_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL);
+  if (r >= 0) {
+    cubeb_locked_pcm_close(dummy);
+  }
+  ctx->is_pa = 0;
+  pthread_mutex_lock(&cubeb_alsa_mutex);
+  ctx->local_config = init_local_config_with_workaround(CUBEB_ALSA_PCM_NAME);
+  pthread_mutex_unlock(&cubeb_alsa_mutex);
+  if (ctx->local_config) {
+    ctx->is_pa = 1;
+    r = cubeb_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+    /* If we got a local_config, we found a PA PCM.  If opening a PCM with that
+       config fails with EINVAL, the PA PCM is too old for this workaround. */
+    if (r == -EINVAL) {
+      pthread_mutex_lock(&cubeb_alsa_mutex);
+      snd_config_delete(ctx->local_config);
+      pthread_mutex_unlock(&cubeb_alsa_mutex);
+      ctx->local_config = NULL;
+    } else if (r >= 0) {
+      cubeb_locked_pcm_close(dummy);
+    }
+  }
+
   *context = ctx;
 
   return CUBEB_OK;
 }
 
 char const *
-cubeb_get_backend_id(cubeb * ctx UNUSED)
+cubeb_get_backend_id(cubeb * ctx)
 {
   return "alsa";
 }
 
 void
 cubeb_destroy(cubeb * ctx)
 {
   int r;
@@ -572,21 +739,27 @@ cubeb_destroy(cubeb * ctx)
   r = pthread_join(ctx->thread, NULL);
   assert(r == 0);
 
   close(ctx->control_fd_read);
   close(ctx->control_fd_write);
   pthread_mutex_destroy(&ctx->mutex);
   free(ctx->fds);
 
+  if (ctx->local_config) {
+    pthread_mutex_lock(&cubeb_alsa_mutex);
+    snd_config_delete(ctx->local_config);
+    pthread_mutex_unlock(&cubeb_alsa_mutex);
+  }
+
   free(ctx);
 }
 
 int
-cubeb_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name UNUSED,
+cubeb_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
                   cubeb_stream_params stream_params, unsigned int latency,
                   cubeb_data_callback data_callback, cubeb_state_callback state_callback,
                   void * user_ptr)
 {
   cubeb_stream * stm;
   int r;
   snd_pcm_format_t format;
 
@@ -633,29 +806,30 @@ cubeb_stream_init(cubeb * ctx, cubeb_str
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
   stm->params = stream_params;
   stm->state = INACTIVE;
 
   r = pthread_mutex_init(&stm->mutex, NULL);
   assert(r == 0);
 
-  r = cubeb_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK);
+  r = cubeb_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
   if (r < 0) {
     cubeb_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   r = snd_pcm_nonblock(stm->pcm, 1);
   assert(r == 0);
 
   /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't
-     possibly work.  See https://bugzilla.mozilla.org/show_bug.cgi?id=761274 */
-  if (pcm_uses_pulseaudio_plugin(stm->pcm)) {
-    latency = latency < 200 ? 200 : latency;
+     possibly work.  See https://bugzilla.mozilla.org/show_bug.cgi?id=761274.
+     Only resort to this hack if the handle_underrun workaround failed. */
+  if (!ctx->local_config && ctx->is_pa) {
+    latency = latency < 500 ? 500 : latency;
   }
 
   r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
                          stm->params.channels, stm->params.rate, 1,
                          latency * 1000);
   if (r < 0) {
     cubeb_stream_destroy(stm);
     return CUBEB_ERROR_INVALID_FORMAT;