Bug 929009 - Support mono configurations in WASAPI backend. r=padenot, a=bajaj
authorMatthew Gregan <kinetik@flim.org>
Tue, 12 Nov 2013 15:48:29 +1300
changeset 166624 02294155afa93afd999f259abfd72c700cb0fe7b
parent 166623 2a3e0cf45dbfec2895f6094e2981441cedc56e97
child 166625 a231b9eade564f5c4524db8c2408a6fb106ff2aa
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)
reviewerspadenot, bajaj
bugs929009
milestone27.0a2
Bug 929009 - Support mono configurations in WASAPI backend. r=padenot, a=bajaj
media/libcubeb/src/cubeb_wasapi.cpp
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -1,14 +1,15 @@
 /*
  * Copyright  2013 Mozilla Foundation
  *
  * This program is made available under an ISC-style license.  See the
  * accompanying file LICENSE for details.
  */
+#undef NDEBUG
 #if defined(HAVE_CONFIG_H)
 #include "config.h"
 #endif
 #include <assert.h>
 #include <windows.h>
 #include <mmdeviceapi.h>
 #include <windef.h>
 #include <audioclient.h>
@@ -130,20 +131,18 @@ struct cubeb_stream
   /* 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;
   /* 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|. */
+   * its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */
   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->mix_params.channels > stream->stream_params.channels;
@@ -156,63 +155,65 @@ bool should_downmix(cubeb_stream * strea
 
 /* 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)
 {
   int j = 0;
-  for (int i = 0; i < insamples; i++, j+=2) {
+  for (int i = 0; i < insamples; ++i, j += 2) {
     out[j] = out[j + 1] = in[i];
   }
 }
 
 template<typename T>
 void
 upmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
 {
+  assert(out_channels >= in_channels);
   /* If we are playing a mono stream over stereo speakers, copy the data over. */
   if (in_channels == 1 && out_channels == 2) {
     mono_to_stereo(in, inframes, out);
     return;
   }
   /* Otherwise, put silence in other channels. */
   long out_index = 0;
-  for (long i = 0; i < inframes * in_channels; i+=in_channels) {
-    for (int j = 0; j < in_channels; j++) {
+  for (long i = 0; i < inframes * in_channels; i += in_channels) {
+    for (int j = 0; j < in_channels; ++j) {
       out[out_index + j] = in[i + j];
-      if (in_channels == 1) {
-        out[out_index + j + 1] = in[i + j];
-      }
     }
-    for (int j = in_channels; j < out_channels; j++) {
+    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)
+downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
 {
+  assert(in_channels >= out_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];
+  long out_index = 0;
+  for (long i = 0; i < inframes * in_channels; i += in_channels) {
+    for (int j = 0; j < out_channels; ++j) {
+      out[out_index + j] = in[i + j];
+    }
+    out_index += out_channels;
   }
 }
 
 /* This returns the size of a frame in the stream,
  * before the eventual upmix occurs. */
 static size_t
-frame_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
+frames_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)
 {
@@ -222,17 +223,17 @@ 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_mix(stm, stm->leftover_frame_count);
+    frames_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);
 
@@ -256,62 +257,63 @@ refill_with_resampling(cubeb_stream * st
                                             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_mix(stm, stm->leftover_frame_count);
+    frames_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_mix(stm, in_frames);
+  leftover_frames_start += frames_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);
+    downmix(resample_dest, out_frames, data,
+            stm->stream_params.channels, stm->mix_params.channels);
   }
 }
 
 void
 refill(cubeb_stream * stm, float * data, long frames_needed)
 {
   /* 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) || should_downmix(stm)) {
     dest = stm->mix_buffer;
   } else {
     dest = data;
   }
 
   long got = stm->data_callback(stm, stm->user_ptr, dest, frames_needed);
-
+  assert(got <= 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);
+  } else if (should_downmix(stm)) {
+    downmix(dest, got, data,
+            stm->stream_params.channels, stm->mix_params.channels);
   }
 }
 
 static unsigned int __stdcall
 wasapi_stream_render_loop(LPVOID stream)
 {
   cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
 
@@ -358,16 +360,17 @@ wasapi_stream_render_loop(LPVOID stream)
       UINT32 padding;
 
       hr = stm->client->GetCurrentPadding(&padding);
       if (FAILED(hr)) {
         LOG("Failed to get padding");
         is_playing = false;
         continue;
       }
+      assert(padding <= stm->buffer_frame_count);
 
       if (stm->draining) {
         if (padding == 0) {
           stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
           is_playing = false;
         }
         continue;
       }
@@ -598,17 +601,17 @@ void wasapi_stream_destroy(cubeb_stream 
 
 /* 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 is stereo. Up-mixing and down-mixing will be
    * handled in the callback. */
-  if ((*mix_format)->nChannels == 2) {
+  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
@@ -642,18 +645,20 @@ 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/downmix ourselve */
+     * eventual upmix/downmix ourselves */
     LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
+    WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
+    assert(closest_pcm->SubFormat == format_pcm->SubFormat);
     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.");
   } else {
@@ -716,19 +721,16 @@ wasapi_stream_init(cubeb * context, cube
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   /* We have to distinguish between the format the mixer uses,
   * and the format the stream we want to play uses. */
   stm->client->GetMixFormat(&mix_format);
 
-  /* this is the number of bytes per frame after the eventual upmix. */
-  stm->bytes_per_frame = static_cast<uint8_t>(mix_format->nBlockAlign);
-
   handle_channel_layout(stm, &mix_format, &stream_params);
 
   /* Shared mode WASAPI always supports float32 sample format, so this
    * is safe. */
   stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE;
 
   stm->mix_params.rate = mix_format->nSamplesPerSec;
   stm->mix_params.channels = mix_format->nChannels;
@@ -752,17 +754,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_mix(stm, stm->leftover_frame_size));
+    stm->leftover_frames_buffer = (float *)malloc(frames_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 |
@@ -782,29 +784,27 @@ wasapi_stream_init(cubeb * context, cube
 
   hr = stm->client->GetBufferSize(&stm->buffer_frame_count);
   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 (should_upmix(stm) || should_downmix(stm)) {
-    stm->mix_buffer = (float *) malloc(frame_to_bytes_before_mix(stm, stm->buffer_frame_count));
+    stm->mix_buffer = (float *) malloc(frames_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_mix(stm, frames_needed));
+    stm->resampling_src_buffer = (float *)malloc(frames_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;
   }