Bug 761274 - Work around buffer sizing bug in PulseAudio ALSA plugin. r=doublec
authorMatthew Gregan <kinetik@flim.org>
Mon, 16 Jul 2012 17:15:24 -0400
changeset 100940 e40d733dae5358a96c42c3d91d95ad83f3365c20
parent 100939 376aa1851c9afaf7c929c14b8d6d849a718e1a16
child 100941 5276035f74f397fab2bdc1e8a65b9b7b5efa53ab
push id23202
push useremorley@mozilla.com
push dateTue, 31 Jul 2012 12:59:27 +0000
treeherdermozilla-central@1ab429a89429 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdoublec
bugs761274
milestone17.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 761274 - Work around buffer sizing bug in PulseAudio ALSA plugin. r=doublec
content/media/nsAudioStream.cpp
media/libcubeb/AUTHORS
media/libcubeb/README_MOZILLA
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb_alsa.c
media/libcubeb/src/cubeb_audiounit.c
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/src/cubeb_winmm.c
--- a/content/media/nsAudioStream.cpp
+++ b/content/media/nsAudioStream.cpp
@@ -875,23 +875,23 @@ class nsBufferedAudioStream : public nsA
   PRInt32 GetMinWriteSize();
 
 private:
   static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
   {
     return static_cast<nsBufferedAudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
   }
 
-  static int StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
+  static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
   {
-    return static_cast<nsBufferedAudioStream*>(aThis)->StateCallback(aState);
+    static_cast<nsBufferedAudioStream*>(aThis)->StateCallback(aState);
   }
 
   long DataCallback(void* aBuffer, long aFrames);
-  int StateCallback(cubeb_state aState);
+  void StateCallback(cubeb_state aState);
 
   // Shared implementation of underflow adjusted position calculation.
   // Caller must own the monitor.
   PRInt64 GetPositionInFramesUnlocked();
 
   // The monitor is held to protect all access to member variables.  Write()
   // waits while mBuffer is full; DataCallback() notifies as it consumes
   // data from mBuffer.  Drain() waits while mState is DRAINING;
@@ -1263,22 +1263,21 @@ nsBufferedAudioStream::DataCallback(void
     memset(static_cast<PRUint8*>(aBuffer) + available, 0, bytesWanted);
     mLostFrames += bytesWanted / mBytesPerFrame;
     bytesWanted = 0;
   }
 
   return aFrames - (bytesWanted / mBytesPerFrame);
 }
 
-int
+void
 nsBufferedAudioStream::StateCallback(cubeb_state aState)
 {
   MonitorAutoLock mon(mMonitor);
   if (aState == CUBEB_STATE_DRAINED) {
     mState = DRAINED;
   } else if (aState == CUBEB_STATE_ERROR) {
     mState = ERRORED;
   }
   mon.NotifyAll();
-  return CUBEB_OK;
 }
 #endif
 
--- a/media/libcubeb/AUTHORS
+++ b/media/libcubeb/AUTHORS
@@ -1,1 +1,2 @@
 Matthew Gregan <kinetik@flim.org>
+Alexandre Ratchov <alex@caoua.org>
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 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 21d9678eb9755761f7a9d26253189b926258de7c.
+The git commit ID used was f3116936eba69aced240df7db77264269f3f95ae.
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -35,18 +35,18 @@ extern "C" {
     unsigned int latency_ms = 250;
 
     cubeb_stream * stm;
     cubeb_stream_init(app_ctx, &stm, "Example Stream 1", params,
                       latency_ms, data_cb, state_cb, NULL);
 
     cubeb_stream_start(stm);
     for (;;) {
-      cubeb_get_time(stm, &ts);
-      printf("time=%lu\n", ts);
+      cubeb_stream_get_position(stm, &ts);
+      printf("time=%llu\n", ts);
       sleep(1);
     }
     cubeb_stream_stop(stm);
 
     cubeb_stream_destroy(stm);
     cubeb_destroy(app_ctx);
     @endcode
 
@@ -59,20 +59,19 @@ extern "C" {
           buf[i][c] = 0;
         }
       }
       return nframes;
     }
     @endcode
 
     @code
