Bug 899050 - Add rudimentary support for multichannel files when using WASAPI. r=kinetik
authorPaul Adenot <paul@paul.cx>
Sat, 12 Oct 2013 13:53:11 -0400
changeset 164406 18c8f9d64847dbd0f20b3e44dbddc757defd3b7a
parent 164384 40fbd6880056ff39979702152e1316dc631949e3
child 164407 331fb081db007dc3751d11ee6177bc11421b1215
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
bugs899050
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 899050 - Add rudimentary support for multichannel files when using WASAPI. r=kinetik
media/libcubeb/src/cubeb_wasapi.cpp
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -114,37 +114,43 @@ struct cubeb_stream
   HANDLE refill_event;
   /* Each cubeb_stream has its own thread. */
   HANDLE thread;
   uint64_t clock_freq;
   /* Maximum number of frames we can be requested in a callback. */
   uint32_t buffer_frame_count;
   /* Resampler instance. If this is !NULL, resampling should happen. */
   SpeexResamplerState * resampler;
-  /* Buffer to resample from, into the upmix buffer or the final buffer. */
+  /* Buffer to resample from, into the mix buffer or the final buffer. */
   float * resampling_src_buffer;
   /* Pointer to the function used to refill the buffer, depending
    * on the respective samplerate of the stream and the mix. */
   refill_function2 refill_function;
   /* Leftover frames handling, only used when resampling. */
   uint32_t leftover_frame_count;
   uint32_t leftover_frame_size;
   float * leftover_frames_buffer;
-  /* upmix buffer of size |buffer_frame_count * bytes_per_frame / 2|. */
-  float * upmix_buffer;
-  /* Number of bytes per frame. Prefer to use frames_to_bytes_before_upmix. */
+  /* Buffer used to downmix or upmix to the number of channels the mixer has.
+   * its size is |buffer_frame_count * bytes_per_frame * mixer_channels|. */
+  float * mix_buffer;
+  /* Number of bytes per frame. Prefer to use frames_to_bytes_before_mix. */
   uint8_t bytes_per_frame;
   /* True if the stream is draining. */
   bool draining;
 };
 
 namespace {
 bool should_upmix(cubeb_stream * stream)
 {
-  return stream->upmix_buffer;
+  return stream->mix_params.channels > stream->stream_params.channels;
+}
+
+bool should_downmix(cubeb_stream * stream)
+{
+  return stream->mix_params.channels < stream->stream_params.channels;
 }
 
 /* Upmix function, copies a mono channel in two interleaved
  * stereo channel. |out| has to be twice as long as |in| */
 template<typename T>
 void
 mono_to_stereo(T * in, long insamples, T * out)
 {
@@ -174,20 +180,33 @@ upmix(T * in, long inframes, T * out, in
     }
     for (int j = in_channels; j < out_channels; j++) {
       out[out_index + j] = 0.0;
     }
     out_index += out_channels;
   }
 }
 
+template<typename T>
+void
+downmix_to_stereo(T * in, long inframes, T * out, int32_t in_channels)
+{
+  /* We could use a downmix matrix here, applying mixing weight based on the
+   * channel, but directsound and winmm simply drop the channels that cannot be
+   * rendered by the hardware, so we do the same for consistency. */
+  for (int32_t i = 0; i < inframes; i++) {
+    out[i * 2] = in[i * in_channels];
+    out[i * 2 + 1] = in[i * in_channels + 1];
+  }
+}
+
 /* This returns the size of a frame in the stream,
  * before the eventual upmix occurs. */
 static size_t
