Bug 1405258 - Update libcubeb to revision ba2a89611875cd9f2dabae99a362461b03c0dd3d
authorPaul Adenot <paul@paul.cx>
Tue, 03 Oct 2017 12:18:07 +0200
changeset 426754 3bdc753d11f0efd26d6ba5cdd0953919be7e9d26
parent 426753 ba149aead55314a6b9094aef39dfd0c8b0439c5a
child 426755 506cddcf82b557a8d01ef67f608d0ba988a7088d
push id97
push userfmarier@mozilla.com
push dateSat, 14 Oct 2017 01:12:59 +0000
bugs1405258
milestone58.0a1
Bug 1405258 - Update libcubeb to revision ba2a89611875cd9f2dabae99a362461b03c0dd3d MozReview-Commit-ID: PngLiVneAh
media/libcubeb/README_MOZILLA
media/libcubeb/gtest/test_resampler.cpp
media/libcubeb/src/cubeb_audiounit.cpp
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/src/cubeb_resampler.cpp
media/libcubeb/src/cubeb_resampler_internal.h
--- 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 ac532ad4f0defaf7a397db64c2c42d2665cd06e9 (2017-09-17 08:23:45 +1200)
+The git commit ID used was ba2a89611875cd9f2dabae99a362461b03c0dd3d (2017-10-03 11:34:20 +0200)
--- a/media/libcubeb/gtest/test_resampler.cpp
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -120,17 +120,17 @@ int16_t epsilon(float ratio) {
   return static_cast<int16_t>(10 * epsilon_tweak_ratio(ratio));
 }
 
 void test_delay_lines(uint32_t delay_frames, uint32_t channels, uint32_t chunk_ms)
 {
   const size_t length_s = 2;
   const size_t rate = 44100;
   const size_t length_frames = rate * length_s;
-  delay_line<float> delay(delay_frames, channels);
+  delay_line<float> delay(delay_frames, channels, rate);
   auto_array<float> input;
   auto_array<float> output;
   uint32_t chunk_length = channels * chunk_ms * rate / 1000;
   uint32_t output_offset = 0;
   uint32_t channel = 0;
 
   /** Generate diracs every 100 frames, and check they are delayed. */
   input.push_silence(length_frames * channels);
@@ -624,60 +624,89 @@ TEST(cubeb, resampler_passthrough_input_
   }
 
   cubeb_resampler_destroy(resampler);
 }
 
 template<typename T>
 long seq(T* array, int stride, long start, long count)
 {
+  uint32_t output_idx = 0;
   for(int i = 0; i < count; i++) {
     for (int j = 0; j < stride; j++) {
-      array[i + j] = static_cast<T>(start + i);
+      array[output_idx + j] = static_cast<T>(start + i);
     }
+    output_idx += stride;
   }
   return start + count;
 }
 
 template<typename T>
 void is_seq(T * array, int stride, long count, long expected_start)
 {
   uint32_t output_index = 0;
   for (long i = 0; i < count; i++) {
     for (int j = 0; j < stride; j++) {
       ASSERT_EQ(array[output_index + j], expected_start + i);
     }
     output_index += stride;
   }
 }
 
