Bug 907817 - Add a cubeb API to get a valid audio latency range per platform. r=kinetik
authorPaul Adenot <paul@paul.cx>
Thu, 17 Oct 2013 15:44:52 +0200
changeset 164940 af8f1f9b11bee23cd188b423d588331683b1d2e3
parent 164939 6c1e6cff2e434366e8b9db02f3826363aae2c81d
child 164941 b77402bffaf7d75a91445277539956c8e16788c3
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs907817
milestone27.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 907817 - Add a cubeb API to get a valid audio latency range per platform. r=kinetik
layout/media/symbols.def.in
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb-internal.h
media/libcubeb/src/cubeb.c
media/libcubeb/src/cubeb_alsa.c
media/libcubeb/src/cubeb_audiotrack.c
media/libcubeb/src/cubeb_audiounit.c
media/libcubeb/src/cubeb_opensl.c
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/src/cubeb_sndio.c
media/libcubeb/src/cubeb_wasapi.cpp
media/libcubeb/src/cubeb_winmm.c
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -110,16 +110,17 @@ speex_resampler_get_output_latency
 speex_resampler_skip_zeros
 speex_resampler_reset_mem
 speex_resampler_strerror
 #endif
 #ifdef MOZ_CUBEB
 cubeb_destroy
 cubeb_init
 cubeb_get_max_channel_count
