Bug 893307 - Handle surround speaker setups when using the WASAPI cubeb backend. r=kinetik
authorPaul Adenot <paul@paul.cx>
Fri, 26 Jul 2013 14:17:30 +0200
changeset 140153 3de1033e44b730906715cfb3b19a07130aa6893f
parent 140152 e8ef43506a4df5964dd5cb464984d18a9d6897cb
child 140154 63887fd246ccb18d142750aa8631d72bf5759fd1
push id1945
push userryanvm@gmail.com
push dateSat, 27 Jul 2013 02:27:26 +0000
treeherderfx-team@4874fa438b1c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs893307
milestone25.0a1
Bug 893307 - Handle surround speaker setups when using the WASAPI cubeb backend. r=kinetik
media/libcubeb/README_MOZILLA
media/libcubeb/src/cubeb_wasapi.cpp
--- 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 f82d1c2c269c452ac4052da404c6b0a42aff3c66.
+The git commit ID used was 5beac74eab38b7026404888a8cee7675017c7407.
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -149,16 +149,38 @@ void
 mono_to_stereo(T * in, long insamples, T * out)
 {
   int j = 0;
   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)
+{
+  /* 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++) {
+      out[out_index + j] = in[i + j];
+    }
+    for (int j = in_channels; j < out_channels; j++) {
+      out[out_index + j] = 0.0;
+    }
+    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_upmix(cubeb_stream * stm, size_t frames)
 {
   return stm->bytes_per_frame / (should_upmix(stm) ? 2 : 1) * frames;
 }
 
@@ -219,17 +241,18 @@ refill_with_resampling(cubeb_stream * st
   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)) {
-    mono_to_stereo(resample_dest, out_frames, data);
+    upmix(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 after resampling, get the data into
    * the upmix buffer to avoid a copy. */
@@ -243,17 +266,18 @@ refill(cubeb_stream * stm, float * 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)) {
-    mono_to_stereo(dest, got, data);
+    upmix(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);
 
@@ -476,16 +500,73 @@ wasapi_get_max_channel_count(cubeb * ctx
   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)
+{
+  assert((*mix_format)->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+
+  /* Common case: the hardware supports stereo, and the stream is mono or
+   * stereo. Easy. */
+  if ((*mix_format)->nChannels == 2 &&
+      stream_params->channels <= 2) {
+    return;
+  }
+
+  /* The hardware is in surround mode, we want to only use front left and front
+   * right. Try that, and check if it works. */
+  WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>((*mix_format));
+  switch (stream_params->channels) {
+    case 1: /* Mono */
+      format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
+      break;
+    case 2: /* Stereo */
+      format_pcm->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
+      break;
+    default:
+      assert(false && "Channel layout not supported.");
+      break;
+  }
+  (*mix_format)->nChannels = stream_params->channels;
+  (*mix_format)->nBlockAlign = ((*mix_format)->wBitsPerSample * (*mix_format)->nChannels) / 8;
+  (*mix_format)->nAvgBytesPerSec = (*mix_format)->nSamplesPerSec * (*mix_format)->nBlockAlign;
+  format_pcm->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+  (*mix_format)->wBitsPerSample = 32;
+  format_pcm->Samples.wValidBitsPerSample = (*mix_format)->wBitsPerSample;
+
+  /* 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 */
+    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.");
+  } else {
+    assert(false && "Not reached.");
+  }
+}
+
 int
 wasapi_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)
 {
   HRESULT hr;
   WAVEFORMATEX * mix_format;
@@ -550,16 +631,18 @@ wasapi_stream_init(cubeb * context, cube
 
   /* 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;
 
   float resampling_rate = static_cast<float>(stm->stream_params.rate) /
@@ -589,42 +672,39 @@ wasapi_stream_init(cubeb * context, cube
     stm->leftover_frames_buffer = (float *)malloc(frame_to_bytes_before_upmix(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 |
-                                     AUDCLNT_STREAMFLAGS_NOPERSIST,
-                                     ms_to_hns(latency),
-                                     0,
-                                     mix_format,
-                                     NULL);
+                               AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
+                               AUDCLNT_STREAMFLAGS_NOPERSIST,
+                               ms_to_hns(latency),
+                               0,
+                               mix_format,
+                               NULL);
 
   CoTaskMemFree(mix_format);
 
   if (FAILED(hr)) {
     LOG("Unable to initialize audio client: %x.", hr);
     wasapi_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   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;
   }
 
-  /* If we are playing a mono stream, we need to upmix to stereo.
-   * For now, we assume that the output support stereo sound.
-   * The alternative would be sad */
-  assert(stm->mix_params.channels == 2);
+  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 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