+template<typename T>
+void is_not_seq(T * array, int stride, long count, long expected_start)
+{
+  uint32_t output_index = 0;
+  for (long i = 0; i < count; i++) {
+    for (int j = 0; j < stride; j++) {
+      ASSERT_NE(array[output_index + j], expected_start + i);
+    }
+    output_index += stride;
+  }
+}
+
+struct closure {
+  int input_channel_count;
+};
+
 // gtest does not support using ASSERT_EQ and friend in a function that returns
 // a value.
 template<typename T>
 void check_duplex(const T * input_buffer,
-                  T * output_buffer, long frame_count)
+                  T * output_buffer, long frame_count,
+                  int input_channel_count)
 {
   ASSERT_EQ(frame_count, 256);
   // Silence scan-build warning.
   ASSERT_TRUE(!!output_buffer); assert(output_buffer);
   ASSERT_TRUE(!!input_buffer); assert(input_buffer);
 
   int output_index = 0;
+  int input_index = 0;
   for (int i = 0; i < frame_count; i++) {
-    // output is two channels, input is one channel, we upmix.
-    output_buffer[output_index] = output_buffer[output_index+1] = input_buffer[i];
+    // output is two channels, input one or two channels.
+    if (input_channel_count == 1) {
+      output_buffer[output_index] = output_buffer[output_index + 1] = input_buffer[i];
+    } else if (input_channel_count == 2) {
+      output_buffer[output_index] = input_buffer[input_index];
+      output_buffer[output_index + 1] = input_buffer[input_index + 1];
+    }
     output_index += 2;
+    input_index += input_channel_count;
   }
 }
 
-long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * /*user_ptr*/,
+long cb_passthrough_resampler_duplex(cubeb_stream * /*stm*/, void * user_ptr,
                                      const void * input_buffer,
                                      void * output_buffer, long frame_count)
 {
-  check_duplex<float>(static_cast<const float*>(input_buffer), static_cast<float*>(output_buffer), frame_count);
+  closure * c = reinterpret_cast<closure*>(user_ptr);
+  check_duplex<float>(static_cast<const float*>(input_buffer),
+                      static_cast<float*>(output_buffer),
+                      frame_count, c->input_channel_count);
   return frame_count;
 }
 
 
 TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
 {
   // Test that when pre-buffering on resampler creation, we can survive an input
   // callback being delayed.
@@ -693,19 +722,22 @@ TEST(cubeb, resampler_passthrough_duplex
   input_params.format = CUBEB_SAMPLE_FLOAT32NE;
 
   output_params.channels = output_channels;
   output_params.rate = input_params.rate;
   output_params.format = CUBEB_SAMPLE_FLOAT32NE;
 
   int target_rate = input_params.rate;
 
+  closure c;
+  c.input_channel_count = input_channels;
+
   cubeb_resampler * resampler =
     cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
-                           target_rate, cb_passthrough_resampler_duplex, nullptr,
+                           target_rate, cb_passthrough_resampler_duplex, &c,
                            CUBEB_RESAMPLER_QUALITY_VOIP);
 
   const long BUF_BASE_SIZE = 256;
   float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
   float input_buffer_glitch[input_channels * BUF_BASE_SIZE * 2];
   float input_buffer_normal[input_channels * BUF_BASE_SIZE];
   float output_buffer[output_channels * BUF_BASE_SIZE];
 
@@ -750,8 +782,106 @@ TEST(cubeb, resampler_passthrough_duplex
       is_seq(output_buffer, 2, BUF_BASE_SIZE, output_seq_idx);
       output_seq_idx += BUF_BASE_SIZE;
     }
     ASSERT_EQ(got, BUF_BASE_SIZE);
   }
 
   cubeb_resampler_destroy(resampler);
 }