-    int state_cb(cubeb_stream * stm, void * user, cubeb_state state)
+    void state_cb(cubeb_stream * stm, void * user, cubeb_state state)
     {
       printf("state=%d\n", state);
-      return CUBEB_OK;
     }
     @endcode
 */
 
 
 /** @file
     The <tt>libcubeb</tt> C API. */
 
@@ -136,31 +135,34 @@ enum {
 typedef long (* cubeb_data_callback)(cubeb_stream * stream,
                                      void * user_ptr,
                                      void * buffer,
                                      long nframes);
 
 /** User supplied state callback.
     @param stream
     @param user_ptr
-    @param state
-    @retval CUBEB_OK
-    @retval CUBEB_ERROR */
-typedef int (* cubeb_state_callback)(cubeb_stream * stream,
-                                     void * user_ptr,
-                                     cubeb_state state);
+    @param state */
+typedef void (* cubeb_state_callback)(cubeb_stream * stream,
+                                      void * user_ptr,
+                                      cubeb_state state);
 
 /** Initialize an application context.  This will perform any library or
     application scoped initialization.
     @param context
     @param context_name
     @retval CUBEB_OK
     @retval CUBEB_ERROR */
 int cubeb_init(cubeb ** context, char const * context_name);
 
+/** Get a read-only string identifying this context's current backend.
+    @param context
+    @retval Read-only string identifying current backend. */
+char const * cubeb_get_backend_id(cubeb * context);
+
 /** Destroy an application context.
     @param context */
 void cubeb_destroy(cubeb * context);
 
 /** Initialize a stream associated with the supplied application context.
     @param context
     @param stream
     @param stream_name
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -15,16 +15,18 @@
 #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 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;
 static int cubeb_alsa_error_handler_set = 0;
 
 struct cubeb {
@@ -469,16 +471,34 @@ cubeb_unregister_stream(cubeb_stream * s
 }
 
 static void
 silent_error_handler(char const * file UNUSED, int line UNUSED, char const * function UNUSED,
                      int err UNUSED, char const * fmt UNUSED, ...)
 {
 }
 
+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 * ctx;
   int r;
   int i;
   int fd[2];
   pthread_attr_t attr;
@@ -526,16 +546,22 @@ cubeb_init(cubeb ** context, char const 
   r = pthread_attr_destroy(&attr);
   assert(r == 0);
 
   *context = ctx;
 
   return CUBEB_OK;
 }
 
+char const *
+cubeb_get_backend_id(cubeb * ctx UNUSED)
+{
+  return "alsa";
+}
+
 void
 cubeb_destroy(cubeb * ctx)
 {
   int r;
 
   assert(ctx);
 
   pthread_mutex_lock(&ctx->mutex);
@@ -616,16 +642,22 @@ cubeb_stream_init(cubeb * ctx, cubeb_str
   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;
+  }
+
   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;
   }
 
--- a/media/libcubeb/src/cubeb_audiounit.c
+++ b/media/libcubeb/src/cubeb_audiounit.c
@@ -82,16 +82,22 @@ audio_unit_output_callback(void * user_p
 
 int
 cubeb_init(cubeb ** context, char const * context_name)
 {
   *context = (void *) 0xdeadbeef;
   return CUBEB_OK;
 }
 
+char const *
+cubeb_get_backend_id(cubeb * ctx)
+{
+  return "audiounit";
+}
+
 void
 cubeb_destroy(cubeb * ctx)
 {
   assert(ctx == (void *) 0xdeadbeef);
 }
 
 int
 cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -111,19 +111,19 @@ stream_request_callback(pa_stream * s, s
       return;
     }
 
     r = pa_stream_write(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
     assert(r == 0);
 
     if ((size_t) got < size / frame_size) {
       size_t buffer_fill = pa_stream_get_buffer_attr(s)->maxlength - pa_stream_writable_size(s);
-      double buffer_time = (double) buffer_fill / stm->sample_spec.rate;
+      pa_usec_t buffer_time = pa_bytes_to_usec(buffer_fill, &stm->sample_spec);
       /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
-      stm->drain_timer = pa_context_rttime_new(stm->context->context, pa_rtclock_now() + buffer_time * 1e6, stream_drain_callback, stm);
+      stm->drain_timer = pa_context_rttime_new(stm->context->context, pa_rtclock_now() + buffer_time, stream_drain_callback, stm);
       stm->shutdown = 1;
       return;
     }
 
     towrite -= size;
   }
 
   assert(towrite == 0);
@@ -206,16 +206,22 @@ cubeb_init(cubeb ** context, char const 
 
   state_wait(ctx, PA_CONTEXT_READY);
 
   *context = ctx;
 
   return CUBEB_OK;
 }
 
+char const *
+cubeb_get_backend_id(cubeb * ctx)
+{
+  return "pulse";
+}
+
 void
 cubeb_destroy(cubeb * ctx)
 {
   pa_operation * o;
 
   if (ctx->context) {
     pa_threaded_mainloop_lock(ctx->mainloop);
     o = pa_context_drain(ctx->context, context_notify_callback, ctx->mainloop);
@@ -290,17 +296,17 @@ cubeb_stream_init(cubeb * context, cubeb
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
 
   stm->sample_spec = ss;
 
   battr.maxlength = -1;
   battr.tlength = pa_usec_to_bytes(latency * PA_USEC_PER_MSEC, &stm->sample_spec);
   battr.prebuf = -1;
-  battr.minreq = battr.tlength / 2;
+  battr.minreq = battr.tlength / 4;
   battr.fragsize = -1;
 
   pa_threaded_mainloop_lock(stm->context->mainloop);
   stm->stream = pa_stream_new(stm->context->context, stream_name, &ss, &map);
   pa_stream_set_state_callback(stm->stream, stream_state_callback, stm->context->mainloop);
   pa_stream_set_write_callback(stm->stream, stream_request_callback, stm);
   pa_stream_connect_playback(stm->stream, NULL, &battr,
                              PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -1,10 +1,10 @@
 /*
- * Copyright  2011 Mozilla Foundation
+ * Copyright © 2011 Mozilla Foundation
  *
  * This program is made available under an ISC-style license.  See the
  * accompanying file LICENSE for details.
  */
 #undef NDEBUG
 #include <assert.h>
 #include <windows.h>
 #include <mmreg.h>
@@ -118,16 +118,17 @@ cubeb_refill_stream(cubeb_stream * stm)
   wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
 
   /* It is assumed that the caller is holding this lock.  It must be dropped
      during the callback to avoid deadlocks. */
   LeaveCriticalSection(&stm->lock);
   got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted);
   EnterCriticalSection(&stm->lock);
   if (got < 0) {
+    LeaveCriticalSection(&stm->lock);
     /* XXX handle this case */
     assert(0);
     return;
   } else if (got < wanted) {
     stm->draining = 1;
   }
 
   assert(hdr->dwFlags & WHDR_PREPARED);
@@ -221,16 +222,22 @@ cubeb_init(cubeb ** context, char const 
   InitializeCriticalSection(&ctx->lock);
   ctx->active_streams = 0;
 
   *context = ctx;
 
   return CUBEB_OK;
 }
 
+char const *
+cubeb_get_backend_id(cubeb * ctx)
+{
+  return "winmm";
+}
+
 void
 cubeb_destroy(cubeb * ctx)
 {
   DWORD rv;
 
   assert(ctx->active_streams == 0);
   assert(!InterlockedPopEntrySList(ctx->work));