-frame_to_bytes_before_upmix(cubeb_stream * stm, size_t frames)
+frame_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
 {
   size_t stream_frame_size = stm->stream_params.channels * sizeof(float);
   return stream_frame_size * frames;
 }
 
 void
 refill_with_resampling(cubeb_stream * stm, float * data, long frames_needed)
 {
@@ -197,91 +216,96 @@ refill_with_resampling(cubeb_stream * st
   float rate =
     static_cast<float>(stm->stream_params.rate) / stm->mix_params.rate;
 
   long before_resampling = frame_count_at_rate(frames_needed, rate);
 
   long frame_requested = before_resampling - stm->leftover_frame_count;
 
   size_t leftover_bytes =
-    frame_to_bytes_before_upmix(stm, stm->leftover_frame_count);
+    frame_to_bytes_before_mix(stm, stm->leftover_frame_count);
 
   /* Copy the previous leftover frames to the front of the buffer. */
   memcpy(stm->resampling_src_buffer, stm->leftover_frames_buffer, leftover_bytes);
   uint8_t * buffer_start = reinterpret_cast<uint8_t *>(
                                   stm->resampling_src_buffer) + leftover_bytes;
 
   long got = stm->data_callback(stm, stm->user_ptr, buffer_start, frame_requested);
 
   if (got != frame_requested) {
     stm->draining = true;
   }
 
   uint32_t in_frames = before_resampling;
   uint32_t out_frames = frames_needed;
 
-  /* if we need to upmix after resampling, resample into
-   * the upmix buffer to avoid a copy */
+  /* If we need to upmix after resampling, resample into the mix buffer to
+   * avoid a copy. */
   float * resample_dest;
-  if (should_upmix(stm)) {
-    resample_dest = stm->upmix_buffer;
+  if (should_upmix(stm) || should_downmix(stm)) {
+    resample_dest = stm->mix_buffer;
   } else {
     resample_dest = data;
   }
 
   speex_resampler_process_interleaved_float(stm->resampler,
                                             stm->resampling_src_buffer,
                                             &in_frames,
                                             resample_dest,
                                             &out_frames);
 
   /* Copy the leftover frames to buffer for the next time. */
   stm->leftover_frame_count = before_resampling - in_frames;
   size_t unresampled_bytes =
-    frame_to_bytes_before_upmix(stm, stm->leftover_frame_count);
+    frame_to_bytes_before_mix(stm, stm->leftover_frame_count);
 
   uint8_t * leftover_frames_start =
     reinterpret_cast<uint8_t *>(stm->resampling_src_buffer);
-  leftover_frames_start += frame_to_bytes_before_upmix(stm, in_frames);
+  leftover_frames_start += frame_to_bytes_before_mix(stm, in_frames);
 
   assert(stm->leftover_frame_count <= stm->leftover_frame_size);
   memcpy(stm->leftover_frames_buffer, leftover_frames_start, unresampled_bytes);
 
   /* If this is not true, there will be glitches.
    * It is alright to have produced less frames if we are draining, though. */
   assert(out_frames == frames_needed || stm->draining);
 
   if (should_upmix(stm)) {
     upmix(resample_dest, out_frames, data,
           stm->stream_params.channels, stm->mix_params.channels);
+  } else if (should_downmix(stm)) {
+    downmix_to_stereo(resample_dest, out_frames, data,
+                      stm->stream_params.channels);
   }
 }
 
 void
 refill(cubeb_stream * stm, float * data, long frames_needed)
 {
-  /* If we need to upmix after resampling, get the data into
-   * the upmix buffer to avoid a copy. */
+  /* If we need to upmix/downmix, get the data into the mix buffer to avoid a
+   * copy, then do the processing process. */
   float * dest;
-  if (should_upmix(stm)) {
-    dest = stm->upmix_buffer;
+  if (should_upmix(stm) || should_downmix(stm)) {
+    dest = stm->mix_buffer;
   } else {
     dest = data;
   }
 
   long got = stm->data_callback(stm, stm->user_ptr, dest, frames_needed);
 
   if (got != frames_needed) {
     LOG("draining.");
     stm->draining = true;
   }
 
   if (should_upmix(stm)) {
     upmix(dest, got, data,
           stm->stream_params.channels, stm->mix_params.channels);
+  } else {
+    downmix_to_stereo(dest, got, data, stm->stream_params.channels);
   }
 }
 
 static unsigned int __stdcall
 wasapi_stream_render_loop(LPVOID stream)
 {
   cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
 
@@ -509,23 +533,24 @@ wasapi_get_max_channel_count(cubeb * ctx
 
 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)
 {
-  /* Common case: the hardware supports stereo, and the stream is mono or
-   * stereo. Easy. */
-  if ((*mix_format)->nChannels == 2 &&
-      stream_params->channels <= 2) {
+  /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
+   * handled in the callback. */
+  if ((*mix_format)->nChannels == 2) {
     return;
   }
 
+  /* Otherwise, the hardware supports more than two channels. */
+
   /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
    * so the reinterpret_cast below should be safe. In practice, this is not
    * true, and we just want to bail out and let the rest of the code find a good
    * conversion path instead of trying to make WASAPI do it by itself.
    * [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
   if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
     return;
   }
@@ -554,17 +579,17 @@ handle_channel_layout(cubeb_stream * stm
   /* Check if wasapi will accept our channel layout request. */
   WAVEFORMATEX * closest;
   HRESULT hr = stm->client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
                                               *mix_format,
                                               &closest);
 
   if (hr == S_FALSE) {
     /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
-     * eventual upmix ourselve */
+     * eventual upmix/downmix ourselve */
     LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
     CoTaskMemFree(*mix_format);
     *mix_format = closest;
   } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
     /* Not supported, no suggestion, there is a bug somewhere. */
     assert(false && "Format not supported, and no suggestion from WASAPI.");
   } else if (hr == S_OK) {
     LOG("Requested format accepted by WASAPI.");
@@ -591,21 +616,16 @@ wasapi_stream_init(cubeb * context, cube
   }
 
   /* 30ms in shared mode is the minimum we can get when using WASAPI */
   if (latency < 30) {
     LOG("Latency too low: got %u (30ms minimum)", latency);
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
-  /* we don't support more that two channels for now. */
-  if (stream_params.channels > 2) {
-    return CUBEB_ERROR_INVALID_FORMAT;
-  }
-
   cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
 
   assert(stm);
 
   stm->context = context;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
@@ -675,17 +695,17 @@ wasapi_stream_init(cubeb * context, cube
       wasapi_stream_destroy(stm);
       return CUBEB_ERROR;
     }
 
     /* Get a little buffer so we can store the leftover frames,
      * that is, the samples not consumed by the resampler that we will end up
      * using next time the render callback is called. */
     stm->leftover_frame_size = static_cast<uint32_t>(ceilf(1 / resampling_rate * 2) + 1);
-    stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_upmix(stm, stm->leftover_frame_size));
+    stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_mix(stm, stm->leftover_frame_size));
 
     stm->refill_function = &refill_with_resampling;
   } else {
     stm->refill_function = &refill;
   }
 
   hr = stm->client->Initialize(AUDCLNT_SHAREMODE_SHARED,
                                AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
@@ -707,27 +727,27 @@ wasapi_stream_init(cubeb * context, cube
   if (FAILED(hr)) {
     LOG("Could not get the buffer size from the client.");
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   assert(stm->mix_params.channels >= 2);
 
-  if (stm->mix_params.channels != stm->stream_params.channels) {
-    stm->upmix_buffer = (float *) malloc(frame_to_bytes_before_upmix(stm, stm->buffer_frame_count));
+  if (should_upmix(stm) || should_downmix(stm)) {
+    stm->mix_buffer = (float *) malloc(frame_to_bytes_before_mix(stm, stm->buffer_frame_count));
   }
 
   /* If we are going to resample, we will end up needing a buffer
    * to resample from, because speex's resampler does not do
    * in-place processing. Of course we need to take the resampling
    * factor and the channel layout into account. */
   if (stm->resampler) {
     size_t frames_needed = static_cast<size_t>(frame_count_at_rate(stm->buffer_frame_count, resampling_rate));
-    stm->resampling_src_buffer = (float *)malloc(frame_to_bytes_before_upmix(stm, frames_needed));
+    stm->resampling_src_buffer = (float *)malloc(frame_to_bytes_before_mix(stm, frames_needed));
   }
 
   hr = stm->client->SetEventHandle(stm->refill_event);
   if (FAILED(hr)) {
     LOG("Could set the event handle for the client.");
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
@@ -778,17 +798,17 @@ void wasapi_stream_destroy(cubeb_stream 
   SafeRelease(stm->audio_clock);
 
   if (stm->resampler) {
     speex_resampler_destroy(stm->resampler);
   }
 
   free(stm->leftover_frames_buffer);
   free(stm->resampling_src_buffer);
-  free(stm->upmix_buffer);
+  free(stm->mix_buffer);
   free(stm);
   CoUninitialize();
 }
 
 int wasapi_stream_start(cubeb_stream * stm)
 {
   HRESULT hr;