+
+// Artificially simulate output thread underruns,
+// by building up artificial delay in the input.
+// Check that the frame drop logic kicks in.
+TEST(cubeb, resampler_drift_drop_data)
+{
+  for (uint32_t input_channels = 1; input_channels < 3; input_channels++) {
+    cubeb_stream_params input_params;
+    cubeb_stream_params output_params;
+
+    const int output_channels = 2;
+    const int sample_rate = 44100;
+
+    input_params.channels = input_channels;
+    input_params.rate = sample_rate;
+    input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+    output_params.channels = output_channels;
+    output_params.rate = sample_rate;
+    output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+
+    int target_rate = input_params.rate;
+
+    closure c;
+    c.input_channel_count = input_channels;
+
+    cubeb_resampler * resampler =
+      cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
+        target_rate, cb_passthrough_resampler_duplex, &c,
+        CUBEB_RESAMPLER_QUALITY_VOIP);
+
+    const long BUF_BASE_SIZE = 256;
+
+    // The factor by which the deadline is missed. This is intentionally
+    // kind of large to trigger the frame drop quickly. In real life, multiple
+    // smaller under-runs would accumulate.
+    const long UNDERRUN_FACTOR = 10;
+    // Number buffer used for pre-buffering, that some backends do.
+    const long PREBUFFER_FACTOR = 2;
+
+    std::vector<float> input_buffer_prebuffer(input_channels * BUF_BASE_SIZE * PREBUFFER_FACTOR);
+    std::vector<float> input_buffer_glitch(input_channels * BUF_BASE_SIZE * UNDERRUN_FACTOR);
+    std::vector<float> input_buffer_normal(input_channels * BUF_BASE_SIZE);
+    std::vector<float> output_buffer(output_channels * BUF_BASE_SIZE);
+
+    long seq_idx = 0;
+    long output_seq_idx = 0;
+
+    long prebuffer_frames = input_buffer_prebuffer.size() / input_params.channels;
+    seq_idx = seq(input_buffer_prebuffer.data(), input_channels, seq_idx,
+      prebuffer_frames);
+
+    long got = cubeb_resampler_fill(resampler, input_buffer_prebuffer.data(), &prebuffer_frames,
+      output_buffer.data(), BUF_BASE_SIZE);
+
+    output_seq_idx += BUF_BASE_SIZE;
+
+    // prebuffer_frames will hold the frames used by the resampler.
+    ASSERT_EQ(prebuffer_frames, BUF_BASE_SIZE);
+    ASSERT_EQ(got, BUF_BASE_SIZE);
+
+    for (uint32_t i = 0; i < 300; i++) {
+      long int frames = BUF_BASE_SIZE;
+      if (i != 0 && (i % 100) == 1) {
+        // Once in a while, the output thread misses its deadline.
+        // The input thread still produces data, so it ends up accumulating. Simulate this by providing a
+        // much bigger input buffer. Check that the sequence is now unaligned, meaning we've dropped data
+        // to keep everything in sync.
+        seq_idx = seq(input_buffer_glitch.data(), input_channels, seq_idx, BUF_BASE_SIZE * UNDERRUN_FACTOR);
+        frames = BUF_BASE_SIZE * UNDERRUN_FACTOR;
+        got = cubeb_resampler_fill(resampler, input_buffer_glitch.data(), &frames, output_buffer.data(), BUF_BASE_SIZE);
+        is_seq(output_buffer.data(), 2, BUF_BASE_SIZE, output_seq_idx);
+        output_seq_idx += BUF_BASE_SIZE;
+      }
+      else if (i != 0 && (i % 100) == 2) {
+        // On the next iteration, the sequence should be broken
+        seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+        long normal_input_frame_count = 256;
+        got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+        is_not_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+        // Reclock so that we can use is_seq again.
+        output_seq_idx = output_buffer[BUF_BASE_SIZE * output_channels - 1] + 1;
+      }
+      else {
+        // normal case
+        seq_idx = seq(input_buffer_normal.data(), input_channels, seq_idx, BUF_BASE_SIZE);
+        long normal_input_frame_count = 256;
+        got = cubeb_resampler_fill(resampler, input_buffer_normal.data(), &normal_input_frame_count, output_buffer.data(), BUF_BASE_SIZE);
+        is_seq(output_buffer.data(), output_channels, BUF_BASE_SIZE, output_seq_idx);
+        output_seq_idx += BUF_BASE_SIZE;
+      }
+      ASSERT_EQ(got, BUF_BASE_SIZE);
+    }
+
+    cubeb_resampler_destroy(resampler);
+  }
+}
+
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -571,17 +571,17 @@ audiounit_get_backend_id(cubeb * /* ctx 
 
 #if !TARGET_OS_IPHONE
 
 static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
 static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
 static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
 static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
 
-static void
+static int
 audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
 {
   assert(stm);
 
   device_info * info = nullptr;
   cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
 
   if (side == INPUT) {
@@ -596,28 +596,33 @@ audiounit_set_device_info(cubeb_stream *
 
   if (side == INPUT) {
     info->flags |= DEV_INPUT;
   } else if (side == OUTPUT) {
     info->flags |= DEV_OUTPUT;
   }
 
   AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
+  if (default_device_id == kAudioObjectUnknown) {
+    return CUBEB_ERROR;
+  }
   if (id == kAudioObjectUnknown) {
     info->id = default_device_id;
     info->flags |= DEV_SELECTED_DEFAULT;
   }
 
   if (info->id == default_device_id) {
     info->flags |= DEV_SYSTEM_DEFAULT;
   }
 
   assert(info->id);
   assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
            !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
+
+  return CUBEB_OK;
 }
 
 
 static int
 audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
 {
   auto_lock context_lock(stm->context->mutex);
   assert((flags & DEV_INPUT && stm->input_unit) ||
@@ -640,22 +645,24 @@ audiounit_reinit_stream(cubeb_stream * s
     }
 
     audiounit_close_stream(stm);
 
     /* Reinit occurs in 2 cases. When the device is not alive any more and when the
      * default system device change. In both cases cubeb switch on the new default
      * device. This is considered the most expected behavior for the user. */
     if (flags & DEV_INPUT) {
-      audiounit_set_device_info(stm, 0, INPUT);
+      r = audiounit_set_device_info(stm, 0, INPUT);
+      assert(r == CUBEB_OK);
     }
     /* Always use the default output on reinit. This is not correct in every case
      * but it is sufficient for Firefox and prevent reinit from reporting failures.
      * It will change soon when reinit mechanism will be updated. */
-    audiounit_set_device_info(stm, 0, OUTPUT);
+    r = audiounit_set_device_info(stm, 0, OUTPUT);
+    assert(r == CUBEB_OK);
 
     if (audiounit_setup_stream(stm) != CUBEB_OK) {
       LOG("(%p) Stream reinit failed.", stm);
       return CUBEB_ERROR;
     }
 
     if (vol_rv == CUBEB_OK) {
       audiounit_stream_set_volume(stm, volume);
@@ -2507,21 +2514,29 @@ audiounit_stream_init(cubeb * context,
   /* These could be different in the future if we have both
    * full-duplex stream and different devices for input vs output. */
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
   stm->latency_frames = latency_frames;
   if (input_stream_params) {
     stm->input_stream_params = *input_stream_params;
-    audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(input_device), INPUT);
+    r = audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(input_device), INPUT);
+    if (r != CUBEB_OK) {
+      LOG("(%p) Fail to set device info for input.", stm.get());
+      return r;
+    }
   }
   if (output_stream_params) {
     stm->output_stream_params = *output_stream_params;
-    audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(output_device), OUTPUT);
+    r = audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(output_device), OUTPUT);
+    if (r != CUBEB_OK) {
+      LOG("(%p) Fail to set device info for output.", stm.get());
+      return r;
+    }
   }
 
   auto_lock context_lock(context->mutex);
   {
     // It's not critical to lock here, because no other thread has been started
     // yet, but it allows to assert that the lock has been taken in
     // `audiounit_setup_stream`.
     context->active_streams += 1;
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -648,17 +648,16 @@ pulse_init(cubeb ** context, char const 
      mainloop to end the wait. */
   WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
   o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
   if (o) {
     operation_wait(ctx, NULL, o);
     WRAP(pa_operation_unref)(o);
   }
   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
-  assert(ctx->default_sink_info);
 
   *context = ctx;
 
   return CUBEB_OK;
 }
 
 static char const *
 pulse_get_backend_id(cubeb * ctx)
@@ -668,38 +667,47 @@ pulse_get_backend_id(cubeb * ctx)
 }
 
 static int
 pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
 {
   (void)ctx;
   assert(ctx && max_channels);
 
+  if (!ctx->default_sink_info)
+    return CUBEB_ERROR;
+
   *max_channels = ctx->default_sink_info->channel_map.channels;
 
   return CUBEB_OK;
 }
 
 static int
 pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
 {
   assert(ctx && rate);
   (void)ctx;
 
+  if (!ctx->default_sink_info)
+    return CUBEB_ERROR;
+
   *rate = ctx->default_sink_info->sample_spec.rate;
 
   return CUBEB_OK;
 }
 
 static int
 pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout)
 {
   assert(ctx && layout);
   (void)ctx;
 
+  if (!ctx->default_sink_info)
+    return CUBEB_ERROR;
+
   *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map);
 
   return CUBEB_OK;
 }
 
 static int
 pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
 {
@@ -1072,45 +1080,48 @@ volume_success(pa_context *c, int succes
 static int
 pulse_stream_set_volume(cubeb_stream * stm, float volume)
 {
   uint32_t index;
   pa_operation * op;
   pa_volume_t vol;
   pa_cvolume cvol;
   const pa_sample_spec * ss;
+  cubeb * ctx;
 
   if (!stm->output_stream) {
     return CUBEB_ERROR;
   }
 
   WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
 
   /* if the pulse daemon is configured to use flat volumes,
    * apply our own gain instead of changing the input volume on the sink. */
-  if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) {
+  ctx = stm->context;
+  if (ctx->default_sink_info &&
+      (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) {
     stm->volume = volume;
   } else {
     ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
 
     vol = WRAP(pa_sw_volume_from_linear)(volume);
     WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
 
     index = WRAP(pa_stream_get_index)(stm->output_stream);
 
-    op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
+    op = WRAP(pa_context_set_sink_input_volume)(ctx->context,
                                                 index, &cvol, volume_success,
                                                 stm);
     if (op) {
-      operation_wait(stm->context, stm->output_stream, op);
+      operation_wait(ctx, stm->output_stream, op);
       WRAP(pa_operation_unref)(op);
     }
   }
 
-  WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
+  WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
 
   return CUBEB_OK;
 }
 
 struct sink_input_info_result {
   pa_cvolume * cvol;
   pa_threaded_mainloop * mainloop;
 };
@@ -1230,17 +1241,22 @@ pulse_sink_info_cb(pa_context * context,
 {
   pulse_dev_list_data * list_data = user_data;
   cubeb_device_info * devinfo;
   char const * prop = NULL;
   char const * device_id = NULL;
 
   (void)context;
 
-  if (eol || info == NULL)
+  if (eol) {
+    WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
+    return;
+  }
+
+  if (info == NULL)
     return;
 
   device_id = info->name;
   if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
     assert(false);
     return;
   }
 
@@ -1269,18 +1285,16 @@ pulse_sink_info_cb(pa_context * context,
   devinfo->min_rate = 1;
   devinfo->max_rate = PA_RATE_MAX;
   devinfo->default_rate = info->sample_spec.rate;
 
   devinfo->latency_lo = 0;
   devinfo->latency_hi = 0;
 
   list_data->count += 1;
-
-  WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
 }
 
 static cubeb_device_state
 pulse_get_state_from_source_port(pa_source_port_info * info)
 {
   if (info != NULL) {
 #if PA_CHECK_VERSION(2, 0, 0)
     if (info->available == PA_PORT_AVAILABLE_NO)
@@ -1299,18 +1313,20 @@ pulse_source_info_cb(pa_context * contex
 {
   pulse_dev_list_data * list_data = user_data;
   cubeb_device_info * devinfo;
   char const * prop = NULL;
   char const * device_id = NULL;
 
   (void)context;
 
-  if (eol)
+  if (eol) {
+    WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
     return;
+  }
 
   device_id = info->name;
   if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) {
     assert(false);
     return;
   }
 
   pulse_ensure_dev_list_data_list_size(list_data);
@@ -1338,30 +1354,31 @@ pulse_source_info_cb(pa_context * contex
   devinfo->min_rate = 1;
   devinfo->max_rate = PA_RATE_MAX;
   devinfo->default_rate = info->sample_spec.rate;
 
   devinfo->latency_lo = 0;
   devinfo->latency_hi = 0;
 
   list_data->count += 1;
-  WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
 }
 
 static void
 pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
 {
   pulse_dev_list_data * list_data = userdata;
 
   (void)c;
 
   free(list_data->default_sink_name);
   free(list_data->default_source_name);
-  list_data->default_sink_name = strdup(i->default_sink_name);
-  list_data->default_source_name = strdup(i->default_source_name);
+  list_data->default_sink_name =
+    i->default_sink_name ? strdup(i->default_sink_name) : NULL;
+  list_data->default_source_name =
+    i->default_source_name ? strdup(i->default_source_name) : NULL;
 
   WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
 }
 
 static int
 pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
                         cubeb_device_collection * collection)
 {
--- a/media/libcubeb/src/cubeb_resampler.cpp
+++ b/media/libcubeb/src/cubeb_resampler.cpp
@@ -34,21 +34,23 @@ to_speex_quality(cubeb_resampler_quality
     return 0XFFFFFFFF;
   }
 }
 
 template<typename T>
 passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
                                                 cubeb_data_callback cb,
                                                 void * ptr,
-                                                uint32_t input_channels)
+                                                uint32_t input_channels,
+                                                uint32_t sample_rate)
   : processor(input_channels)
   , stream(s)
   , data_callback(cb)
   , user_ptr(ptr)
+  , sample_rate(sample_rate)
 {
 }
 
 template<typename T>
 long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
                                     void * output_buffer, long output_frames)
 {
   if (input_buffer) {
@@ -67,16 +69,17 @@ long passthrough_resampler<T>::fill(void
   }
 
   long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
                           output_buffer, output_frames);
 
   if (input_buffer) {
     internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
     *input_frames_count = output_frames;
+    drop_audio_if_needed();
   }
 
   return rv;
 }
 
 template<typename T, typename InputProcessor, typename OutputProcessor>
 cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
   ::cubeb_resampler_speex(InputProcessor * input_processor,
@@ -236,19 +239,25 @@ cubeb_resampler_speex<T, InputProcessor,
                       output_frames_before_processing);
 
   if (got < 0) {
     return got;
   }
 
   output_processor->written(got);
 
+  input_processor->drop_audio_if_needed();
+
   /* Process the output. If not enough frames have been returned from the
    * callback, drain the processors. */
-  return output_processor->output(out_buffer, output_frames_needed);
+  got = output_processor->output(out_buffer, output_frames_needed);
+
+  output_processor->drop_audio_if_needed();
+
+  return got;
 }
 
 /* Resampler C API */
 
 cubeb_resampler *
 cubeb_resampler_create(cubeb_stream * stream,
                        cubeb_stream_params * input_params,
                        cubeb_stream_params * output_params,
--- a/media/libcubeb/src/cubeb_resampler_internal.h
+++ b/media/libcubeb/src/cubeb_resampler_internal.h
@@ -33,16 +33,27 @@ MOZ_END_STD_NAMESPACE
 #endif
 #include "cubeb/cubeb.h"
 #include "cubeb_utils.h"
 #include "cubeb-speex-resampler.h"
 #include "cubeb_resampler.h"
 #include <stdio.h>
 
 /* This header file contains the internal C++ API of the resamplers, for testing. */
+namespace {
+// When dropping audio input frames to prevent building
+// an input delay, this function returns the number of frames
+// to keep in the buffer.
+// @parameter sample_rate The sample rate of the stream.
+// @return A number of frames to keep.
+uint32_t min_buffered_audio_frame(uint32_t sample_rate)
+{
+  return sample_rate / 20;
+}
+}
 
 int to_speex_quality(cubeb_resampler_quality q);
 
 struct cubeb_resampler {
   virtual long fill(void * input_buffer, long * input_frames_count,
                     void * output_buffer, long frames_needed) = 0;
   virtual long latency() = 0;
   virtual ~cubeb_resampler() {}
@@ -70,33 +81,44 @@ protected:
 
 template<typename T>
 class passthrough_resampler : public cubeb_resampler
                             , public processor {
 public:
   passthrough_resampler(cubeb_stream * s,
                         cubeb_data_callback cb,
                         void * ptr,
-                        uint32_t input_channels);
+                        uint32_t input_channels,
+                        uint32_t sample_rate);
 
   virtual long fill(void * input_buffer, long * input_frames_count,
                     void * output_buffer, long output_frames);
 
   virtual long latency()
   {
     return 0;
   }
 
+  void drop_audio_if_needed()
+  {
+    uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+    uint32_t available = samples_to_frames(internal_input_buffer.length());
+    if (available > to_keep) {
+      internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+    }
+  }
+
 private:
   cubeb_stream * const stream;
   const cubeb_data_callback data_callback;
   void * const user_ptr;
   /* This allows to buffer some input to account for the fact that we buffer
    * some inputs. */
   auto_array<T> internal_input_buffer;
+  uint32_t sample_rate;
 };
 
 /** Bidirectional resampler, can resample an input and an output stream, or just
  * an input stream or output stream. In this case a delay is inserted in the
  * opposite direction to keep the streams synchronized. */
 template<typename T, typename InputProcessing, typename OutputProcessing>
 class cubeb_resampler_speex : public cubeb_resampler {
 public:
@@ -159,16 +181,17 @@ public:
    * @parameter quality A number between 0 (fast, low quality) and 10 (slow,
    * high quality). */
   cubeb_resampler_speex_one_way(uint32_t channels,
                                 uint32_t source_rate,
                                 uint32_t target_rate,
                                 int quality)
   : processor(channels)
   , resampling_ratio(static_cast<float>(source_rate) / target_rate)
+  , source_rate(source_rate)
   , additional_latency(0)
   , leftover_samples(0)
   {
     int r;
     speex_resampler = speex_resampler_init(channels, source_rate,
                                            target_rate, quality, &r);
     assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
   }
@@ -291,16 +314,26 @@ public:
 
   /** This method works with `input_buffer`, and allows to inform the processor
       how much frames have been written in the provided buffer. */
   void written(size_t written_frames)
   {
     resampling_in_buffer.set_length(leftover_samples +
                                     frames_to_samples(written_frames));
   }
+
+  void drop_audio_if_needed()
+  {
+    // Keep at most 100ms buffered.
+    uint32_t available = samples_to_frames(resampling_in_buffer.length());
+    uint32_t to_keep = min_buffered_audio_frame(source_rate);
+    if (available > to_keep) {
+      resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+    }
+  }
 private:
   /** Wrapper for the speex resampling functions to have a typed
     * interface. */
   void speex_resample(float * input_buffer, uint32_t * input_frame_count,
                       float * output_buffer, uint32_t * output_frame_count)
   {
 #ifndef NDEBUG
     int rv;
@@ -327,16 +360,17 @@ private:
                                               output_buffer,
                                               output_frame_count);
     assert(rv == RESAMPLER_ERR_SUCCESS);
   }
   /** The state for the speex resampler used internaly. */
   SpeexResamplerState * speex_resampler;
   /** Source rate / target rate. */
   const float resampling_ratio;
+  const uint32_t source_rate;
   /** Storage for the input frames, to be resampled. Also contains
    * any unresampled frames after resampling. */
   auto_array<T> resampling_in_buffer;
   /* Storage for the resampled frames, to be passed back to the caller. */
   auto_array<T> resampling_out_buffer;
   /** Additional latency inserted into the pipeline for synchronisation. */
   uint32_t additional_latency;
   /** When `input_buffer` is called, this allows tracking the number of samples
@@ -345,21 +379,23 @@ private:
 };
 
 /** This class allows delaying an audio stream by `frames` frames. */
 template<typename T>
 class delay_line : public processor {
 public:
   /** Constructor
    * @parameter frames the number of frames of delay.
-   * @parameter channels the number of channels of this delay line. */
-  delay_line(uint32_t frames, uint32_t channels)
+   * @parameter channels the number of channels of this delay line.
+   * @parameter sample_rate sample-rate of the audio going through this delay line */
+  delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
     : processor(channels)
     , length(frames)
     , leftover_samples(0)
+    , sample_rate(sample_rate)
   {
     /* Fill the delay line with some silent frames to add latency. */
     delay_input_buffer.push_silence(frames * channels);
   }
   /* Add some latency to the delay line.
    * @param frames the number of frames of latency to add. */
   void add_latency(size_t frames)
   {
@@ -439,27 +475,37 @@ public:
     return input_frames;
   }
   /** The number of frames this delay line delays the stream by.
    * @returns The number of frames of delay. */
   size_t latency()
   {
     return length;
   }
+
+  void drop_audio_if_needed()
+  {
+    size_t available = samples_to_frames(delay_input_buffer.length());
+    uint32_t to_keep = min_buffered_audio_frame(sample_rate);
+    if (available > to_keep) {
+      delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
+    }
+  }
 private:
   /** The length, in frames, of this delay line */
   uint32_t length;
   /** When `input_buffer` is called, this allows tracking the number of samples
       that where in the buffer. */
   uint32_t leftover_samples;
   /** The input buffer, where the delay is applied. */
   auto_array<T> delay_input_buffer;
   /** The output buffer. This is only ever used if using the ::output with a
    * single argument. */
   auto_array<T> delay_output_buffer;
+  uint32_t sample_rate;
 };
 
 /** This sits behind the C API and is more typed. */
 template<typename T>
 cubeb_resampler *
 cubeb_resampler_create_internal(cubeb_stream * stream,
                                 cubeb_stream_params * input_params,
                                 cubeb_stream_params * output_params,
@@ -480,17 +526,18 @@ cubeb_resampler_create_internal(cubeb_st
      sample rate, use a no-op resampler, that simply forwards the buffers to the
      callback. */
   if (((input_params && input_params->rate == target_rate) &&
       (output_params && output_params->rate == target_rate)) ||
       (input_params && !output_params && (input_params->rate == target_rate)) ||
       (output_params && !input_params && (output_params->rate == target_rate))) {
     return new passthrough_resampler<T>(stream, callback,
                                         user_ptr,
-                                        input_params ? input_params->channels : 0);
+                                        input_params ? input_params->channels : 0,
+                                        target_rate);
   }
 
   /* Determine if we need to resampler one or both directions, and create the
      resamplers. */
   if (output_params && (output_params->rate != target_rate)) {
     output_resampler.reset(
         new cubeb_resampler_speex_one_way<T>(output_params->channels,
                                              target_rate,
@@ -512,23 +559,25 @@ cubeb_resampler_create_internal(cubeb_st
     }
   }
 
   /* If we resample only one direction but we have a duplex stream, insert a
    * delay line with a length equal to the resampler latency of the
    * other direction so that the streams are synchronized. */
   if (input_resampler && !output_resampler && input_params && output_params) {
     output_delay.reset(new delay_line<T>(input_resampler->latency(),
-                                         output_params->channels));
+                                         output_params->channels,
+                                         output_params->rate));
     if (!output_delay) {
       return NULL;
     }
   } else if (output_resampler && !input_resampler && input_params && output_params) {
     input_delay.reset(new delay_line<T>(output_resampler->latency(),
-                                        input_params->channels));
+                                        input_params->channels,
+                                        output_params->rate));
     if (!input_delay) {
       return NULL;
     }
   }
 
   if (input_resampler && output_resampler) {
     return new cubeb_resampler_speex<T,
                                      cubeb_resampler_speex_one_way<T>,