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 151125 af8f1f9b11bee23cd188b423d588331683b1d2e3
parent 151124 6c1e6cff2e434366e8b9db02f3826363aae2c81d
child 151126 b77402bffaf7d75a91445277539956c8e16788c3
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerskinetik
bugs907817
milestone27.0a1
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
 };