+cubeb_get_min_latency
 cubeb_stream_destroy
 cubeb_stream_get_position
 cubeb_stream_init
 cubeb_stream_start
 cubeb_stream_stop
 cubeb_stream_get_latency
 #endif
 #ifdef MOZ_OGG
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -183,16 +183,27 @@ char const * cubeb_get_backend_id(cubeb 
 /** Get the maximum possible number of channels.
     @param context
     @param max_channels The maximum number of channels.
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER
     @retval CUBEB_ERROR */
 int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
 
+/** Get the minimal latency value, in milliseconds, that is guaranteed to work
+    when creating a stream for the specified samplerate. This is platform and
+    backend dependant.
+    @param context
+    @param params On some backends, the minimum achievable latency depends on
+                  the characteristics of the stream.
+    @param latency The latency value, in ms, to pass to cubeb_stream_init.
+    @retval CUBEB_ERROR_INVALID_PARAMETER
+    @retval CUBEB_OK */
+int cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms);
+
 /** 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-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -8,16 +8,19 @@
 #define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
 
 #include "cubeb/cubeb.h"
 
 struct cubeb_ops {
   int (* init)(cubeb ** context, char const * context_name);
   char const * (* get_backend_id)(cubeb * context);
   int (* get_max_channel_count)(cubeb * context, uint32_t * max_channels);
+  int (* get_min_latency)(cubeb * context,
+                          cubeb_stream_params params,
+                          uint32_t * latency_ms);
   void (* destroy)(cubeb * context);
   int (* stream_init)(cubeb * context, 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);
   void (* stream_destroy)(cubeb_stream * stream);
   int (* stream_start)(cubeb_stream * stream);
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -145,16 +145,25 @@ cubeb_get_max_channel_count(cubeb * cont
 {
   if (!context || !max_channels) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
   return context->ops->get_max_channel_count(context, max_channels);
 }
 
+int
+cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  if (!latency_ms || !params) {
+    return CUBEB_ERROR_INVALID_PARAMETER;
+  }
+  return context->ops->get_min_latency(context, params, latency_ms);
+}
+
 void
 cubeb_destroy(cubeb * context)
 {
   if (!context) {
     return;
   }
 
   context->ops->destroy(context);
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -924,16 +924,26 @@ alsa_get_max_channel_count(cubeb * ctx, 
     return CUBEB_ERROR;
   }
 
   alsa_stream_destroy(stm);
 
   return CUBEB_OK;
 }
 
+static int
+alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  /* This is found to be an acceptable minimum, even on a super low-end
+  * machine. */
+  *latency_ms = 40;
+
+  return CUBEB_OK;
+}
+
 
 static int
 alsa_stream_start(cubeb_stream * stm)
 {
   cubeb * ctx;
 
   assert(stm);
   ctx = stm->context;
@@ -1023,16 +1033,17 @@ alsa_stream_get_latency(cubeb_stream * s
 
   return CUBEB_OK;
 }
 
 static struct cubeb_ops const alsa_ops = {
   .init = alsa_init,
   .get_backend_id = alsa_get_backend_id,
   .get_max_channel_count = alsa_get_max_channel_count,
+  .get_min_latency = alsa_get_min_latency,
   .destroy = alsa_destroy,
   .stream_init = alsa_stream_init,
   .stream_destroy = alsa_stream_destroy,
   .stream_start = alsa_stream_start,
   .stream_stop = alsa_stream_stop,
   .stream_get_position = alsa_stream_get_position,
   .stream_get_latency = alsa_stream_get_latency
 };
--- a/media/libcubeb/src/cubeb_audiotrack.c
+++ b/media/libcubeb/src/cubeb_audiotrack.c
@@ -285,16 +285,34 @@ audiotrack_get_max_channel_count(cubeb *
 
   /* The android mixer handles up to two channels, see
   http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
   *max_channels = 2;
 
   return CUBEB_OK;
 }
 
+static int
+audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  /* We always use the lowest latency possible when using this backend (see
+   * audiotrack_stream_init), so this value is not going to be used. */
+  int rv;
+
+  rv = audiotrack_get_min_frame_count(ctx, &params, latency_ms);
+  if (rv != CUBEB_OK) {
+    return CUBEB_ERROR;
+  }
+
+  /* Convert to milliseconds. */
+  *latency_ms = *latency_ms * 1000 / params.rate;
+
+  return CUBEB_OK;
+}
+
 void
 audiotrack_destroy(cubeb * context)
 {
   assert(context);
 
   dlclose(context->library);
 
   free(context);
@@ -438,16 +456,17 @@ audiotrack_stream_get_latency(cubeb_stre
 
   return 0;
 }
 
 static struct cubeb_ops const audiotrack_ops = {
   .init = audiotrack_init,
   .get_backend_id = audiotrack_get_backend_id,
   .get_max_channel_count = audiotrack_get_max_channel_count,
+  .get_min_latency = audiotrack_get_min_latency,
   .destroy = audiotrack_destroy,
   .stream_init = audiotrack_stream_init,
   .stream_destroy = audiotrack_stream_destroy,
   .stream_start = audiotrack_stream_start,
   .stream_stop = audiotrack_stream_stop,
   .stream_get_position = audiotrack_stream_get_position,
   .stream_get_latency = audiotrack_stream_get_latency
 };
--- a/media/libcubeb/src/cubeb_audiounit.c
+++ b/media/libcubeb/src/cubeb_audiounit.c
@@ -80,17 +80,17 @@ audiounit_output_callback(void * user_pt
     }
     return noErr;
   }
 
   pthread_mutex_unlock(&stm->mutex);
   got = stm->data_callback(stm, stm->user_ptr, buf, nframes);
   pthread_mutex_lock(&stm->mutex);
   if (got < 0) {
-    // XXX handle this case.
+    /* XXX handle this case. */
     assert(false);
     pthread_mutex_unlock(&stm->mutex);
     return noErr;
   }
 
   if ((UInt32) got < nframes) {
     size_t got_bytes = got * stm->sample_spec.mBytesPerFrame;
     size_t rem_bytes = (nframes - got) * stm->sample_spec.mBytesPerFrame;
@@ -125,16 +125,75 @@ audiounit_init(cubeb ** context, char co
 }
 
 static char const *
 audiounit_get_backend_id(cubeb * ctx)
 {
   return "audiounit";
 }
 
+static int
+audiounit_get_output_device_id(AudioDeviceID * device_id)
+{
+  UInt32 size;
+  OSStatus r;
+  AudioObjectPropertyAddress output_device_address = {
+    kAudioHardwarePropertyDefaultOutputDevice,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  size = sizeof(device_id);
+
+  r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+                                 &output_device_address,
+                                 0,
+                                 0,
+                                 &size,
+                                 &device_id);
+  if (r != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  return CUBEB_OK;
+}
+
+/* Get the acceptable buffer size (in frames) that this device can work with. */
+static int
+audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
+{
+  UInt32 size;
+  OSStatus r;
+  AudioDeviceID output_device_id;
+  AudioObjectPropertyAddress output_device_buffer_size_range = {
+    kAudioDevicePropertyBufferFrameSizeRange,
+    kAudioDevicePropertyScopeOutput,
+    kAudioObjectPropertyElementMaster
+  };
+
+  if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+    return CUBEB_ERROR;
+  }
+
+  /* Get the buffer size range this device supports */
+  size = sizeof(*latency_range);
+
+  r = AudioObjectGetPropertyData(output_device_id,
+                                 &output_device_buffer_size_range,
+                                 0,
+                                 NULL,
+                                 &size,
+                                 latency_range);
+  if (r != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  return CUBEB_OK;
+}
+
 
 int
 audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
 {
   UInt32 size;
   OSStatus r;
   AudioDeviceID output_device_id;
   AudioStreamBasicDescription stream_format;
@@ -146,27 +205,17 @@ audiounit_get_max_channel_count(cubeb * 
   AudioObjectPropertyAddress stream_format_address = {
     kAudioDevicePropertyStreamFormat,
     kAudioDevicePropertyScopeOutput,
     kAudioObjectPropertyElementMaster
   };
 
   assert(ctx && max_channels);
 
-  /* Get the output device. */
-  size = sizeof(output_device_id);
-
-  r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
-                                 &output_device_address,
-                                 0,
-                                 0,
-                                 &size,
-                                 &output_device_id);
-
-  if (r != noErr) {
+  if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
     return CUBEB_ERROR;
   }
 
   size = sizeof(stream_format);
 
   r = AudioObjectGetPropertyData(output_device_id,
                                  &stream_format_address,
                                  0,
@@ -177,16 +226,30 @@ audiounit_get_max_channel_count(cubeb * 
     return CUBEB_ERROR;
   }
 
   *max_channels = stream_format.mChannelsPerFrame;
 
   return CUBEB_OK;
 }
 
+static int
+audiounit_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  AudioValueRange latency_range;
+
+  if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {
+    return CUBEB_ERROR;
+  }
+
+  *latency_ms = latency_range.mMinimum * 1000 / params.rate;
+
+  return CUBEB_OK;
+}
+
 static void
 audiounit_destroy(cubeb * ctx)
 {
   free(ctx);
 }
 
 static void audiounit_stream_destroy(cubeb_stream * stm);
 
@@ -204,27 +267,18 @@ audiounit_stream_init(cubeb * context, c
   AudioComponentDescription desc;
   AudioComponent comp;
 #endif
   cubeb_stream * stm;
   AURenderCallbackStruct input;
   unsigned int buffer_size;
   OSStatus r;
   UInt32 size;
-  AudioObjectPropertyAddress output_device_address = {
-    kAudioHardwarePropertyDefaultOutputDevice,
-    kAudioObjectPropertyScopeGlobal,
-    kAudioObjectPropertyElementMaster
-  };
-  AudioObjectPropertyAddress output_device_buffer_size_range = {
-    kAudioDevicePropertyBufferFrameSizeRange,
-    kAudioObjectPropertyScopeOutput,
-    kAudioObjectPropertyElementMaster
-  };
   AudioDeviceID output_device_id;
+  AudioValueRange latency_range;
 
 
   assert(context);
   *stream = NULL;
 
   memset(&ss, 0, sizeof(ss));
   ss.mFormatFlags = 0;
 
@@ -306,49 +360,27 @@ audiounit_stream_init(cubeb * context, c
                            kAudioUnitScope_Global, 0, &input, sizeof(input));
   if (r != 0) {
     audiounit_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   buffer_size = latency / 1000.0 * ss.mSampleRate;
 
-  size = sizeof(output_device_id);
-  /* Get the default output device id. */
-  r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
-                                 &output_device_address,
-                                 0,
-                                 0,
-                                 &size,
-                                 &output_device_id);
-
-  if (r != 0) {
+  /* Get the range of latency this particular device can work with, and clamp
+   * the requested latency to this acceptable range. */
+  if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {
     audiounit_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
-  /* Get the buffer size range this device supports and clamp the supplied
-   * latency to an acceptable value */
-  AudioValueRange buffer_size_range;
-  size = sizeof(buffer_size_range);
-
-  r = AudioObjectGetPropertyData(output_device_id,
-                                 &output_device_buffer_size_range,
-                                 0,
-                                 NULL,
-                                 &size,
-                                 &buffer_size_range);
-  if (r != 0) {
-    audiounit_stream_destroy(stm);
-    return CUBEB_ERROR;
-  }
-  if (buffer_size < (unsigned int) buffer_size_range.mMinimum) {
-    buffer_size = (unsigned int) buffer_size_range.mMinimum;
-  } else if (buffer_size > (unsigned int) buffer_size_range.mMaximum) {
-    buffer_size = (unsigned int) buffer_size_range.mMaximum;
+  if (buffer_size < (unsigned int) latency_range.mMinimum) {
+    buffer_size = (unsigned int) latency_range.mMinimum;
+  } else if (buffer_size > (unsigned int) latency_range.mMaximum) {
+    buffer_size = (unsigned int) latency_range.mMaximum;
   }
 
   /* Set the maximum number of frame that the render callback will ask for,
    * effectively setting the latency of the stream. This is process-wide. */
   r = AudioUnitSetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize,
                            kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size));
   if (r != 0) {
     audiounit_stream_destroy(stm);
@@ -496,33 +528,34 @@ audiounit_stream_get_latency(cubeb_strea
                                    NULL,
                                    &size,
                                    &device_safety_offset);
     if (r != noErr) {
       pthread_mutex_unlock(&stm->mutex);
       return CUBEB_ERROR;
     }
 
-    // This part is fixed and depend on the stream parameter and the hardware.
+    /* This part is fixed and depend on the stream parameter and the hardware. */
     stm->hw_latency_frames =
       (uint32_t)(unit_latency_sec * stm->sample_spec.mSampleRate)
       + device_latency_frames
       + device_safety_offset;
   }
 
   *latency = stm->hw_latency_frames + stm->current_latency_frames;
   pthread_mutex_unlock(&stm->mutex);
 
   return CUBEB_OK;
 }
 
 static struct cubeb_ops const audiounit_ops = {
   .init = audiounit_init,
   .get_backend_id = audiounit_get_backend_id,
   .get_max_channel_count = audiounit_get_max_channel_count,
+  .get_min_latency = audiounit_get_min_latency,
   .destroy = audiounit_destroy,
   .stream_init = audiounit_stream_init,
   .stream_destroy = audiounit_stream_destroy,
   .stream_start = audiounit_stream_start,
   .stream_stop = audiounit_stream_stop,
   .stream_get_position = audiounit_stream_get_position,
   .stream_get_latency = audiounit_stream_get_latency
 };
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -218,16 +218,70 @@ opensl_get_max_channel_count(cubeb * ctx
   assert(ctx && max_channels);
   /* The android mixer handles up to two channels, see
   http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
   *max_channels = 2;
 
   return CUBEB_OK;
 }
 
+static int
+opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
+   * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
+   * so we just dlopen the library and get the two symbols we need. */
+
+  int rv;
+  void * libmedia;
+  uint32_t (*get_primary_output_samplingrate)(void);
+  size_t (*get_primary_output_frame_count)(void);
+  uint32_t primary_sampling_rate;
+  size_t primary_buffer_size;
+
+  libmedia = dlopen("libmedia.so", RTLD_LAZY);
+  if (!libmedia) {
+    return CUBEB_ERROR;
+  }
+
+  /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */
+  get_primary_output_samplingrate =
+    dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv");
+  if (!get_primary_output_samplingrate) {
+    dlclose(libmedia);
+    return CUBEB_ERROR;
+  }
+
+  /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */
+  get_primary_output_frame_count =
+    dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv");
+  if (!get_primary_output_frame_count) {
+    dlclose(libmedia);
+    return CUBEB_ERROR;
+  }
+
+  primary_sampling_rate = get_primary_output_samplingrate();
+  primary_buffer_size = get_primary_output_frame_count();
+
+  /* To get a fast track in Android's mixer, we need to be at the native
+   * samplerate, which is device dependant. Some devices might be able to
+   * resample when playing a fast track, but it's pretty rare. */
+  if (primary_sampling_rate != params.rate) {
+    /* If we don't know what to use, let's say 4 * 20ms buffers will do. */
+    *latency_ms = NBUFS * 20;
+  } else {
+    *latency_ms = NBUFS * primary_buffer_size / (primary_sampling_rate / 1000);
+  }
+
+  dlclose(libmedia);
+
+  return CUBEB_OK;
+}
+
+
 static void
 opensl_destroy(cubeb * ctx)
 {
   if (ctx->outmixObj)
     (*ctx->outmixObj)->Destroy(ctx->outmixObj);
   if (ctx->engObj)
     (*ctx->engObj)->Destroy(ctx->engObj);
   dlclose(ctx->lib);
@@ -283,17 +337,20 @@ opensl_stream_init(cubeb * ctx, cubeb_st
   stm->context = ctx;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
 
   stm->framesize = stream_params.channels * sizeof(int16_t);
   stm->bytespersec = stream_params.rate * stm->framesize;
   stm->queuebuf_len = (stm->bytespersec * latency) / (1000 * NBUFS);
-  stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
+  // round up to the next multiple of stm->framesize, if needed.
+  if (stm->queuebuf_len % stm->framesize) {
+    stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
+  }
   int i;
   for (i = 0; i < NBUFS; i++) {
     stm->queuebuf[i] = malloc(stm->queuebuf_len);
     assert(stm->queuebuf[i]);
   }
 
   SLDataLocator_BufferQueue loc_bufq;
   loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
@@ -420,16 +477,17 @@ opensl_stream_get_latency(cubeb_stream *
 
   return CUBEB_OK;
 }
 
 static struct cubeb_ops const opensl_ops = {
   .init = opensl_init,
   .get_backend_id = opensl_get_backend_id,
   .get_max_channel_count = opensl_get_max_channel_count,
+  .get_min_latency = opensl_get_min_latency,
   .destroy = opensl_destroy,
   .stream_init = opensl_stream_init,
   .stream_destroy = opensl_stream_destroy,
   .stream_start = opensl_stream_start,
   .stream_stop = opensl_stream_stop,
   .stream_get_position = opensl_stream_get_position,
   .stream_get_latency = opensl_stream_get_latency
 };
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -378,16 +378,25 @@ pulse_get_max_channel_count(cubeb * ctx,
     WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
   }
 
   *max_channels = ctx->default_sink_info->channel_map.channels;
 
   return CUBEB_OK;
 }
 
+static int
+pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t latency)
+{
+  // According to PulseAudio developers, this is a safe minimum.
+  *latency = 40;
+
+  return CUBEB_OK;
+}
+
 static void
 pulse_destroy(cubeb * ctx)
 {
   pa_operation * o;
 
   if (ctx->context) {
     WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
     o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
@@ -587,16 +596,17 @@ pulse_stream_get_latency(cubeb_stream * 
   *latency = r_usec * stm->sample_spec.rate / PA_USEC_PER_SEC;
   return CUBEB_OK;
 }
 
 static struct cubeb_ops const pulse_ops = {
   .init = pulse_init,
   .get_backend_id = pulse_get_backend_id,
   .get_max_channel_count = pulse_get_max_channel_count,
+  .get_min_latency = pulse_get_min_latency,
   .destroy = pulse_destroy,
   .stream_init = pulse_stream_init,
   .stream_destroy = pulse_stream_destroy,
   .stream_start = pulse_stream_start,
   .stream_stop = pulse_stream_stop,
   .stream_get_position = pulse_stream_get_position,
   .stream_get_latency = pulse_stream_get_latency
 };
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -253,16 +253,25 @@ sndio_get_max_channel_count(cubeb * ctx,
 {
   assert(ctx && max_channels);
 
   *max_channels = 8;
 
   return CUBEB_OK;
 }
 
+static int
+sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  // XXX Not yet implemented.
+  latency_ms = 40;
+
+  return CUBEB_OK;
+}
+
 static void
 sndio_stream_destroy(cubeb_stream *s)
 {
   DPR("sndio_stream_destroy()\n");
   sio_close(s->hdl);
   free(s);
 }
 
@@ -321,16 +330,18 @@ sndio_stream_get_latency(cubeb_stream * 
   // in the "Measuring the latency and buffers usage" paragraph.
   *latency = stm->wrpos - stm->rdpos;
   return CUBEB_OK;
 }
 
 static struct cubeb_ops const sndio_ops = {
   .init = sndio_init,
   .get_backend_id = sndio_get_backend_id,
+  .get_max_channel_count = sndio_get_max_channel_count,
+  .get_min_latency = sndio_get_min_latency,
   .destroy = sndio_destroy,
   .stream_init = sndio_stream_init,
   .stream_destroy = sndio_stream_destroy,
   .stream_start = sndio_stream_start,
   .stream_stop = sndio_stream_stop,
   .stream_get_position = sndio_stream_get_position,
   .stream_get_latency = sndio_stream_get_latency
 };
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -35,16 +35,22 @@
 
 namespace {
 uint32_t
 ms_to_hns(uint32_t ms)
 {
   return ms * 10000;
 }
 
+uint32_t
+hns_to_ms(uint32_t hns)
+{
+  return hns / 10000;
+}
+
 double
 hns_to_s(uint32_t hns)
 {
   return static_cast<double>(hns) / 10000000;
 }
 
 long
 frame_count_at_rate(long frame_count, float rate)
@@ -525,16 +531,44 @@ wasapi_get_max_channel_count(cubeb * ctx
   *max_channels = mix_format->nChannels;
 
   CoTaskMemFree(mix_format);
   SafeRelease(client);
 
   return CUBEB_OK;
 }
 
+int
+wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
+{
+  HRESULT hr;
+  IAudioClient * client;
+  REFERENCE_TIME default_period;
+
+  hr = ctx->device->Activate(__uuidof(IAudioClient),
+                             CLSCTX_INPROC_SERVER,
+                             NULL, (void **)&client);
+
+  if (FAILED(hr)) {
+    return CUBEB_ERROR;
+  }
+
+  /* The second parameter is for exclusive mode, that we don't use. */
+  hr= client->GetDevicePeriod(&default_period, NULL);
+
+  /* According to the docs, the best latency we can achieve is by synchronizing
+   * the stream and the engine.
+   * http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */
+  *latency_ms = hns_to_ms(default_period);
+
+  SafeRelease(client);
+
+  return CUBEB_OK;
+}
+
 
 void wasapi_stream_destroy(cubeb_stream * stm);
 
 /* Based on the mix format and the stream format, try to find a way to play what
  * the user requested. */
 static void
 handle_channel_layout(cubeb_stream * stm,  WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
 {
@@ -887,17 +921,18 @@ int wasapi_stream_get_latency(cubeb_stre
   *latency = static_cast<uint32_t>(latency_s * stm->stream_params.rate);
 
   return CUBEB_OK;
 }
 
 cubeb_ops const wasapi_ops = {
   /*.init =*/ wasapi_init,
   /*.get_backend_id =*/ wasapi_get_backend_id,
-  /*.get_max_channel_count*/ wasapi_get_max_channel_count,
+  /*.get_max_channel_count =*/ wasapi_get_max_channel_count,
+  /*.get_min_latency =*/ wasapi_get_min_latency,
   /*.destroy =*/ wasapi_destroy,
   /*.stream_init =*/ wasapi_stream_init,
   /*.stream_destroy =*/ wasapi_stream_destroy,
   /*.stream_start =*/ wasapi_stream_start,
   /*.stream_stop =*/ wasapi_stream_stop,
   /*.stream_get_position =*/ wasapi_stream_get_position,
   /*.stream_get_latency =*/ wasapi_stream_get_latency
  };
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -232,17 +232,17 @@ calculate_minimum_latency(void)
   mask = 0;
   VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
   VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
 
   if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
     return 200;
   }
 
-  return 0;
+  return 100;
 }
 
 static void winmm_destroy(cubeb * ctx);
 
 /*static*/ int
 winmm_init(cubeb ** context, char const * context_name)
 {
   cubeb * ctx;
@@ -503,30 +503,39 @@ winmm_stream_destroy(cubeb_stream * stm)
   EnterCriticalSection(&stm->context->lock);
   assert(stm->context->active_streams >= 1);
   stm->context->active_streams -= 1;
   LeaveCriticalSection(&stm->context->lock);
 
   free(stm);
 }
 
-int
+static int
 winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
 {
   MMRESULT rv;
   LPWAVEOUTCAPS waveout_caps;
   assert(ctx && max_channels);
 
   /* We don't support more than two channels in this backend. */
   *max_channels = 2;
 
   return CUBEB_OK;
 }
 
 static int
+winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
+{
+  // 100ms minimum, if we are not in a bizarre configuration.
+  *latency = ctx->minimum_latency;
+
+  return CUBEB_OK;
+}
+
+static int
 winmm_stream_start(cubeb_stream * stm)
 {
   MMRESULT r;
 
   EnterCriticalSection(&stm->lock);
   r = waveOutRestart(stm->waveout);
   LeaveCriticalSection(&stm->lock);
 
@@ -572,17 +581,17 @@ winmm_stream_get_position(cubeb_stream *
     return CUBEB_ERROR;
   }
 
   *position = time.u.sample;
 
   return CUBEB_OK;
 }
 
-int
+static int
 winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
 {
   MMRESULT r;
   MMTIME time;
   uint64_t written;
 
   EnterCriticalSection(&stm->lock);
   time.wType = TIME_SAMPLES;
@@ -594,16 +603,17 @@ winmm_stream_get_latency(cubeb_stream * 
 
   return CUBEB_OK;
 }
 
 static struct cubeb_ops const winmm_ops = {
   /*.init =*/ winmm_init,
   /*.get_backend_id =*/ winmm_get_backend_id,
   /*.get_max_channel_count=*/ winmm_get_max_channel_count,
+  /*.get_min_latency=*/ winmm_get_min_latency,
   /*.destroy =*/ winmm_destroy,
   /*.stream_init =*/ winmm_stream_init,
   /*.stream_destroy =*/ winmm_stream_destroy,
   /*.stream_start =*/ winmm_stream_start,
   /*.stream_stop =*/ winmm_stream_stop,
   /*.stream_get_position =*/ winmm_stream_get_position,
   /*.stream_get_latency = */ winmm_stream_get_latency
 };