Bug 1331869 - Update cubeb from upstream to d96e35f02d. r=kinetik,padenot
authorAlex Chronopoulos <achronop@gmail.com>
Thu, 19 Jan 2017 18:02:42 +0200
changeset 358329 056197b55d495037138db1507583816efd574d87
parent 358328 e8c9758e6322ef6de8a7c03deefcc49417382f96
child 358330 2d93fb71eb88ab0ef03943f883ccbff284db9fc2
push id10621
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 16:02:43 +0000
treeherdermozilla-aurora@dca7b42e6c67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik, padenot
bugs1331869
milestone53.0a1
Bug 1331869 - Update cubeb from upstream to d96e35f02d. r=kinetik,padenot MozReview-Commit-ID: 9H3cKh82Jwv
media/libcubeb/README_MOZILLA
media/libcubeb/gtest/common.h
media/libcubeb/gtest/test_audio.cpp
media/libcubeb/gtest/test_duplex.cpp
media/libcubeb/gtest/test_latency.cpp
media/libcubeb/gtest/test_mixer.cpp
media/libcubeb/gtest/test_record.cpp
media/libcubeb/gtest/test_resampler.cpp
media/libcubeb/gtest/test_sanity.cpp
media/libcubeb/gtest/test_tone.cpp
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.cpp
media/libcubeb/src/cubeb_jack.cpp
media/libcubeb/src/cubeb_mixer.cpp
media/libcubeb/src/cubeb_mixer.h
media/libcubeb/src/cubeb_opensl.c
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/src/cubeb_resampler.cpp
media/libcubeb/src/cubeb_sndio.c
media/libcubeb/src/cubeb_utils.h
media/libcubeb/src/cubeb_wasapi.cpp
media/libcubeb/src/cubeb_winmm.c
media/libcubeb/src/moz.build
media/libcubeb/update.sh
--- 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 051cd8478dd48a18e2e91e5f07837640f40618c1.
+The git commit ID used was d96e35f02dbb9a093e5bfdff4f2948b7a6e9d3f9.
--- a/media/libcubeb/gtest/common.h
+++ b/media/libcubeb/gtest/common.h
@@ -11,30 +11,65 @@
 #ifndef WIN32_LEAN_AND_MEAN
 #define WIN32_LEAN_AND_MEAN
 #endif
 #include <windows.h>
 #else
 #include <unistd.h>
 #endif
 
+template<typename T, size_t N>
+constexpr size_t
+ARRAY_LENGTH(T(&)[N])
+{
+  return N;
+}
+
 void delay(unsigned int ms)
 {
 #if defined(_WIN32)
   Sleep(ms);
 #else
   sleep(ms / 1000);
   usleep(ms % 1000 * 1000);
 #endif
 }
 
 #if !defined(M_PI)
 #define M_PI 3.14159265358979323846
 #endif
 
+typedef struct {
+  char const * name;
+  unsigned int const channels;
+  cubeb_channel_layout const layout;
+} layout_info;
+
+layout_info const layout_infos[CUBEB_LAYOUT_MAX] = {
+  { "undefined",      0,  CUBEB_LAYOUT_UNDEFINED },
+  { "dual mono",      2,  CUBEB_LAYOUT_DUAL_MONO },
+  { "dual mono lfe",  3,  CUBEB_LAYOUT_DUAL_MONO_LFE },
+  { "mono",           1,  CUBEB_LAYOUT_MONO },
+  { "mono lfe",       2,  CUBEB_LAYOUT_MONO_LFE },
+  { "stereo",         2,  CUBEB_LAYOUT_STEREO },
+  { "stereo lfe",     3,  CUBEB_LAYOUT_STEREO_LFE },
+  { "3f",             3,  CUBEB_LAYOUT_3F },
+  { "3f lfe",         4,  CUBEB_LAYOUT_3F_LFE },
+  { "2f1",            3,  CUBEB_LAYOUT_2F1 },
+  { "2f1 lfe",        4,  CUBEB_LAYOUT_2F1_LFE },
+  { "3f1",            4,  CUBEB_LAYOUT_3F1 },
+  { "3f1 lfe",        5,  CUBEB_LAYOUT_3F1_LFE },
+  { "2f2",            4,  CUBEB_LAYOUT_2F2 },
+  { "2f2 lfe",        5,  CUBEB_LAYOUT_2F2_LFE },
+  { "3f2",            5,  CUBEB_LAYOUT_3F2 },
+  { "3f2 lfe",        6,  CUBEB_LAYOUT_3F2_LFE },
+  { "3f3r lfe",       7,  CUBEB_LAYOUT_3F3R_LFE },
+  { "3f4 lfe",        8,  CUBEB_LAYOUT_3F4_LFE }
+};
+
 int has_available_input_device(cubeb * ctx)
 {
   cubeb_device_collection * devices;
   int input_device_available = 0;
   int r;
   /* Bail out early if the host does not have input devices. */
   r = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_INPUT, &devices);
   if (r != CUBEB_OK) {
--- a/media/libcubeb/gtest/test_audio.cpp
+++ b/media/libcubeb/gtest/test_audio.cpp
@@ -19,17 +19,16 @@
 #include "common.h"
 
 #define MAX_NUM_CHANNELS 32
 
 #if !defined(M_PI)
 #define M_PI 3.14159265358979323846
 #endif
 
-#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
 #define VOLUME 0.2
 
 float get_frequency(int channel_index)
 {
   return 220.0f * (channel_index+1);
 }
 
 /* store the phase of the generated waveform */
@@ -113,17 +112,17 @@ int supports_int16(const char* backend_i
 
 /* Some backends don't have code to deal with more than mono or stereo. */
 int supports_channel_count(const char* backend_id, int nchannels)
 {
   return nchannels <= 2 ||
     (strcmp(backend_id, "opensl") != 0 && strcmp(backend_id, "audiotrack") != 0);
 }
 
-int run_test(int num_channels, int sampling_rate, int is_float)
+int run_test(int num_channels, layout_info layout, int sampling_rate, int is_float)
 {
   int r = CUBEB_OK;
 
   cubeb *ctx = NULL;
   synth_state* synth = NULL;
   cubeb_stream *stream = NULL;
   const char * backend_id = NULL;
 
@@ -137,22 +136,23 @@ int run_test(int num_channels, int sampl
 
   if ((is_float && !supports_float32(backend_id)) ||
       (!is_float && !supports_int16(backend_id)) ||
       !supports_channel_count(backend_id, num_channels)) {
     /* don't treat this as a test failure. */
     goto cleanup;
   }
 
-  fprintf(stderr, "Testing %d channel(s), %d Hz, %s (%s)\n", num_channels, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
+  fprintf(stderr, "Testing %d channel(s), layout: %s, %d Hz, %s (%s)\n", num_channels, layout.name, sampling_rate, is_float ? "float" : "short", cubeb_get_backend_id(ctx));
 
   cubeb_stream_params params;
   params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
   params.rate = sampling_rate;
   params.channels = num_channels;
+  params.layout = layout.layout;
 
   synth = synth_create(params.channels, params.rate);
   if (synth == NULL) {
     fprintf(stderr, "Out of memory\n");
     goto cleanup;
   }
 
   r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
@@ -195,16 +195,17 @@ int run_panning_volume_test(int is_float
     /* don't treat this as a test failure. */
     goto cleanup;
   }
 
   cubeb_stream_params params;
   params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
   params.rate = 44100;
   params.channels = 2;
+  params.layout = CUBEB_LAYOUT_STEREO;
 
   synth = synth_create(params.channels, params.rate);
   if (synth == NULL) {
     fprintf(stderr, "Out of memory\n");
     goto cleanup;
   }
 
   r = cubeb_stream_init(ctx, &stream, "test tone", NULL, NULL, NULL, &params,
@@ -254,33 +255,36 @@ TEST(cubeb, run_panning_volume_test_shor
 
 TEST(cubeb, run_panning_volume_test_float)
 {
   ASSERT_EQ(run_panning_volume_test(1), CUBEB_OK);
 }
 
 TEST(cubeb, run_channel_rate_test)
 {
-  int channel_values[] = {
+  unsigned int channel_values[] = {
     1,
     2,
     3,
     4,
     6,
   };
 
   int freq_values[] = {
     16000,
     24000,
     44100,
     48000,
   };
 
-  for(int j = 0; j < NELEMS(channel_values); ++j) {
-    for(int i = 0; i < NELEMS(freq_values); ++i) {
+  for(unsigned int j = 0; j < ARRAY_LENGTH(channel_values); ++j) {
+    for(unsigned int i = 0; i < ARRAY_LENGTH(freq_values); ++i) {
       ASSERT_TRUE(channel_values[j] < MAX_NUM_CHANNELS);
       fprintf(stderr, "--------------------------\n");
-      ASSERT_EQ(run_test(channel_values[j], freq_values[i], 0), CUBEB_OK);
-      ASSERT_EQ(run_test(channel_values[j], freq_values[i], 1), CUBEB_OK);
+      for (unsigned int k = 0 ; k < ARRAY_LENGTH(layout_infos); ++k ) {
+        if (layout_infos[k].channels == channel_values[j]) {
+          ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 0), CUBEB_OK);
+          ASSERT_EQ(run_test(channel_values[j], layout_infos[k], freq_values[i], 1), CUBEB_OK);
+        }
+      }
     }
   }
 }
-
--- a/media/libcubeb/gtest/test_duplex.cpp
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -103,19 +103,21 @@ TEST(cubeb, duplex)
   if (!has_available_input_device(ctx)) {
     return;
   }
 
   /* typical user-case: mono input, stereo output, low latency. */
   input_params.format = STREAM_FORMAT;
   input_params.rate = 48000;
   input_params.channels = 1;
+  input_params.layout = CUBEB_LAYOUT_MONO;
   output_params.format = STREAM_FORMAT;
   output_params.rate = 48000;
   output_params.channels = 2;
+  output_params.layout = CUBEB_LAYOUT_STEREO;
 
   r = cubeb_get_min_latency(ctx, output_params, &latency_frames);
 
   if (r != CUBEB_OK) {
     fprintf(stderr, "Could not get minimal latency\n");
     ASSERT_EQ(r, CUBEB_OK);
   }
 
--- a/media/libcubeb/gtest/test_latency.cpp
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -4,36 +4,44 @@
 
 TEST(cubeb, latency)
 {
   cubeb * ctx = NULL;
   int r;
   uint32_t max_channels;
   uint32_t preferred_rate;
   uint32_t latency_frames;
+  cubeb_channel_layout layout;
 
   r = cubeb_init(&ctx, "Cubeb audio test");
   ASSERT_EQ(r, CUBEB_OK);
 
   r = cubeb_get_max_channel_count(ctx, &max_channels);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_GT(max_channels, 0u);
   }
 
   r = cubeb_get_preferred_sample_rate(ctx, &preferred_rate);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_GT(preferred_rate, 0u);
   }
 
+  r = cubeb_get_preferred_channel_layout(ctx, &layout);
+  ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
+
   cubeb_stream_params params = {
     CUBEB_SAMPLE_FLOAT32NE,
     preferred_rate,
-    max_channels
+    max_channels,
+    (r == CUBEB_OK) ? layout : CUBEB_LAYOUT_UNDEFINED
+#if defined(__ANDROID__)
+    , CUBEB_STREAM_TYPE_MUSIC
+#endif
   };
   r = cubeb_get_min_latency(ctx, params, &latency_frames);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_GT(latency_frames, 0u);
   }
 
   cubeb_destroy(ctx);
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/gtest/test_mixer.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license.  See the
+ * accompanying file LICENSE for details.
+ */
+#include "gtest/gtest.h"
+#include "cubeb/cubeb.h"
+#include "cubeb_mixer.h"
+#include "common.h"
+#include <vector>
+
+using std::vector;
+
+#define STREAM_FREQUENCY 48000
+#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+
+float const M = 1.0f;     // Mono
+float const L = 2.0f;     // Left
+float const R = 3.0f;     // Right
+float const C = 4.0f;     // Center
+float const LS = 5.0f;    // Left Surround
+float const RS = 6.0f;    // Right Surround
+float const RLS = 7.0f;   // Rear Left Surround
+float const RC = 8.0f;    // Rear Center
+float const RRS = 9.0f;   // Rear Right Surround
+float const LFE = 10.0f;  // Low Frequency Effects
+
+float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
+static float const DOWNMIX_3F2_RESULTS[2][12][5] = {
+  // 3F2
+  {
+    { INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS) },                          // Mono
+    { INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS), 0 },                       // Mono-LFE
+    { L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS) },                 // Stereo
+    { L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS), 0 },              // Stereo-LFE
+    { L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C },                      // 3F
+    { L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C, 0 },                   // 3F-LFE
+    { L + C*INV_SQRT_2, R + C*INV_SQRT_2, INV_SQRT_2*(LS+RS) },       // 2F1
+    { L + C*INV_SQRT_2, R + C*INV_SQRT_2, 0, INV_SQRT_2*(LS+RS) },    // 2F1-LFE
+    { L, R, C, INV_SQRT_2*(LS+RS) },                                  // 3F1
+    { L, R, C, 0, INV_SQRT_2*(LS+RS) },                               // 3F1-LFE
+    { L + INV_SQRT_2*C, R + INV_SQRT_2*C, LS, RS },                   // 2F2
+    { L + INV_SQRT_2*C, R + INV_SQRT_2*C, 0, LS, RS }                 // 2F2-LFE
+  },
+  // 3F2-LFE
+  {
+    { INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS) },                          // Mono
+    { INV_SQRT_2*(L+R) + C + 0.5f*(LS+RS), LFE },                     // Mono-LFE
+    { L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS) },                 // Stereo
+    { L + INV_SQRT_2*(C+LS), R + INV_SQRT_2*(C+RS), LFE },            // Stereo-LFE
+    { L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C },                      // 3F
+    { L + INV_SQRT_2*LS, R + INV_SQRT_2*RS, C, LFE },                 // 3F-LFE
+    { L + C*INV_SQRT_2, R + C*INV_SQRT_2, INV_SQRT_2*(LS+RS) },       // 2F1
+    { L + C*INV_SQRT_2, R + C*INV_SQRT_2, LFE, INV_SQRT_2*(LS+RS) },  // 2F1-LFE
+    { L, R, C, INV_SQRT_2*(LS+RS) },                                  // 3F1
+    { L, R, C, LFE, INV_SQRT_2*(LS+RS) },                             // 3F1-LFE
+    { L + INV_SQRT_2*C, R + INV_SQRT_2*C, LS, RS },                   // 2F2
+    { L + INV_SQRT_2*C, R + INV_SQRT_2*C, LFE, LS, RS }               // 2F2-LFE
+  }
+};
+
+typedef struct {
+  cubeb_channel_layout layout;
+  float data[10];
+} audio_input;
+
+audio_input audio_inputs[CUBEB_LAYOUT_MAX] = {
+  { CUBEB_LAYOUT_UNDEFINED,     { } },
+  { CUBEB_LAYOUT_DUAL_MONO,     { L, R } },
+  { CUBEB_LAYOUT_DUAL_MONO_LFE, { L, R, LFE } },
+  { CUBEB_LAYOUT_MONO,          { M } },
+  { CUBEB_LAYOUT_MONO_LFE,      { M, LFE } },
+  { CUBEB_LAYOUT_STEREO,        { L, R } },
+  { CUBEB_LAYOUT_STEREO_LFE,    { L, R, LFE } },
+  { CUBEB_LAYOUT_3F,            { L, R, C } },
+  { CUBEB_LAYOUT_3F_LFE,        { L, R, C, LFE } },
+  { CUBEB_LAYOUT_2F1,           { L, R, RC } },
+  { CUBEB_LAYOUT_2F1_LFE,       { L, R, LFE, RC } },
+  { CUBEB_LAYOUT_3F1,           { L, R, C, RC } },
+  { CUBEB_LAYOUT_3F1_LFE,       { L, R, C, LFE, RC } },
+  { CUBEB_LAYOUT_2F2,           { L, R, LS, RS } },
+  { CUBEB_LAYOUT_2F2_LFE,       { L, R, LFE, LS, RS } },
+  { CUBEB_LAYOUT_3F2,           { L, R, C, LS, RS } },
+  { CUBEB_LAYOUT_3F2_LFE,       { L, R, C, LFE, LS, RS } },
+  { CUBEB_LAYOUT_3F3R_LFE,      { L, R, C, LFE, RC, LS, RS } },
+  { CUBEB_LAYOUT_3F4_LFE,       { L, R, C, LFE, RLS, RRS, LS, RS } }
+};
+
+void
+downmix_test(float const * data, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+  if (in_layout == CUBEB_LAYOUT_UNDEFINED) {
+    return; // Only possible output layout would be UNDEFINED.
+  }
+
+  cubeb_stream_params in_params = {
+    STREAM_FORMAT,
+    STREAM_FREQUENCY,
+    layout_infos[in_layout].channels,
+    in_layout
+#if defined(__ANDROID__)
+    , CUBEB_STREAM_TYPE_MUSIC
+#endif
+  };
+
+  cubeb_stream_params out_params = {
+    STREAM_FORMAT,
+    STREAM_FREQUENCY,
+    // To downmix audio data with undefined layout, its channel number must be
+    // smaller than or equal to the input channels.
+    (out_layout == CUBEB_LAYOUT_UNDEFINED) ?
+      layout_infos[in_layout].channels : layout_infos[out_layout].channels,
+    out_layout
+#if defined(__ANDROID__)
+    , CUBEB_STREAM_TYPE_MUSIC
+#endif
+   };
+
+  if (!cubeb_should_downmix(&in_params, &out_params)) {
+    return;
+  }
+
+  fprintf(stderr, "Downmix from %s to %s\n", layout_infos[in_layout].name, layout_infos[out_layout].name);
+
+  unsigned int const inframes = 10;
+  vector<float> in(in_params.channels * inframes);
+  vector<float> out(out_params.channels * inframes);
+
+  for (unsigned int offset = 0 ; offset < inframes * in_params.channels ; offset += in_params.channels) {
+    for (unsigned int i = 0 ; i < in_params.channels ; ++i) {
+      in[offset + i] = data[i];
+    }
+  }
+
+  cubeb_downmix_float(in.data(), inframes, out.data(), in_params.channels, out_params.channels, in_params.layout, out_params.layout);
+
+  uint32_t in_layout_mask = 0;
+  for (unsigned int i = 0 ; i < in_params.channels; ++i) {
+    in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
+  }
+
+  uint32_t out_layout_mask = 0;
+  for (unsigned int i = 0 ; out_layout != CUBEB_LAYOUT_UNDEFINED && i < out_params.channels; ++i) {
+    out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
+  }
+
+  for (unsigned int i = 0 ; i < inframes * out_params.channels ; ++i) {
+    unsigned int index = i % out_params.channels;
+
+    // downmix_3f2
+    if ((in_layout == CUBEB_LAYOUT_3F2 || in_layout == CUBEB_LAYOUT_3F2_LFE) &&
+        out_layout >= CUBEB_LAYOUT_MONO && out_layout <= CUBEB_LAYOUT_2F2_LFE) {
+      auto & downmix_results = DOWNMIX_3F2_RESULTS[in_layout - CUBEB_LAYOUT_3F2][out_layout - CUBEB_LAYOUT_MONO];
+      fprintf(stderr, "[3f2] Expect: %lf, Get: %lf\n", downmix_results[index], out[index]);
+      ASSERT_EQ(out[index], downmix_results[index]);
+      continue;
+    }
+
+    // mix_remap
+    if (out_layout_mask & in_layout_mask) {
+      uint32_t mask = 1 << CHANNEL_INDEX_TO_ORDER[out_layout][index];
+      fprintf(stderr, "[map channels] Expect: %lf, Get: %lf\n", (mask & in_layout_mask) ? audio_inputs[out_layout].data[index] : 0, out[index]);
+      ASSERT_EQ(out[index], (mask & in_layout_mask) ? audio_inputs[out_layout].data[index] : 0);
+      continue;
+    }
+
+    // downmix_fallback
+    fprintf(stderr, "[fallback] Expect: %lf, Get: %lf\n", audio_inputs[in_layout].data[index], out[index]);
+    ASSERT_EQ(out[index], audio_inputs[in_layout].data[index]);
+  }
+}
+
+TEST(cubeb, run_mixing_test)
+{
+  for (unsigned int i = 0 ; i < ARRAY_LENGTH(audio_inputs) ; ++i) {
+    for (unsigned int j = 0 ; j < ARRAY_LENGTH(layout_infos) ; ++j) {
+      downmix_test(audio_inputs[i].data, audio_inputs[i].layout, layout_infos[j].layout);
+    }
+  }
+}
--- a/media/libcubeb/gtest/test_record.cpp
+++ b/media/libcubeb/gtest/test_record.cpp
@@ -90,16 +90,17 @@ TEST(cubeb, record)
    * have one. */
   if (!has_available_input_device(ctx)) {
     return;
   }
 
   params.format = STREAM_FORMAT;
   params.rate = SAMPLE_FREQUENCY;
   params.channels = 1;
+  params.layout = CUBEB_LAYOUT_MONO;
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb record (mono)", NULL, &params, NULL, nullptr,
                         4096, data_cb_record, state_cb_record, &stream_state);
   if (r != CUBEB_OK) {
     fprintf(stderr, "Error initializing cubeb stream\n");
     ASSERT_EQ(r, CUBEB_OK);
   }
 
--- a/media/libcubeb/gtest/test_resampler.cpp
+++ b/media/libcubeb/gtest/test_resampler.cpp
@@ -281,17 +281,16 @@ uint32_t fill_with_sine(float * buf, uin
 
 long data_cb_resampler(cubeb_stream * /*stm*/, void * user_ptr,
              const void * input_buffer, void * output_buffer, long frame_count)
 {
   osc_state * state = reinterpret_cast<osc_state*>(user_ptr);
   const float * in = reinterpret_cast<const float*>(input_buffer);
   float * out = reinterpret_cast<float*>(output_buffer);
 
-
   state->input.push(in, frame_count * state->input_channels);
 
   /* Check how much output frames we need to write */
   uint32_t remaining = state->max_output_phase_index - state->output_phase_index;
   uint32_t to_write = std::min<uint32_t>(remaining, frame_count);
   state->output_phase_index = fill_with_sine(out,
                                              state->target_rate,
                                              state->output_channels,
@@ -357,17 +356,16 @@ void test_resampler_duplex(uint32_t inpu
 
   /* expected output is a 440Hz sine wave at 16kHz */
   fill_with_sine(expected_resampled_input.data() + latency,
                  target_rate, input_channels, duration_frames - latency, 0);
   /* expected output is a 440Hz sine wave at 32kHz */
   fill_with_sine(expected_resampled_output.data() + latency,
                  output_rate, output_channels, output_rate * duration_s - latency, 0);
 
-
   while (state.output_phase_index != state.max_output_phase_index) {
     uint32_t leftover_samples = input_buffer.length() * input_channels;
     input_buffer.reserve(input_array_frame_count);
     state.input_phase_index = fill_with_sine(input_buffer.data() + leftover_samples,
                                              input_rate,
                                              input_channels,
                                              input_array_frame_count - leftover_samples,
                                              state.input_phase_index);
--- a/media/libcubeb/gtest/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -13,29 +13,23 @@
 #include <stdio.h>
 #include <string.h>
 #include <math.h>
 #include "common.h"
 
 #define STREAM_RATE 44100
 #define STREAM_LATENCY 100 * STREAM_RATE / 1000
 #define STREAM_CHANNELS 1
+#define STREAM_LAYOUT CUBEB_LAYOUT_MONO
 #if (defined(_WIN32) || defined(__WIN32__))
 #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
 #else
 #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
 #endif
 
-template<typename T, size_t N>
-constexpr size_t
-ARRAY_LENGTH(T(&)[N])
-{
-  return N;
-}
-
 int is_windows_7()
 {
 #ifdef __MINGW32__
   printf("Warning: this test was built with MinGW.\n"
          "MinGW does not contain necessary version checking infrastructure. Claiming to be Windows 7, even if we're not.\n");
   return 1;
 #endif
 #if (defined(_WIN32) || defined(__WIN32__)) && ( !defined(__MINGW32__))
@@ -90,17 +84,16 @@ TEST(cubeb, init_destroy_context)
   int r;
   cubeb * ctx;
   char const* backend_id;
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
-
   backend_id = cubeb_get_backend_id(ctx);
   ASSERT_TRUE(backend_id);
 
   fprintf(stderr, "Backend: %s\n", backend_id);
 
   cubeb_destroy(ctx);
 }
 
@@ -133,16 +126,17 @@ TEST(cubeb, context_variables)
 
   r = cubeb_init(&ctx, "test_context_variables");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.channels = STREAM_CHANNELS;
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
   r = cubeb_get_min_latency(ctx, params, &value);
   ASSERT_TRUE(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
   if (r == CUBEB_OK) {
     ASSERT_TRUE(value > 0);
   }
@@ -165,16 +159,17 @@ TEST(cubeb, init_destroy_stream)
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
@@ -193,16 +188,17 @@ TEST(cubeb, init_destroy_multiple_stream
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
     r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                           test_data_callback, test_state_callback, &dummy);
     ASSERT_EQ(r, CUBEB_OK);
@@ -225,16 +221,17 @@ TEST(cubeb, configure_stream)
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = 2; // panning
+  params.layout = CUBEB_LAYOUT_STEREO;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
@@ -260,32 +257,32 @@ test_init_start_stop_destroy_multiple_st
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
     r = cubeb_stream_init(ctx, &stream[i], "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                           test_data_callback, test_state_callback, &dummy);
     ASSERT_EQ(r, CUBEB_OK);
     ASSERT_NE(stream[i], nullptr);
     if (early) {
       r = cubeb_stream_start(stream[i]);
       ASSERT_EQ(r, CUBEB_OK);
     }
   }
 
-
   if (!early) {
     for (i = 0; i < ARRAY_LENGTH(stream); ++i) {
       r = cubeb_stream_start(stream[i]);
       ASSERT_EQ(r, CUBEB_OK);
     }
   }
 
   if (delay_ms) {
@@ -347,16 +344,17 @@ TEST(cubeb, init_destroy_multiple_contex
    * and is not documented as a possible return value for this call. Hence, we
    * try to limit the number of streams we create in this test. */
   if (is_windows_7())
     return;
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
     r = cubeb_init(&ctx[i], "test_sanity");
     ASSERT_EQ(r, CUBEB_OK);
     ASSERT_NE(ctx[i], nullptr);
@@ -387,16 +385,17 @@ TEST(cubeb, basic_stream_operations)
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
@@ -437,16 +436,17 @@ TEST(cubeb, stream_position)
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_data_callback, test_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
@@ -576,16 +576,17 @@ TEST(cubeb, drain)
 
   r = cubeb_init(&ctx, "test_sanity");
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(ctx, nullptr);
 
   params.format = STREAM_FORMAT;
   params.rate = STREAM_RATE;
   params.channels = STREAM_CHANNELS;
+  params.layout = STREAM_LAYOUT;
 #if defined(__ANDROID__)
   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
 
   r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
                         test_drain_data_callback, test_drain_state_callback, &dummy);
   ASSERT_EQ(r, CUBEB_OK);
   ASSERT_NE(stream, nullptr);
--- a/media/libcubeb/gtest/test_tone.cpp
+++ b/media/libcubeb/gtest/test_tone.cpp
@@ -105,16 +105,17 @@ TEST(cubeb, tone)
   if (r != CUBEB_OK) {
     fprintf(stderr, "Error initializing cubeb library\n");
     ASSERT_EQ(r, CUBEB_OK);
   }
 
   params.format = STREAM_FORMAT;
   params.rate = SAMPLE_FREQUENCY;
   params.channels = 1;
+  params.layout = CUBEB_LAYOUT_MONO;
 
   user_data = (struct cb_user_data *) malloc(sizeof(*user_data));
   if (user_data == NULL) {
     fprintf(stderr, "Error allocating user data\n");
     FAIL();
   }
   user_data->position = 0;
 
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -171,22 +171,81 @@ typedef void const * cubeb_devid;
 
 /** Level (verbosity) of logging for a particular cubeb context. */
 typedef enum {
   CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
   CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */
   CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
 } cubeb_log_level;
 
+/** SMPTE channel layout (also known as wave order)
+ * DUAL-MONO      L   R
+ * DUAL-MONO-LFE  L   R   LFE
+ * MONO           M
+ * MONO-LFE       M   LFE
+ * STEREO         L   R
+ * STEREO-LFE     L   R   LFE
+ * 3F             L   R   C
+ * 3F-LFE         L   R   C    LFE
+ * 2F1            L   R   S
+ * 2F1-LFE        L   R   LFE  S
+ * 3F1            L   R   C    S
+ * 3F1-LFE        L   R   C    LFE S
+ * 2F2            L   R   LS   RS
+ * 2F2-LFE        L   R   LFE  LS   RS
+ * 3F2            L   R   C    LS   RS
+ * 3F2-LFE        L   R   C    LFE  LS   RS
+ * 3F3R-LFE       L   R   C    LFE  RC   LS   RS
+ * 3F4-LFE        L   R   C    LFE  RLS  RRS  LS   RS
+ *
+ * The abbreviation of channel name is defined in following table:
+ * Abbr  Channel name
+ * ---------------------------
+ * M     Mono
+ * L     Left
+ * R     Right
+ * C     Center
+ * LS    Left Surround
+ * RS    Right Surround
+ * RLS   Rear Left Surround
+ * RC    Rear Center
+ * RRS   Rear Right Surround
+ * LFE   Low Frequency Effects
+ */
+
+typedef enum {
+  CUBEB_LAYOUT_UNDEFINED, // Indicate the speaker's layout is undefined.
+  CUBEB_LAYOUT_DUAL_MONO,
+  CUBEB_LAYOUT_DUAL_MONO_LFE,
+  CUBEB_LAYOUT_MONO,
+  CUBEB_LAYOUT_MONO_LFE,
+  CUBEB_LAYOUT_STEREO,
+  CUBEB_LAYOUT_STEREO_LFE,
+  CUBEB_LAYOUT_3F,
+  CUBEB_LAYOUT_3F_LFE,
+  CUBEB_LAYOUT_2F1,
+  CUBEB_LAYOUT_2F1_LFE,
+  CUBEB_LAYOUT_3F1,
+  CUBEB_LAYOUT_3F1_LFE,
+  CUBEB_LAYOUT_2F2,
+  CUBEB_LAYOUT_2F2_LFE,
+  CUBEB_LAYOUT_3F2,
+  CUBEB_LAYOUT_3F2_LFE,
+  CUBEB_LAYOUT_3F3R_LFE,
+  CUBEB_LAYOUT_3F4_LFE,
+  CUBEB_LAYOUT_MAX
+} cubeb_channel_layout;
+
 /** Stream format initialization parameters. */
 typedef struct {
-  cubeb_sample_format format; /**< Requested sample format.  One of
-                                   #cubeb_sample_format. */
-  unsigned int rate;          /**< Requested sample rate.  Valid range is [1000, 192000]. */
-  unsigned int channels;      /**< Requested channel count.  Valid range is [1, 8]. */
+  cubeb_sample_format format;   /**< Requested sample format.  One of
+                                     #cubeb_sample_format. */
+  unsigned int rate;            /**< Requested sample rate.  Valid range is [1000, 192000]. */
+  unsigned int channels;        /**< Requested channel count.  Valid range is [1, 8]. */
+  cubeb_channel_layout layout;  /**< Requested channel layout. This must be consistent with the provided channels. */
 #if defined(__ANDROID__)
   cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
 #endif
 } cubeb_stream_params;
 
 /** Audio device description */
 typedef struct {
   char * output_name; /**< The name of the output device */
@@ -371,38 +430,47 @@ CUBEB_EXPORT char const * cubeb_get_back
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER
     @retval CUBEB_ERROR_NOT_SUPPORTED
     @retval CUBEB_ERROR */
 CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
 
 /** Get the minimal latency value, in frames, that is guaranteed to work
     when creating a stream for the specified sample rate. This is platform,
-    hardware and backend dependant.
+    hardware and backend dependent.
     @param context A pointer to the cubeb context.
     @param params On some backends, the minimum achievable latency depends on
                   the characteristics of the stream.
     @param latency_frames The latency value, in frames, to pass to
                           cubeb_stream_init.
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER
     @retval CUBEB_ERROR_NOT_SUPPORTED */
 CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
                                        cubeb_stream_params params,
                                        uint32_t * latency_frames);
 
 /** Get the preferred sample rate for this backend: this is hardware and
-    platform dependant, and can avoid resampling, and/or trigger fastpaths.
+    platform dependent, and can avoid resampling, and/or trigger fastpaths.
     @param context A pointer to the cubeb context.
     @param rate The samplerate (in Hz) the current configuration prefers.
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER
     @retval CUBEB_ERROR_NOT_SUPPORTED */
 CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
 
+/** Get the preferred layout for this backend: this is hardware and
+    platform dependent.
+    @param context A pointer to the cubeb context.
+    @param layout The layout of the current speaker configuration.
+    @retval CUBEB_OK
+    @retval CUBEB_ERROR_INVALID_PARAMETER
+    @retval CUBEB_ERROR_NOT_SUPPORTED */
+CUBEB_EXPORT int cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout);
+
 /** Destroy an application context. This must be called after all stream have
  *  been destroyed.
     @param context A pointer to the cubeb context.*/
 CUBEB_EXPORT void cubeb_destroy(cubeb * context);
 
 /** Initialize a stream associated with the supplied application context.
     @param context A pointer to the cubeb context.
     @param stream An out parameter to be filled with the an opaque pointer to a
--- a/media/libcubeb/src/cubeb-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -30,24 +30,33 @@ extern "C" {
 
 /* Crash the caller.  */
 void cubeb_crash() CLANG_ANALYZER_NORETURN;
 
 #if defined(__cplusplus)
 }
 #endif
 
+typedef struct {
+  char const * name;
+  unsigned int const channels;
+  cubeb_channel_layout const layout;
+} cubeb_layout_map;
+
+extern cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX];
+
 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);
   int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
+  int (* get_preferred_channel_layout)(cubeb * context, cubeb_channel_layout * layout);
   int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
                             cubeb_device_collection ** collection);
   void (* destroy)(cubeb * context);
   int (* stream_init)(cubeb * context,
                       cubeb_stream ** stream,
                       char const * stream_name,
                       cubeb_devid input_device,
                       cubeb_stream_params * input_stream_params,
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -50,17 +50,16 @@ int opensl_init(cubeb ** context, char c
 #endif
 #if defined(USE_AUDIOTRACK)
 int audiotrack_init(cubeb ** context, char const * context_name);
 #endif
 #if defined(USE_KAI)
 int kai_init(cubeb ** context, char const * context_name);
 #endif
 
-
 static int
 validate_stream_params(cubeb_stream_params * input_stream_params,
                        cubeb_stream_params * output_stream_params)
 {
   XASSERT(input_stream_params || output_stream_params);
   if (output_stream_params) {
     if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
         output_stream_params->channels < 1 || output_stream_params->channels > 8) {
@@ -91,18 +90,16 @@ validate_stream_params(cubeb_stream_para
   case CUBEB_SAMPLE_FLOAT32LE:
   case CUBEB_SAMPLE_FLOAT32BE:
     return CUBEB_OK;
   }
 
   return CUBEB_ERROR_INVALID_FORMAT;
 }
 
-
-
 static int
 validate_latency(int latency)
 {
   if (latency < 1 || latency > 96000) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
   return CUBEB_OK;
 }
@@ -213,16 +210,30 @@ cubeb_get_preferred_sample_rate(cubeb * 
 
   if (!context->ops->get_preferred_sample_rate) {
     return CUBEB_ERROR_NOT_SUPPORTED;
   }
 
   return context->ops->get_preferred_sample_rate(context, rate);
 }
 
+int
+cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout)
+{
+  if (!context || !layout) {
+    return CUBEB_ERROR_INVALID_PARAMETER;
+  }
+
+  if (!context->ops->get_preferred_channel_layout) {
+    return CUBEB_ERROR_NOT_SUPPORTED;
+  }
+
+  return context->ops->get_preferred_channel_layout(context, layout);
+}
+
 void
 cubeb_destroy(cubeb * context)
 {
   if (!context) {
     return;
   }
 
   context->ops->destroy(context);
@@ -560,9 +571,8 @@ int cubeb_set_log_callback(cubeb_log_lev
 }
 
 void
 cubeb_crash()
 {
   abort();
   *((volatile int *) NULL) = 0;
 }
-
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -77,17 +77,17 @@ enum stream_state {
 
 struct cubeb_stream {
   cubeb * context;
   pthread_mutex_t mutex;
   snd_pcm_t * pcm;
   cubeb_data_callback data_callback;
   cubeb_state_callback state_callback;
   void * user_ptr;
-  snd_pcm_uframes_t write_position;
+  snd_pcm_uframes_t stream_position;
   snd_pcm_uframes_t last_position;
   snd_pcm_uframes_t buffer_size;
   cubeb_stream_params params;
 
   /* Every member after this comment is protected by the owning context's
      mutex rather than the stream's mutex, or is only used on the context's
      run thread. */
   pthread_cond_t cond; /* Signaled when the stream's state is changed. */
@@ -102,16 +102,22 @@ struct cubeb_stream {
 
   /* XXX: Horrible hack -- if an active stream has been idle for
      CUBEB_WATCHDOG_MS it will be disabled and the error callback will be
      called.  This works around a bug seen with older versions of ALSA and
      PulseAudio where streams would stop requesting new data despite still
      being logically active and playing. */
   struct timeval last_activity;
   float volume;
+
+  char * buffer;
+  snd_pcm_uframes_t bufframes;
+  snd_pcm_stream_t stream_type;
+
+  struct cubeb_stream * other_stream;
 };
 
 static int
 any_revents(struct pollfd * fds, nfds_t nfds)
 {
   nfds_t i;
 
   for (i = 0; i < nfds; ++i) {
@@ -230,116 +236,205 @@ static void
 set_timeout(struct timeval * timeout, unsigned int ms)
 {
   gettimeofday(timeout, NULL);
   timeout->tv_sec += ms / 1000;
   timeout->tv_usec += (ms % 1000) * 1000;
 }
 
 static void
+stream_buffer_decrement(cubeb_stream * stm, long count)
+{
+  char * bufremains = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, count);
+  memmove(stm->buffer, bufremains, snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes - count));
+  stm->bufframes -= count;
+}
+
+static void
 alsa_set_stream_state(cubeb_stream * stm, enum stream_state state)
 {
   cubeb * ctx;
   int r;
 
   ctx = stm->context;
   stm->state = state;
   r = pthread_cond_broadcast(&stm->cond);
   assert(r == 0);
   ctx->rebuild = 1;
   poll_wake(ctx);
 }
 
 static enum stream_state
-alsa_refill_stream(cubeb_stream * stm)
+alsa_process_stream(cubeb_stream * stm)
 {
+  unsigned short revents;
   snd_pcm_sframes_t avail;
-  long got;
-  void * p;
   int draining;
 
   draining = 0;
 
   pthread_mutex_lock(&stm->mutex);
 
+  /* Call _poll_descriptors_revents() even if we don't use it
+     to let underlying plugins clear null events.  Otherwise poll()
+     may wake up again and again, producing unnecessary CPU usage. */
+  snd_pcm_poll_descriptors_revents(stm->pcm, stm->fds, stm->nfds, &revents);
+
   avail = snd_pcm_avail_update(stm->pcm);
-  if (avail < 0) {
-    snd_pcm_recover(stm->pcm, avail, 1);
-    avail = snd_pcm_avail_update(stm->pcm);
+
+  /* Got null event? Bail and wait for another wakeup. */
+  if (avail == 0) {
+    pthread_mutex_unlock(&stm->mutex);
+    return RUNNING;
+  }
+
+  /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */
+  if ((unsigned int) avail > stm->buffer_size) {
+    avail = stm->buffer_size;
+  }
+
+  /* Capture: Read available frames */
+  if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) {
+    snd_pcm_sframes_t got;
+
+    if (avail + stm->bufframes > stm->buffer_size) {
+      /* Buffer overflow. Skip and overwrite with new data. */
+      stm->bufframes = 0;
+      // TODO: should it be marked as DRAINING?
+    }
+
+    got = snd_pcm_readi(stm->pcm, stm->buffer+stm->bufframes, avail);
+
+    if (got < 0) {
+      avail = got; // the error handler below will recover us
+    } else {
+      stm->bufframes += got;
+      stm->stream_position += got;
+
+      gettimeofday(&stm->last_activity, NULL);
+    }
+  }
+
+  /* Capture: Pass read frames to callback function */
+  if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 &&
+      (!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) {
+    long wrote = stm->bufframes;
+    struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm;
+    void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL;
+
+    /* Correct write size to the other stream available space */
+    if (stm->other_stream && wrote > stm->other_stream->buffer_size - stm->other_stream->bufframes) {
+      wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes;
+    }
+
+    pthread_mutex_unlock(&stm->mutex);
+    wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote);
+    pthread_mutex_lock(&stm->mutex);
+
+    if (wrote < 0) {
+      avail = wrote; // the error handler below will recover us
+    } else {
+      stream_buffer_decrement(stm, wrote);
+
+      if (stm->other_stream) {
+        stm->other_stream->bufframes += wrote;
+      }
+    }
   }
 
-  /* Failed to recover from an xrun, this stream must be broken. */
+  /* Playback: Don't have enough data? Let's ask for more. */
+  if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > stm->bufframes &&
+      (!stm->other_stream || stm->other_stream->bufframes > 0)) {
+    long got = avail - stm->bufframes;
+    void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
+    char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes);
+
+    /* Correct read size to the other stream available frames */
+    if (stm->other_stream && got > stm->other_stream->bufframes) {
+      got = stm->other_stream->bufframes;
+    }
+
+    pthread_mutex_unlock(&stm->mutex);
+    got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got);
+    pthread_mutex_lock(&stm->mutex);
+
+    if (got < 0) {
+      avail = got; // the error handler below will recover us
+    } else {
+      stm->bufframes += got;
+
+      if (stm->other_stream) {
+        stream_buffer_decrement(stm->other_stream, got);
+      }
+    }
+  }
+
+  /* Playback: Still don't have enough data? Add some silence. */
+  if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > stm->bufframes) {
+    long drain_frames = avail - stm->bufframes;
+    double drain_time = (double) drain_frames / stm->params.rate;
+
+    char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes);
+    memset(buftail, 0, snd_pcm_frames_to_bytes(stm->pcm, drain_frames));
+    stm->bufframes = avail;
+
+    /* Mark as draining, unless we're waiting for capture */
+    if (!stm->other_stream || stm->other_stream->bufframes > 0) {
+      set_timeout(&stm->drain_timeout, drain_time * 1000);
+
+      draining = 1;
+    }
+  }
+
+  /* Playback: Have enough data and no errors. Let's write it out. */
+  if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) {
+    snd_pcm_sframes_t wrote;
+
+    if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
+      float * b = (float *) stm->buffer;
+      for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
+        b[i] *= stm->volume;
+      }
+    } else {
+      short * b = (short *) stm->buffer;
+      for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
+        b[i] *= stm->volume;
+      }
+    }
+
+    wrote = snd_pcm_writei(stm->pcm, stm->buffer, avail);
+    if (wrote < 0) {
+      avail = wrote; // the error handler below will recover us
+    } else {
+      stream_buffer_decrement(stm, wrote);
+
+      stm->stream_position += wrote;
+      gettimeofday(&stm->last_activity, NULL);
+    }
+  }
+
+  /* Got some error? Let's try to recover the stream. */
+  if (avail < 0) {
+    avail = snd_pcm_recover(stm->pcm, avail, 0);
+
+    /* Capture pcm must be started after initial setup/recover */
+    if (avail >= 0 &&
+        stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+        snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) {
+      avail = snd_pcm_start(stm->pcm);
+    }
+  }
+
+  /* Failed to recover, this stream must be broken. */
   if (avail < 0) {
     pthread_mutex_unlock(&stm->mutex);
     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
     return ERROR;
   }
 
-  /* This should never happen. */
-  if ((unsigned int) avail > stm->buffer_size) {
-    avail = stm->buffer_size;
-  }
-
-  /* poll(2) claims this stream is active, so there should be some space
-     available to write.  If avail is still zero here, the stream must be in
-     a funky state, bail and wait for another wakeup. */
-  if (avail == 0) {
-    pthread_mutex_unlock(&stm->mutex);
-    return RUNNING;
-  }
-
-  p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail));
-  assert(p);
-
-  pthread_mutex_unlock(&stm->mutex);
-  got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail);
-  pthread_mutex_lock(&stm->mutex);
-  if (got < 0) {
-    pthread_mutex_unlock(&stm->mutex);
-    stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
-    free(p);
-    return ERROR;
-  }
-  if (got > 0) {
-    snd_pcm_sframes_t wrote;
-
-    if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
-      float * b = (float *) p;
-      for (uint32_t i = 0; i < got * stm->params.channels; i++) {
-        b[i] *= stm->volume;
-      }
-    } else {
-      short * b = (short *) p;
-      for (uint32_t i = 0; i < got * stm->params.channels; i++) {
-        b[i] *= stm->volume;
-      }
-    }
-    wrote = snd_pcm_writei(stm->pcm, p, got);
-    if (wrote < 0) {
-      snd_pcm_recover(stm->pcm, wrote, 1);
-      wrote = snd_pcm_writei(stm->pcm, p, got);
-    }
-    assert(wrote >= 0 && wrote == got);
-    stm->write_position += wrote;
-    gettimeofday(&stm->last_activity, NULL);
-  }
-  if (got != avail) {
-    long buffer_fill = stm->buffer_size - (avail - got);
-    double buffer_time = (double) buffer_fill / stm->params.rate;
-
-    /* Fill the remaining buffer with silence to guarantee one full period
-       has been written. */
-    snd_pcm_writei(stm->pcm, (char *) p + got, avail - got);
-
-    set_timeout(&stm->drain_timeout, buffer_time * 1000);
-
-    draining = 1;
-  }
-
-  free(p);
   pthread_mutex_unlock(&stm->mutex);
   return draining ? DRAINING : RUNNING;
 }
 
 static int
 alsa_run(cubeb * ctx)
 {
   int r;
@@ -385,17 +480,17 @@ alsa_run(cubeb * ctx)
 
     for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
       stm = ctx->streams[i];
       /* We can't use snd_pcm_poll_descriptors_revents here because of
          https://github.com/kinetiknz/cubeb/issues/135. */
       if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) {
         alsa_set_stream_state(stm, PROCESSING);
         pthread_mutex_unlock(&ctx->mutex);
-        state = alsa_refill_stream(stm);
+        state = alsa_process_stream(stm);
         pthread_mutex_lock(&ctx->mutex);
         alsa_set_stream_state(stm, state);
       }
     }
   } else if (r == 0) {
     for (i = 0; i < CUBEB_STREAM_MAX; ++i) {
       stm = ctx->streams[i];
       if (stm) {
@@ -571,25 +666,25 @@ init_local_config_with_workaround(char c
   } while (0);
 
   snd_config_delete(lconf);
 
   return NULL;
 }
 
 static int
-alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config)
+alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config)
 {
   int r;
 
   pthread_mutex_lock(&cubeb_alsa_mutex);
   if (local_config) {
-    r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config);
+    r = snd_pcm_open_lconf(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config);
   } else {
-    r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK);
+    r = snd_pcm_open(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
   }
   pthread_mutex_unlock(&cubeb_alsa_mutex);
 
   return r;
 }
 
 static int
 alsa_locked_pcm_close(snd_pcm_t * pcm)
@@ -702,27 +797,27 @@ alsa_init(cubeb ** context, char const *
   r = pthread_create(&ctx->thread, &attr, alsa_run_thread, ctx);
   assert(r == 0);
 
   r = pthread_attr_destroy(&attr);
   assert(r == 0);
 
   /* Open a dummy PCM to force the configuration space to be evaluated so that
      init_local_config_with_workaround can find and modify the default node. */
-  r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL);
+  r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, NULL);
   if (r >= 0) {
     alsa_locked_pcm_close(dummy);
   }
   ctx->is_pa = 0;
   pthread_mutex_lock(&cubeb_alsa_mutex);
   ctx->local_config = init_local_config_with_workaround(CUBEB_ALSA_PCM_NAME);
   pthread_mutex_unlock(&cubeb_alsa_mutex);
   if (ctx->local_config) {
     ctx->is_pa = 1;
-    r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+    r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
     /* If we got a local_config, we found a PA PCM.  If opening a PCM with that
        config fails with EINVAL, the PA PCM is too old for this workaround. */
     if (r == -EINVAL) {
       pthread_mutex_lock(&cubeb_alsa_mutex);
       snd_config_delete(ctx->local_config);
       pthread_mutex_unlock(&cubeb_alsa_mutex);
       ctx->local_config = NULL;
     } else if (r >= 0) {
@@ -769,48 +864,38 @@ alsa_destroy(cubeb * ctx)
   }
 
   free(ctx);
 }
 
 static void alsa_stream_destroy(cubeb_stream * stm);
 
 static int
-alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
-                 cubeb_devid input_device,
-                 cubeb_stream_params * input_stream_params,
-                 cubeb_devid output_device,
-                 cubeb_stream_params * output_stream_params,
-                 unsigned int latency_frames,
-                 cubeb_data_callback data_callback, cubeb_state_callback state_callback,
-                 void * user_ptr)
+alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+                        snd_pcm_stream_t stream_type,
+                        cubeb_devid deviceid,
+                        cubeb_stream_params * stream_params,
+                        unsigned int latency_frames,
+                        cubeb_data_callback data_callback,
+                        cubeb_state_callback state_callback,
+                        void * user_ptr)
 {
   (void)stream_name;
   cubeb_stream * stm;
   int r;
   snd_pcm_format_t format;
   snd_pcm_uframes_t period_size;
   int latency_us = 0;
-
+  char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME;
 
   assert(ctx && stream);
 
-  if (input_stream_params) {
-    /* Capture support not yet implemented. */
-    return CUBEB_ERROR_NOT_SUPPORTED;
-  }
-
-  if (input_device || output_device) {
-    /* Device selection not yet implemented. */
-    return CUBEB_ERROR_DEVICE_UNAVAILABLE;
-  }
-
   *stream = NULL;
 
-  switch (output_stream_params->format) {
+  switch (stream_params->format) {
   case CUBEB_SAMPLE_S16LE:
     format = SND_PCM_FORMAT_S16_LE;
     break;
   case CUBEB_SAMPLE_S16BE:
     format = SND_PCM_FORMAT_S16_BE;
     break;
   case CUBEB_SAMPLE_FLOAT32LE:
     format = SND_PCM_FORMAT_FLOAT_LE;
@@ -832,24 +917,28 @@ alsa_stream_init(cubeb * ctx, cubeb_stre
 
   stm = calloc(1, sizeof(*stm));
   assert(stm);
 
   stm->context = ctx;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
-  stm->params = *output_stream_params;
+  stm->params = *stream_params;
   stm->state = INACTIVE;
   stm->volume = 1.0;
+  stm->buffer = NULL;
+  stm->bufframes = 0;
+  stm->stream_type = stream_type;
+  stm->other_stream = NULL;
 
   r = pthread_mutex_init(&stm->mutex, NULL);
   assert(r == 0);
 
-  r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config);
+  r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config);
   if (r < 0) {
     alsa_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   r = snd_pcm_nonblock(stm->pcm, 1);
   assert(r == 0);
 
@@ -869,16 +958,21 @@ alsa_stream_init(cubeb * ctx, cubeb_stre
   if (r < 0) {
     alsa_stream_destroy(stm);
     return CUBEB_ERROR_INVALID_FORMAT;
   }
 
   r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size);
   assert(r == 0);
 
+  /* Double internal buffer size to have enough space when waiting for the other side of duplex connection */
+  stm->buffer_size *= 2;
+  stm->buffer = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, stm->buffer_size));
+  assert(stm->buffer);
+
   stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm);
   assert(stm->nfds > 0);
 
   stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
   assert(stm->saved_fds);
   r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds);
   assert((nfds_t) r == stm->nfds);
 
@@ -890,28 +984,72 @@ alsa_stream_init(cubeb * ctx, cubeb_stre
     return CUBEB_ERROR;
   }
 
   *stream = stm;
 
   return CUBEB_OK;
 }
 
+static int
+alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
+                 cubeb_devid input_device,
+                 cubeb_stream_params * input_stream_params,
+                 cubeb_devid output_device,
+                 cubeb_stream_params * output_stream_params,
+                 unsigned int latency_frames,
+                 cubeb_data_callback data_callback, cubeb_state_callback state_callback,
+                 void * user_ptr)
+{
+  int result = CUBEB_OK;
+  cubeb_stream * instm = NULL, * outstm = NULL;
+
+  if (result == CUBEB_OK && input_stream_params) {
+    result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE,
+                                     input_device, input_stream_params, latency_frames,
+                                     data_callback, state_callback, user_ptr);
+  }
+
+  if (result == CUBEB_OK && output_stream_params) {
+    result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK,
+                                     output_device, output_stream_params, latency_frames,
+                                     data_callback, state_callback, user_ptr);
+  }
+
+  if (result == CUBEB_OK && input_stream_params && output_stream_params) {
+    instm->other_stream = outstm;
+    outstm->other_stream = instm;
+  }
+
+  if (result != CUBEB_OK && instm) {
+    alsa_stream_destroy(instm);
+  }
+
+  *stream = outstm ? outstm : instm;
+
+  return result;
+}
+
 static void
 alsa_stream_destroy(cubeb_stream * stm)
 {
   int r;
   cubeb * ctx;
 
   assert(stm && (stm->state == INACTIVE ||
                  stm->state == ERROR ||
                  stm->state == DRAINING));
 
   ctx = stm->context;
 
+  if (stm->other_stream) {
+    stm->other_stream->other_stream = NULL; // to stop infinite recursion
+    alsa_stream_destroy(stm->other_stream);
+  }
+
   pthread_mutex_lock(&stm->mutex);
   if (stm->pcm) {
     if (stm->state == DRAINING) {
       snd_pcm_drain(stm->pcm);
     }
     alsa_locked_pcm_close(stm->pcm);
     stm->pcm = NULL;
   }
@@ -924,16 +1062,18 @@ alsa_stream_destroy(cubeb_stream * stm)
 
   alsa_unregister_stream(stm);
 
   pthread_mutex_lock(&ctx->mutex);
   assert(ctx->active_streams >= 1);
   ctx->active_streams -= 1;
   pthread_mutex_unlock(&ctx->mutex);
 
+  free(stm->buffer);
+
   free(stm);
 }
 
 static int
 alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
 {
   int r;
   cubeb_stream * stm;
@@ -947,16 +1087,18 @@ alsa_get_max_channel_count(cubeb * ctx, 
 
   assert(ctx);
 
   r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL, NULL, NULL);
   if (r != CUBEB_OK) {
     return CUBEB_ERROR;
   }
 
+  assert(stm);
+
   r = snd_pcm_hw_params_any(stm->pcm, hw_params);
   if (r < 0) {
     return CUBEB_ERROR;
   }
 
   r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels);
   if (r < 0) {
     return CUBEB_ERROR;
@@ -1024,17 +1166,28 @@ alsa_get_min_latency(cubeb * ctx, cubeb_
 static int
 alsa_stream_start(cubeb_stream * stm)
 {
   cubeb * ctx;
 
   assert(stm);
   ctx = stm->context;
 
+  if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+    int r = alsa_stream_start(stm->other_stream);
+    if (r != CUBEB_OK)
+      return r;
+  }
+
   pthread_mutex_lock(&stm->mutex);
+  /* Capture pcm must be started after initial setup/recover */
+  if (stm->stream_type == SND_PCM_STREAM_CAPTURE &&
+      snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) {
+    snd_pcm_start(stm->pcm);
+  }
   snd_pcm_pause(stm->pcm, 0);
   gettimeofday(&stm->last_activity, NULL);
   pthread_mutex_unlock(&stm->mutex);
 
   pthread_mutex_lock(&ctx->mutex);
   if (stm->state != INACTIVE) {
     pthread_mutex_unlock(&ctx->mutex);
     return CUBEB_ERROR;
@@ -1049,16 +1202,22 @@ static int
 alsa_stream_stop(cubeb_stream * stm)
 {
   cubeb * ctx;
   int r;
 
   assert(stm);
   ctx = stm->context;
 
+  if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) {
+    int r = alsa_stream_stop(stm->other_stream);
+    if (r != CUBEB_OK)
+      return r;
+  }
+
   pthread_mutex_lock(&ctx->mutex);
   while (stm->state == PROCESSING) {
     r = pthread_cond_wait(&stm->cond, &ctx->mutex);
     assert(r == 0);
   }
 
   alsa_set_stream_state(stm, INACTIVE);
   pthread_mutex_unlock(&ctx->mutex);
@@ -1085,18 +1244,18 @@ alsa_stream_get_position(cubeb_stream * 
     *position = stm->last_position;
     pthread_mutex_unlock(&stm->mutex);
     return CUBEB_OK;
   }
 
   assert(delay >= 0);
 
   *position = 0;
-  if (stm->write_position >= (snd_pcm_uframes_t) delay) {
-    *position = stm->write_position - delay;
+  if (stm->stream_position >= (snd_pcm_uframes_t) delay) {
+    *position = stm->stream_position - delay;
   }
 
   stm->last_position = *position;
 
   pthread_mutex_unlock(&stm->mutex);
   return CUBEB_OK;
 }
 
@@ -1121,23 +1280,73 @@ alsa_stream_set_volume(cubeb_stream * st
   /* setting the volume using an API call does not seem very stable/supported */
   pthread_mutex_lock(&stm->mutex);
   stm->volume = volume;
   pthread_mutex_unlock(&stm->mutex);
 
   return CUBEB_OK;
 }
 
+static int
+alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
+                       cubeb_device_collection ** collection)
+{
+  if (!context)
+    return CUBEB_ERROR;
+
+  uint32_t rate, max_channels;
+  int r;
+
+  r = alsa_get_preferred_sample_rate(context, &rate);
+  if (r != CUBEB_OK) {
+    return CUBEB_ERROR;
+  }
+
+  r = alsa_get_max_channel_count(context, &max_channels);
+  if (r != CUBEB_OK) {
+    return CUBEB_ERROR;
+  }
+
+  *collection = (cubeb_device_collection *) calloc(1, sizeof(cubeb_device_collection) + 1*sizeof(cubeb_device_info *));
+  assert(*collection);
+
+  char const * a_name = "default";
+  (*collection)->device[0] = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info));
+  assert((*collection)->device[0]);
+
+  (*collection)->device[0]->device_id = strdup(a_name);
+  (*collection)->device[0]->devid = (*collection)->device[0]->device_id;
+  (*collection)->device[0]->friendly_name = strdup(a_name);
+  (*collection)->device[0]->group_id = strdup(a_name);
+  (*collection)->device[0]->vendor_name = strdup(a_name);
+  (*collection)->device[0]->type = type;
+  (*collection)->device[0]->state = CUBEB_DEVICE_STATE_ENABLED;
+  (*collection)->device[0]->preferred = CUBEB_DEVICE_PREF_ALL;
+  (*collection)->device[0]->format = CUBEB_DEVICE_FMT_S16NE;
+  (*collection)->device[0]->default_format = CUBEB_DEVICE_FMT_S16NE;
+  (*collection)->device[0]->max_channels = max_channels;
+  (*collection)->device[0]->min_rate = rate;
+  (*collection)->device[0]->max_rate = rate;
+  (*collection)->device[0]->default_rate = rate;
+  (*collection)->device[0]->latency_lo = 0;
+  (*collection)->device[0]->latency_hi = 0;
+
+  (*collection)->count = 1;
+
+  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,
   .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
-  .enumerate_devices = NULL,
+  .get_preferred_channel_layout = NULL,
+  .enumerate_devices = alsa_enumerate_devices,
   .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,
   .stream_set_volume = alsa_stream_set_volume,
--- a/media/libcubeb/src/cubeb_audiotrack.c
+++ b/media/libcubeb/src/cubeb_audiotrack.c
@@ -416,16 +416,17 @@ audiotrack_stream_set_volume(cubeb_strea
 }
 
 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,
   .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
+  .get_preferred_channel_layout = NULL,
   .enumerate_devices = NULL,
   .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.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -35,42 +35,47 @@
 
 #if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
 typedef UInt32  AudioFormatFlags;
 #endif
 
 #define AU_OUT_BUS    0
 #define AU_IN_BUS     1
 
-#define PRINT_ERROR_CODE(str, r) do {                                \
-  LOG("System call failed: %s (rv: %d)", str, r);                    \
-} while(0)
+#define PRINT_ERROR_CODE(str, r) do {                           \
+    LOG("System call failed: %s (rv: %d)", str, (int) r);       \
+  } while(0)
+
+const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
 
 /* Testing empirically, some headsets report a minimal latency that is very
  * low, but this does not work in practice. Lie and say the minimum is 256
  * frames. */
 const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
 const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
 
 void audiounit_stream_stop_internal(cubeb_stream * stm);
 void audiounit_stream_start_internal(cubeb_stream * stm);
-static void close_audiounit_stream(cubeb_stream * stm);
-static int setup_audiounit_stream(cubeb_stream * stm);
+static void audiounit_close_stream(cubeb_stream *stm);
+static int audiounit_setup_stream(cubeb_stream *stm);
 
 extern cubeb_ops const audiounit_ops;
 
 struct cubeb {
   cubeb_ops const * ops = &audiounit_ops;
   owned_critical_section mutex;
   std::atomic<int> active_streams{ 0 };
+  uint32_t global_latency_frames = 0;
   cubeb_device_collection_changed_callback collection_changed_callback = nullptr;
   void * collection_changed_user_ptr = nullptr;
   /* Differentiate input from output devices. */
   cubeb_device_type collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
   std::vector<AudioObjectID> devtype_device_array;
+  // The queue is asynchronously deallocated once all references to it are released
+  dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
 };
 
 struct auto_array_wrapper {
   virtual void push(void * elements, size_t length) = 0;
   virtual size_t length() = 0;
   virtual void push_silence(size_t length) = 0;
   virtual bool pop(size_t length) = 0;
   virtual void * data() = 0;
@@ -128,18 +133,18 @@ private:
 struct cubeb_stream {
   explicit cubeb_stream(cubeb * context);
 
   cubeb * context;
   cubeb_data_callback data_callback = nullptr;
   cubeb_state_callback state_callback = nullptr;
   cubeb_device_changed_callback device_changed_callback = nullptr;
   /* Stream creation parameters */
-  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
-  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
+  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
   bool is_default_input;
   AudioDeviceID input_device = 0;
   AudioDeviceID output_device = 0;
   /* User pointer of data_callback */
   void * user_ptr = nullptr;
   /* Format descriptions */
   AudioStreamBasicDescription input_desc;
   AudioStreamBasicDescription output_desc;
@@ -172,16 +177,17 @@ struct cubeb_stream {
   std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
   /* This is the number of output callback we got in a row. This is usually one,
    * but can be two when the input and output rate are different, and more when
    * a device has been plugged or unplugged, as there can be some time before
    * the device is ready. */
   std::atomic<int> output_callback_in_a_row{ 0 };
   /* This is true if a device change callback is currently running.  */
   std::atomic<bool> switching_device{ false };
+  std::atomic<bool> buffer_size_change_state{ false };
 };
 
 bool has_input(cubeb_stream * stm)
 {
   return stm->input_stream_params.rate != 0;
 }
 
 bool has_output(cubeb_stream * stm)
@@ -223,16 +229,24 @@ audiotimestamp_to_latency(AudioTimeStamp
 
   uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
   uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
 
   return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
 }
 
 static void
+audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
+{
+  stm->mutex.assert_current_thread_owns();
+  assert(stm->context->active_streams == 1);
+  stm->context->global_latency_frames = latency_frames;
+}
+
+static void
 audiounit_make_silent(AudioBuffer * ioData)
 {
   assert(ioData);
   assert(ioData->mData);
   memset(ioData->mData, 0, ioData->mDataByteSize);
 }
 
 static OSStatus
@@ -262,21 +276,22 @@ audiounit_render_input(cubeb_stream * st
     PRINT_ERROR_CODE("AudioUnitRender", r);
     return r;
   }
 
   /* Copy input data in linear buffer. */
   stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
                                  input_frames * stm->input_desc.mChannelsPerFrame);
 
-  LOGV("(%p) input:  buffers %d, size %d, channels %d, frames %d.",
-       stm, input_buffer_list.mNumberBuffers,
-       input_buffer_list.mBuffers[0].mDataByteSize,
-       input_buffer_list.mBuffers[0].mNumberChannels,
-       input_frames);
+  LOGV("(%p) input:  buffers %u, size %u, channels %u, frames %d.",
+       stm,
+       (unsigned int) input_buffer_list.mNumberBuffers,
+       (unsigned int) input_buffer_list.mBuffers[0].mDataByteSize,
+       (unsigned int) input_buffer_list.mBuffers[0].mNumberChannels,
+       (unsigned int) input_frames);
 
   /* Advance input frame counter. */
   assert(input_frames > 0);
   stm->frames_read += input_frames;
 
   return noErr;
 }
 
@@ -284,17 +299,16 @@ static OSStatus
 audiounit_input_callback(void * user_ptr,
                          AudioUnitRenderActionFlags * flags,
                          AudioTimeStamp const * tstamp,
                          UInt32 bus,
                          UInt32 input_frames,
                          AudioBufferList * /* bufs */)
 {
   cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
-  long outframes;
 
   assert(stm->input_unit != NULL);
   assert(AU_IN_BUS == bus);
 
   if (stm->shutdown) {
     LOG("(%p) input shutdown", stm);
     return noErr;
   }
@@ -318,25 +332,25 @@ audiounit_input_callback(void * user_ptr
     stm->output_callback_in_a_row = 0;
     return noErr;
   }
 
   /* Input only. Call the user callback through resampler.
      Resampler will deliver input buffer in the correct rate. */
   assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);
   long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
-  outframes = cubeb_resampler_fill(stm->resampler.get(),
-                                   stm->input_linear_buffer->data(),
-                                   &total_input_frames,
-                                   NULL,
-                                   0);
+  long outframes = cubeb_resampler_fill(stm->resampler.get(),
+                                        stm->input_linear_buffer->data(),
+                                        &total_input_frames,
+                                        NULL,
+                                        0);
   // Reset input buffer
   stm->input_linear_buffer->clear();
 
-  if (outframes < 0 || outframes != input_frames) {
+  if (outframes < 0 || (UInt32) outframes != input_frames) {
     stm->shutdown = true;
     return noErr;
   }
 
   return noErr;
 }
 
 static bool
@@ -370,22 +384,24 @@ audiounit_output_callback(void * user_pt
 {
   assert(AU_OUT_BUS == bus);
   assert(outBufferList->mNumberBuffers == 1);
 
   cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);
 
   stm->output_callback_in_a_row++;
 
-  LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.",
-       stm, outBufferList->mNumberBuffers,
-       outBufferList->mBuffers[0].mDataByteSize,
-       outBufferList->mBuffers[0].mNumberChannels, output_frames);
+  LOGV("(%p) output: buffers %u, size %u, channels %u, frames %u.",
+       stm,
+       (unsigned int) outBufferList->mNumberBuffers,
+       (unsigned int) outBufferList->mBuffers[0].mDataByteSize,
+       (unsigned int) outBufferList->mBuffers[0].mNumberChannels,
+       (unsigned int) output_frames);
 
-  long outframes = 0, input_frames = 0;
+  long input_frames = 0;
   void * output_buffer = NULL, * input_buffer = NULL;
 
   if (stm->shutdown) {
     LOG("(%p) output shutdown.", stm);
     audiounit_make_silent(&outBufferList->mBuffers[0]);
     return noErr;
   }
 
@@ -414,33 +430,33 @@ audiounit_output_callback(void * user_pt
     }
     // The input buffer
     input_buffer = stm->input_linear_buffer->data();
     // Number of input frames in the buffer
     input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
   }
 
   /* Call user callback through resampler. */
-  outframes = cubeb_resampler_fill(stm->resampler.get(),
-                                   input_buffer,
-                                   input_buffer ? &input_frames : NULL,
-                                   output_buffer,
-                                   output_frames);
+  long outframes = cubeb_resampler_fill(stm->resampler.get(),
+                                        input_buffer,
+                                        input_buffer ? &input_frames : NULL,
+                                        output_buffer,
+                                        output_frames);
 
   if (input_buffer) {
     stm->input_linear_buffer->pop(input_frames * stm->input_desc.mChannelsPerFrame);
   }
 
   if (outframes < 0) {
     stm->shutdown = true;
     return noErr;
   }
 
   size_t outbpf = stm->output_desc.mBytesPerFrame;
-  stm->draining = outframes < output_frames;
+  stm->draining = (UInt32) outframes < output_frames;
   stm->frames_played = stm->frames_queued;
   stm->frames_queued += outframes;
 
   AudioFormatFlags outaff = stm->output_desc.mFormatFlags;
   float panning = (stm->output_desc.mChannelsPerFrame == 2) ?
       stm->panning.load(std::memory_order_relaxed) : 0.0f;
 
   /* Post process output samples. */
@@ -528,59 +544,84 @@ audiounit_get_input_device_id(AudioDevic
                                  device_id);
   if (r != noErr) {
     return CUBEB_ERROR;
   }
 
   return CUBEB_OK;
 }
 
+static int
+audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
+{
+  if (is_started) {
+    audiounit_stream_stop_internal(stm);
+  }
+
+  {
+    auto_lock lock(stm->mutex);
+
+    audiounit_close_stream(stm);
+
+    if (audiounit_setup_stream(stm) != CUBEB_OK) {
+      LOG("(%p) Stream reinit failed.", stm);
+      return CUBEB_ERROR;
+    }
+
+    // Reset input frames to force new stream pre-buffer
+    // silence if needed, check `is_extra_input_needed()`
+    stm->frames_read = 0;
+
+    // If the stream was running, start it again.
+    if (is_started) {
+      audiounit_stream_start_internal(stm);
+    }
+  }
+  return CUBEB_OK;
+}
+
 static OSStatus
 audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
                                      const AudioObjectPropertyAddress * addresses,
                                      void * user)
 {
   cubeb_stream * stm = (cubeb_stream*) user;
-  int rv;
-  bool was_running = false;
-
   stm->switching_device = true;
+  // Note if the stream was running or not
+  bool was_running = !stm->shutdown;
 
-  // Note if the stream was running or not
-  was_running = !stm->shutdown;
-
-  LOG("(%p) Audio device changed, %d events.", stm, address_count);
+  LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count);
   for (UInt32 i = 0; i < address_count; i++) {
     switch(addresses[i].mSelector) {
       case kAudioHardwarePropertyDefaultOutputDevice: {
-          LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
+          LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", (unsigned int) i);
           // Allow restart to choose the new default
           stm->output_device = 0;
         }
         break;
       case kAudioHardwarePropertyDefaultInputDevice: {
-          LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i);
+          LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice", (unsigned int) i);
           // Allow restart to choose the new default
           stm->input_device = 0;
         }
       break;
       case kAudioDevicePropertyDeviceIsAlive: {
-          LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i);
+          LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive", (unsigned int) i);
           // If this is the default input device ignore the event,
           // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
           if (stm->is_default_input) {
             LOG("It's the default input device, ignore the event");
             return noErr;
           }
           // Allow restart to choose the new default. Event register only for input.
           stm->input_device = 0;
         }
         break;
       case kAudioDevicePropertyDataSource:
-        LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i);
+        LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i);
         break;
     }
   }
 
   for (UInt32 i = 0; i < address_count; i++) {
     switch(addresses[i].mSelector) {
     case kAudioHardwarePropertyDefaultOutputDevice:
     case kAudioHardwarePropertyDefaultInputDevice:
@@ -591,38 +632,25 @@ audiounit_property_listener_callback(Aud
         if (stm->device_changed_callback) {
           stm->device_changed_callback(stm->user_ptr);
         }
         break;
       }
     }
   }
 
-  // This means the callback won't be called again.
-  audiounit_stream_stop_internal(stm);
-
-  {
-    auto_lock lock(stm->mutex);
-    close_audiounit_stream(stm);
-    rv = setup_audiounit_stream(stm);
-    if (rv != CUBEB_OK) {
-      LOG("(%p) Could not reopen a stream after switching.", stm);
+  // Use a new thread, through the queue, to avoid deadlock when calling
+  // Get/SetProperties method from inside notify callback
+  dispatch_async(stm->context->serial_queue, ^() {
+    if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
       stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
-      return noErr;
+      LOG("(%p) Could not reopen the stream after switching.", stm);
     }
-
-    stm->frames_read = 0;
-
-    // If the stream was running, start it again.
-    if (was_running) {
-      audiounit_stream_start_internal(stm);
-    }
-  }
-
-  stm->switching_device = false;
+    stm->switching_device = false;
+  });
 
   return noErr;
 }
 
 OSStatus
 audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
     AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
 {
@@ -1094,18 +1122,17 @@ audiounit_init_input_linear_buffer(cubeb
 
     assert(stream->input_linear_buffer->length() == silence_size);
   }
 
   return CUBEB_OK;
 }
 
 static uint32_t
-audiounit_clamp_latency(cubeb_stream * stm,
-                              uint32_t latency_frames)
+audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
 {
   // For the 1st stream set anything within safe min-max
   assert(stm->context->active_streams > 0);
   if (stm->context->active_streams == 1) {
     return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
                     SAFE_MIN_LATENCY_FRAMES);
   }
 
@@ -1158,26 +1185,374 @@ audiounit_clamp_latency(cubeb_stream * s
   } else {
     upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
   }
 
   return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
                   SAFE_MIN_LATENCY_FRAMES);
 }
 
+/*
+ * Change buffer size is prone to deadlock thus we change it
+ * following the steps:
+ * - register a listener for the buffer size property
+ * - change the property
+ * - wait until the listener is executed
+ * - property has changed, remove the listener
+ * */
+static void
+buffer_size_changed_callback(void * inClientData,
+                             AudioUnit inUnit,
+                             AudioUnitPropertyID	inPropertyID,
+                             AudioUnitScope		inScope,
+                             AudioUnitElement	inElement)
+{
+  cubeb_stream * stm = (cubeb_stream *)inClientData;
+
+  AudioUnit au = inUnit;
+  AudioUnitScope au_scope = kAudioUnitScope_Input;
+  AudioUnitElement au_element = inElement;
+  const char * au_type = "output";
+
+  if (au == stm->input_unit) {
+    au_scope = kAudioUnitScope_Output;
+    au_type = "input";
+  }
+
+  switch (inPropertyID) {
+
+    case kAudioDevicePropertyBufferFrameSize: {
+      if (inScope != au_scope) {
+        break;
+      }
+      UInt32 new_buffer_size;
+      UInt32 outSize = sizeof(UInt32);
+      OSStatus r = AudioUnitGetProperty(au,
+                                        kAudioDevicePropertyBufferFrameSize,
+                                        au_scope,
+                                        au_element,
+                                        &new_buffer_size,
+                                        &outSize);
+      if (r != noErr) {
+        LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
+      } else {
+        LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
+            au_type, new_buffer_size, inScope);
+      }
+      stm->buffer_size_change_state = true;
+      break;
+    }
+  }
+}
+
+enum set_buffer_size_side {
+  INPUT,
+  OUTPUT,
+};
+
 static int
-setup_audiounit_stream(cubeb_stream * stm)
+audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
+{
+  AudioUnit au = stm->output_unit;
+  AudioUnitScope au_scope = kAudioUnitScope_Input;
+  AudioUnitElement au_element = AU_OUT_BUS;
+  const char * au_type = "output";
+
+  if (set_side == INPUT) {
+    au = stm->input_unit;
+    au_scope = kAudioUnitScope_Output;
+    au_element = AU_IN_BUS;
+    au_type = "input";
+  }
+
+  uint32_t buffer_frames = 0;
+  UInt32 size = sizeof(buffer_frames);
+  int r = AudioUnitGetProperty(au,
+                               kAudioDevicePropertyBufferFrameSize,
+                               au_scope,
+                               au_element,
+                               &buffer_frames,
+                               &size);
+  if (r != noErr) {
+    if (set_side == INPUT) {
+      PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
+    } else {
+      PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
+    }
+    return CUBEB_ERROR;
+  }
+
+  if (new_size_frames == buffer_frames) {
+    LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
+    return CUBEB_OK;
+  }
+
+  r = AudioUnitAddPropertyListener(au,
+                                   kAudioDevicePropertyBufferFrameSize,
+                                   buffer_size_changed_callback,
+                                   stm);
+  if (r != noErr) {
+    if (set_side == INPUT) {
+      PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
+    } else {
+      PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
+    }
+    return CUBEB_ERROR;
+  }
+
+  stm->buffer_size_change_state = false;
+
+  r = AudioUnitSetProperty(au,
+                           kAudioDevicePropertyBufferFrameSize,
+                           au_scope,
+                           au_element,
+                           &new_size_frames,
+                           sizeof(new_size_frames));
+  if (r != noErr) {
+    if (set_side == INPUT) {
+      PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
+    } else {
+      PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
+    }
+
+    r = AudioUnitRemovePropertyListenerWithUserData(au,
+                                                    kAudioDevicePropertyBufferFrameSize,
+                                                    buffer_size_changed_callback,
+                                                    stm);
+    if (r != noErr) {
+      if (set_side == INPUT) {
+        PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
+      } else {
+        PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
+      }
+    }
+
+    return CUBEB_ERROR;
+  }
+
+  int count = 0;
+  while (!stm->buffer_size_change_state && count++ < 30) {
+    struct timespec req, rem;
+    req.tv_sec = 0;
+    req.tv_nsec = 100000000L; // 0.1 sec
+    if (nanosleep(&req , &rem) < 0 ) {
+      LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
+    }
+    LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
+  }
+
+  r = AudioUnitRemovePropertyListenerWithUserData(au,
+                                                  kAudioDevicePropertyBufferFrameSize,
+                                                  buffer_size_changed_callback,
+                                                  stm);
+  if (r != noErr) {
+    if (set_side == INPUT) {
+      PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
+    } else {
+      PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
+    }
+    return CUBEB_ERROR;
+  }
+
+  if (!stm->buffer_size_change_state && count >= 30) {
+    LOG("(%p) Error, did not get buffer size change callback ...", stm);
+    return CUBEB_ERROR;
+  }
+
+  LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
+  return CUBEB_OK;
+}
+
+static int
+audiounit_configure_input(cubeb_stream * stm)
+{
+  int r = 0;
+  UInt32 size;
+  AURenderCallbackStruct aurcbs_in;
+
+  LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
+      stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
+      stm->input_stream_params.format, stm->latency_frames);
+
+  /* Get input device sample rate. */
+  AudioStreamBasicDescription input_hw_desc;
+  size = sizeof(AudioStreamBasicDescription);
+  r = AudioUnitGetProperty(stm->input_unit,
+                           kAudioUnitProperty_StreamFormat,
+                           kAudioUnitScope_Input,
+                           AU_IN_BUS,
+                           &input_hw_desc,
+                           &size);
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
+    return CUBEB_ERROR;
+  }
+  stm->input_hw_rate = input_hw_desc.mSampleRate;
+  LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
+
+  /* Set format description according to the input params. */
+  r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
+  if (r != CUBEB_OK) {
+    LOG("(%p) Setting format description for input failed.", stm);
+    return r;
+  }
+
+  // Use latency to set buffer size
+  stm->input_buffer_frames = stm->latency_frames;
+  r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
+  if (r != CUBEB_OK) {
+    LOG("(%p) Error in change input buffer size.", stm);
+    return CUBEB_ERROR;
+  }
+
+  AudioStreamBasicDescription src_desc = stm->input_desc;
+  /* Input AudioUnit must be configured with device's sample rate.
+     we will resample inside input callback. */
+  src_desc.mSampleRate = stm->input_hw_rate;
+
+  r = AudioUnitSetProperty(stm->input_unit,
+                           kAudioUnitProperty_StreamFormat,
+                           kAudioUnitScope_Output,
+                           AU_IN_BUS,
+                           &src_desc,
+                           sizeof(AudioStreamBasicDescription));
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
+    return CUBEB_ERROR;
+  }
+
+  /* Frames per buffer in the input callback. */
+  r = AudioUnitSetProperty(stm->input_unit,
+                           kAudioUnitProperty_MaximumFramesPerSlice,
+                           kAudioUnitScope_Global,
+                           AU_IN_BUS,
+                           &stm->input_buffer_frames,
+                           sizeof(UInt32));
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
+    return CUBEB_ERROR;
+  }
+
+  // Input only capacity
+  unsigned int array_capacity = 1;
+  if (has_output(stm)) {
+    // Full-duplex increase capacity
+    array_capacity = 8;
+  }
+  if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
+    return CUBEB_ERROR;
+  }
+
+  assert(stm->input_unit != NULL);
+  aurcbs_in.inputProc = audiounit_input_callback;
+  aurcbs_in.inputProcRefCon = stm;
+
+  r = AudioUnitSetProperty(stm->input_unit,
+                           kAudioOutputUnitProperty_SetInputCallback,
+                           kAudioUnitScope_Global,
+                           AU_OUT_BUS,
+                           &aurcbs_in,
+                           sizeof(aurcbs_in));
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
+    return CUBEB_ERROR;
+  }
+  LOG("(%p) Input audiounit init successfully.", stm);
+
+  return CUBEB_OK;
+}
+
+static int
+audiounit_configure_output(cubeb_stream * stm)
+{
+  int r;
+  AURenderCallbackStruct aurcbs_out;
+  UInt32 size;
+
+
+  LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
+      stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
+      stm->output_stream_params.format, stm->latency_frames);
+
+  r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
+  if (r != CUBEB_OK) {
+    LOG("(%p) Could not initialize the audio stream description.", stm);
+    return r;
+  }
+
+  /* Get output device sample rate. */
+  AudioStreamBasicDescription output_hw_desc;
+  size = sizeof(AudioStreamBasicDescription);
+  memset(&output_hw_desc, 0, size);
+  r = AudioUnitGetProperty(stm->output_unit,
+                           kAudioUnitProperty_StreamFormat,
+                           kAudioUnitScope_Output,
+                           AU_OUT_BUS,
+                           &output_hw_desc,
+                           &size);
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
+    return CUBEB_ERROR;
+  }
+  stm->output_hw_rate = output_hw_desc.mSampleRate;
+  LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
+
+  r = AudioUnitSetProperty(stm->output_unit,
+                           kAudioUnitProperty_StreamFormat,
+                           kAudioUnitScope_Input,
+                           AU_OUT_BUS,
+                           &stm->output_desc,
+                           sizeof(AudioStreamBasicDescription));
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
+    return CUBEB_ERROR;
+  }
+
+  r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
+  if (r != CUBEB_OK) {
+    LOG("(%p) Error in change output buffer size.", stm);
+    return CUBEB_ERROR;
+  }
+
+  /* Frames per buffer in the input callback. */
+  r = AudioUnitSetProperty(stm->output_unit,
+                           kAudioUnitProperty_MaximumFramesPerSlice,
+                           kAudioUnitScope_Global,
+                           AU_OUT_BUS,
+                           &stm->latency_frames,
+                           sizeof(UInt32));
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
+    return CUBEB_ERROR;
+  }
+
+  assert(stm->output_unit != NULL);
+  aurcbs_out.inputProc = audiounit_output_callback;
+  aurcbs_out.inputProcRefCon = stm;
+  r = AudioUnitSetProperty(stm->output_unit,
+                           kAudioUnitProperty_SetRenderCallback,
+                           kAudioUnitScope_Global,
+                           AU_OUT_BUS,
+                           &aurcbs_out,
+                           sizeof(aurcbs_out));
+  if (r != noErr) {
+    PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
+    return CUBEB_ERROR;
+  }
+
+  LOG("(%p) Output audiounit init successfully.", stm);
+  return CUBEB_OK;
+}
+
+static int
+audiounit_setup_stream(cubeb_stream * stm)
 {
   stm->mutex.assert_current_thread_owns();
 
-  int r;
-  AURenderCallbackStruct aurcbs_in;
-  AURenderCallbackStruct aurcbs_out;
-  UInt32 size;
-
+  int r = 0;
   if (has_input(stm)) {
     r = audiounit_create_unit(&stm->input_unit, true,
                               &stm->input_stream_params,
                               stm->input_device);
     if (r != CUBEB_OK) {
       LOG("(%p) AudioUnit creation for input failed.", stm);
       return r;
     }
@@ -1188,180 +1563,46 @@ setup_audiounit_stream(cubeb_stream * st
                               &stm->output_stream_params,
                               stm->output_device);
     if (r != CUBEB_OK) {
       LOG("(%p) AudioUnit creation for output failed.", stm);
       return r;
     }
   }
 
+  /* Latency cannot change if another stream is operating in parallel. In this case
+  * latecy is set to the other stream value. */
+  if (stm->context->active_streams > 1) {
+    LOG("(%p) More than one active stream, use global latency.", stm);
+    stm->latency_frames = stm->context->global_latency_frames;
+  } else {
+    /* Silently clamp the latency down to the platform default, because we
+    * synthetize the clock from the callbacks, and we want the clock to update
+    * often. */
+    stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
+    assert(stm->latency_frames); // Ungly error check
+    audiounit_set_global_latency(stm, stm->latency_frames);
+  }
+
   /* Setup Input Stream! */
   if (has_input(stm)) {
-    LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
-        stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
-        stm->input_stream_params.format, stm->latency_frames);
-    /* Get input device sample rate. */
-    AudioStreamBasicDescription input_hw_desc;
-    size = sizeof(AudioStreamBasicDescription);
-    r = AudioUnitGetProperty(stm->input_unit,
-                            kAudioUnitProperty_StreamFormat,
-                            kAudioUnitScope_Input,
-                            AU_IN_BUS,
-                            &input_hw_desc,
-                            &size);
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
-      return CUBEB_ERROR;
-    }
-    stm->input_hw_rate = input_hw_desc.mSampleRate;
-    LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
-
-    /* Set format description according to the input params. */
-    r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
+    r = audiounit_configure_input(stm);
     if (r != CUBEB_OK) {
-      LOG("(%p) Setting format description for input failed.", stm);
+      LOG("(%p) Configure audiounit input failed.", stm);
       return r;
     }
-
-    // Use latency to set buffer size
-    stm->input_buffer_frames = stm->latency_frames;
-    LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames));
-    r = AudioUnitSetProperty(stm->input_unit,
-                             kAudioDevicePropertyBufferFrameSize,
-                             kAudioUnitScope_Output,
-                             AU_IN_BUS,
-                             &stm->input_buffer_frames,
-                             sizeof(UInt32));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
-      return CUBEB_ERROR;
-    }
-
-    AudioStreamBasicDescription src_desc = stm->input_desc;
-    /* Input AudioUnit must be configured with device's sample rate.
-       we will resample inside input callback. */
-    src_desc.mSampleRate = stm->input_hw_rate;
-
-    r = AudioUnitSetProperty(stm->input_unit,
-                             kAudioUnitProperty_StreamFormat,
-                             kAudioUnitScope_Output,
-                             AU_IN_BUS,
-                             &src_desc,
-                             sizeof(AudioStreamBasicDescription));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
-      return CUBEB_ERROR;
-    }
-
-    /* Frames per buffer in the input callback. */
-    r = AudioUnitSetProperty(stm->input_unit,
-                             kAudioUnitProperty_MaximumFramesPerSlice,
-                             kAudioUnitScope_Output,
-                             AU_IN_BUS,
-                             &stm->input_buffer_frames,
-                             sizeof(UInt32));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
-      return CUBEB_ERROR;
-    }
-
-    // Input only capacity
-    unsigned int array_capacity = 1;
-    if (has_output(stm)) {
-      // Full-duplex increase capacity
-      array_capacity = 8;
-    }
-    if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
-      return CUBEB_ERROR;
-    }
-
-    assert(stm->input_unit != NULL);
-    aurcbs_in.inputProc = audiounit_input_callback;
-    aurcbs_in.inputProcRefCon = stm;
-
-    r = AudioUnitSetProperty(stm->input_unit,
-                             kAudioOutputUnitProperty_SetInputCallback,
-                             kAudioUnitScope_Global,
-                             AU_OUT_BUS,
-                             &aurcbs_in,
-                             sizeof(aurcbs_in));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
-      return CUBEB_ERROR;
-    }
-    LOG("(%p) Input audiounit init successfully.", stm);
   }
 
   /* Setup Output Stream! */
   if (has_output(stm)) {
-    LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
-        stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
-        stm->output_stream_params.format, stm->latency_frames);
-    r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
+    r = audiounit_configure_output(stm);
     if (r != CUBEB_OK) {
-      LOG("(%p) Could not initialize the audio stream description.", stm);
+      LOG("(%p) Configure audiounit output failed.", stm);
       return r;
     }
-
-    /* Get output device sample rate. */
-    AudioStreamBasicDescription output_hw_desc;
-    size = sizeof(AudioStreamBasicDescription);
-    memset(&output_hw_desc, 0, size);
-    r = AudioUnitGetProperty(stm->output_unit,
-                             kAudioUnitProperty_StreamFormat,
-                             kAudioUnitScope_Output,
-                             AU_OUT_BUS,
-                             &output_hw_desc,
-                             &size);
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
-      return CUBEB_ERROR;
-    }
-    stm->output_hw_rate = output_hw_desc.mSampleRate;
-    LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
-
-    r = AudioUnitSetProperty(stm->output_unit,
-                             kAudioUnitProperty_StreamFormat,
-                             kAudioUnitScope_Input,
-                             AU_OUT_BUS,
-                             &stm->output_desc,
-                             sizeof(AudioStreamBasicDescription));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
-      return CUBEB_ERROR;
-    }
-
-    // Use latency to calculate buffer size
-    uint32_t output_buffer_frames = stm->latency_frames;
-    LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames);
-    r = AudioUnitSetProperty(stm->output_unit,
-                             kAudioDevicePropertyBufferFrameSize,
-                             kAudioUnitScope_Input,
-                             AU_OUT_BUS,
-                             &output_buffer_frames,
-                             sizeof(output_buffer_frames));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
-      return CUBEB_ERROR;
-    }
-
-    assert(stm->output_unit != NULL);
-    aurcbs_out.inputProc = audiounit_output_callback;
-    aurcbs_out.inputProcRefCon = stm;
-    r = AudioUnitSetProperty(stm->output_unit,
-                             kAudioUnitProperty_SetRenderCallback,
-                             kAudioUnitScope_Global,
-                             AU_OUT_BUS,
-                             &aurcbs_out,
-                             sizeof(aurcbs_out));
-    if (r != noErr) {
-      PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
-      return CUBEB_ERROR;
-    }
-    LOG("(%p) Output audiounit init successfully.", stm);
   }
 
   // Setting the latency doesn't work well for USB headsets (eg. plantronics).
   // Keep the default latency for now.
 #if 0
   buffer_size = latency;
 
   /* Get the range of latency this particular device can work with, and clamp
@@ -1481,54 +1722,49 @@ audiounit_stream_init(cubeb * context,
                       cubeb_state_callback state_callback,
                       void * user_ptr)
 {
   std::unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(nullptr, audiounit_stream_destroy);
   int r;
 
   assert(context);
   *stream = NULL;
-
+  assert(latency_frames > 0);
   if ((input_device && !input_stream_params) ||
       (output_device && !output_stream_params)) {
     return CUBEB_ERROR_INVALID_PARAMETER;
   }
 
-  context->active_streams += 1;
-
   stm.reset(new cubeb_stream(context));
 
   /* These could be different in the future if we have both
    * full-duplex stream and different devices for input vs output. */
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
+  stm->latency_frames = latency_frames;
   if (input_stream_params) {
     stm->input_stream_params = *input_stream_params;
     stm->input_device = reinterpret_cast<uintptr_t>(input_device);
     stm->is_default_input = stm->input_device == 0 ||
                             (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == stm->input_device);
   }
   if (output_stream_params) {
     stm->output_stream_params = *output_stream_params;
     stm->output_device = reinterpret_cast<uintptr_t>(output_device);
   }
 
-  /* Silently clamp the latency down to the platform default, because we
-   * synthetize the clock from the callbacks, and we want the clock to update
-   * often. */
-  stm->latency_frames = audiounit_clamp_latency(stm.get(), latency_frames);
-  assert(latency_frames > 0);
-
+  auto_lock context_lock(context->mutex);
   {
     // It's not critical to lock here, because no other thread has been started
     // yet, but it allows to assert that the lock has been taken in
-    // `setup_audiounit_stream`.
+    // `audiounit_setup_stream`.
+    context->active_streams += 1;
     auto_lock lock(stm->mutex);
-    r = setup_audiounit_stream(stm.get());
+    r = audiounit_setup_stream(stm.get());
   }
 
   if (r != CUBEB_OK) {
     LOG("(%p) Could not setup the audiounit stream.", stm.get());
     return r;
   }
 
   r = audiounit_install_device_changed_callback(stm.get());
@@ -1538,17 +1774,17 @@ audiounit_stream_init(cubeb * context,
   }
 
   *stream = stm.release();
   LOG("Cubeb stream (%p) init successful.", *stream);
   return CUBEB_OK;
 }
 
 static void
-close_audiounit_stream(cubeb_stream * stm)
+audiounit_close_stream(cubeb_stream *stm)
 {
   stm->mutex.assert_current_thread_owns();
   if (stm->input_unit) {
     AudioUnitUninitialize(stm->input_unit);
     AudioComponentInstanceDispose(stm->input_unit);
   }
 
   stm->input_linear_buffer.reset();
@@ -1561,33 +1797,35 @@ close_audiounit_stream(cubeb_stream * st
   stm->resampler.reset();
 }
 
 static void
 audiounit_stream_destroy(cubeb_stream * stm)
 {
   stm->shutdown = true;
 
+  auto_lock context_locl(stm->context->mutex);
   audiounit_stream_stop_internal(stm);
 
   {
     auto_lock lock(stm->mutex);
-    close_audiounit_stream(stm);
+    audiounit_close_stream(stm);
   }
 
 #if !TARGET_OS_IPHONE
   int r = audiounit_uninstall_device_changed_callback(stm);
   if (r != CUBEB_OK) {
     LOG("(%p) Could not uninstall the device changed callback", stm);
   }
 #endif
 
   assert(stm->context->active_streams >= 1);
   stm->context->active_streams -= 1;
 
+  LOG("Cubeb stream (%p) destroyed successful.", stm);
   delete stm;
 }
 
 void
 audiounit_stream_start_internal(cubeb_stream * stm)
 {
   OSStatus r;
   if (stm->input_unit != NULL) {
@@ -1601,16 +1839,17 @@ audiounit_stream_start_internal(cubeb_st
 }
 
 static int
 audiounit_stream_start(cubeb_stream * stm)
 {
   stm->shutdown = false;
   stm->draining = false;
 
+  auto_lock context_locl(stm->context->mutex);
   audiounit_stream_start_internal(stm);
 
   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
 
   LOG("Cubeb stream (%p) started successfully.", stm);
   return CUBEB_OK;
 }
 
@@ -1628,16 +1867,17 @@ audiounit_stream_stop_internal(cubeb_str
   }
 }
 
 static int
 audiounit_stream_stop(cubeb_stream * stm)
 {
   stm->shutdown = true;
 
+  auto_lock context_locl(stm->context->mutex);
   audiounit_stream_stop_internal(stm);
 
   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
 
   LOG("Cubeb stream (%p) stopped successfully.", stm);
   return CUBEB_OK;
 }
 
@@ -2289,16 +2529,17 @@ int audiounit_register_device_collection
 }
 
 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,
   /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate,
+  /*.get_preferred_channel_layout =*/ nullptr,
   /*.enumerate_devices =*/ audiounit_enumerate_devices,
   /*.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_jack.cpp
+++ b/media/libcubeb/src/cubeb_jack.cpp
@@ -109,16 +109,17 @@ static int cbjack_stream_get_position(cu
 static int cbjack_stream_set_volume(cubeb_stream * stm, float volume);
 
 static struct cubeb_ops const cbjack_ops = {
   .init = jack_init,
   .get_backend_id = cbjack_get_backend_id,
   .get_max_channel_count = cbjack_get_max_channel_count,
   .get_min_latency = cbjack_get_min_latency,
   .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
+  .get_preferred_channel_layout = NULL,
   .enumerate_devices = cbjack_enumerate_devices,
   .destroy = cbjack_destroy,
   .stream_init = cbjack_stream_init,
   .stream_destroy = cbjack_stream_destroy,
   .stream_start = cbjack_stream_start,
   .stream_stop = cbjack_stream_stop,
   .stream_get_position = cbjack_stream_get_position,
   .stream_get_latency = cbjack_get_latency,
@@ -415,30 +416,28 @@ cbjack_process(jack_nframes_t nframes, v
           }
         }
       }
     }
   }
   return 0;
 }
 
-
 static void
 cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes)
 {
   float * out_interleaved_buffer = nullptr;
 
   float * inptr = (in != NULL) ? *in : nullptr;
   float * outptr = (bufs_out != NULL) ? *bufs_out : nullptr;
 
   long needed_frames = (bufs_out != NULL) ? nframes : 0;
   long done_frames = 0;
   long input_frames_count = (in != NULL) ? nframes : 0;
 
-
   done_frames = cubeb_resampler_fill(stream->resampler,
                                      inptr,
                                      &input_frames_count,
                                      (bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL,
                                      needed_frames);
 
   out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float;
 
@@ -929,17 +928,16 @@ cbjack_stream_get_position(cubeb_stream 
 
 static int
 cbjack_stream_set_volume(cubeb_stream * stm, float volume)
 {
   stm->volume = volume;
   return CUBEB_OK;
 }
 
-
 static int
 cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
 {
   *device = (cubeb_device *)calloc(1, sizeof(cubeb_device));
   if (*device == NULL)
     return CUBEB_ERROR;
 
   const char * j_in = "JACK capture";
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -0,0 +1,348 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license.  See the
+ * accompanying file LICENSE for details.
+ */
+
+#include <cassert>
+#include "cubeb-internal.h"
+#include "cubeb_mixer.h"
+
+cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX] = {
+  { "undefined",      0,  CUBEB_LAYOUT_UNDEFINED },
+  { "dual mono",      2,  CUBEB_LAYOUT_DUAL_MONO },
+  { "dual mono lfe",  3,  CUBEB_LAYOUT_DUAL_MONO_LFE },
+  { "mono",           1,  CUBEB_LAYOUT_MONO },
+  { "mono lfe",       2,  CUBEB_LAYOUT_MONO_LFE },
+  { "stereo",         2,  CUBEB_LAYOUT_STEREO },
+  { "stereo lfe",     3,  CUBEB_LAYOUT_STEREO_LFE },
+  { "3f",             3,  CUBEB_LAYOUT_3F },
+  { "3f lfe",         4,  CUBEB_LAYOUT_3F_LFE },
+  { "2f1",            3,  CUBEB_LAYOUT_2F1 },
+  { "2f1 lfe",        4,  CUBEB_LAYOUT_2F1_LFE },
+  { "3f1",            4,  CUBEB_LAYOUT_3F1 },
+  { "3f1 lfe",        5,  CUBEB_LAYOUT_3F1_LFE },
+  { "2f2",            4,  CUBEB_LAYOUT_2F2 },
+  { "2f2 lfe",        5,  CUBEB_LAYOUT_2F2_LFE },
+  { "3f2",            5,  CUBEB_LAYOUT_3F2 },
+  { "3f2 lfe",        6,  CUBEB_LAYOUT_3F2_LFE },
+  { "3f3r lfe",       7,  CUBEB_LAYOUT_3F3R_LFE },
+  { "3f4 lfe",        8,  CUBEB_LAYOUT_3F4_LFE }
+};
+
+static int const CHANNEL_ORDER_TO_INDEX[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
+ // M | L | R | C | LS | RS | RLS | RC | RRS | LFE
+  { -1, -1, -1, -1,  -1,  -1,   -1,  -1,   -1,  -1 }, // UNDEFINED
+  { -1,  0,  1, -1,  -1,  -1,   -1,  -1,   -1,  -1 }, // DUAL_MONO
+  { -1,  0,  1, -1,  -1,  -1,   -1,  -1,   -1,   2 }, // DUAL_MONO_LFE
+  {  0, -1, -1, -1,  -1,  -1,   -1,  -1,   -1,  -1 }, // MONO
+  {  0, -1, -1, -1,  -1,  -1,   -1,  -1,   -1,   1 }, // MONO_LFE
+  { -1,  0,  1, -1,  -1,  -1,   -1,  -1,   -1,  -1 }, // STEREO
+  { -1,  0,  1, -1,  -1,  -1,   -1,  -1,   -1,   2 }, // STEREO_LFE
+  { -1,  0,  1,  2,  -1,  -1,   -1,  -1,   -1,  -1 }, // 3F
+  { -1,  0,  1,  2,  -1,  -1,   -1,  -1,   -1,   3 }, // 3F_LFE
+  { -1,  0,  1, -1,  -1,  -1,   -1,   2,   -1,  -1 }, // 2F1
+  { -1,  0,  1, -1,  -1,  -1,   -1,   3,   -1,   2 }, // 2F1_LFE
+  { -1,  0,  1,  2,  -1,  -1,   -1,   3,   -1,  -1 }, // 3F1
+  { -1,  0,  1,  2,  -1,  -1,   -1,   4,   -1,   3 }, // 3F1_LFE
+  { -1,  0,  1, -1,   2,   3,   -1,  -1,   -1,  -1 }, // 2F2
+  { -1,  0,  1, -1,   3,   4,   -1,  -1,   -1,   2 }, // 2F2_LFE
+  { -1,  0,  1,  2,   3,   4,   -1,  -1,   -1,  -1 }, // 3F2
+  { -1,  0,  1,  2,   4,   5,   -1,  -1,   -1,   3 }, // 3F2_LFE
+  { -1,  0,  1,  2,   5,   6,   -1,   4,   -1,   3 }, // 3F3R_LFE
+  { -1,  0,  1,  2,   6,   7,    4,  -1,    5,   3 }, // 3F4_LFE
+};
+
+// The downmix matrix from TABLE 2 in the ITU-R BS.775-3[1] defines a way to
+// convert 3F2 input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 output data. We extend it
+// to convert 3F2-LFE input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs
+// output data.
+// [1] https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.775-3-201208-I!!PDF-E.pdf
+
+// Number of converted layouts: 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs.
+unsigned int const SUPPORTED_LAYOUT_NUM = 12;
+// Number of input channel for downmix conversion.
+unsigned int const INPUT_CHANNEL_NUM = 6; // 3F2-LFE
+// Max number of possible output channels.
+unsigned int const MAX_OUTPUT_CHANNEL_NUM = 5; // 2F2-LFE or 3F1-LFE
+float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
+// Each array contains coefficients that will be multiplied with
+// { L, R, C, LFE, LS, RS } channels respectively.
+static float const DOWNMIX_MATRIX_3F2_LFE[SUPPORTED_LAYOUT_NUM][MAX_OUTPUT_CHANNEL_NUM][INPUT_CHANNEL_NUM] =
+{
+// 1F Mono
+  {
+    { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
+  },
+// 1F Mono-LFE
+  {
+    { INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
+    { 0, 0, 0, 1, 0, 0 }                        // LFE
+  },
+// 2F Stereo
+  {
+    { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 },     // L
+    { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 }      // R
+  },
+// 2F Stereo-LFE
+  {
+    { 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 },     // L
+    { 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 },     // R
+    { 0, 0, 0, 1, 0, 0 }                        // LFE
+  },
+// 3F
+  {
+    { 1, 0, 0, 0, INV_SQRT_2, 0 },              // L
+    { 0, 1, 0, 0, 0, INV_SQRT_2 },              // R
+    { 0, 0, 1, 0, 0, 0 }                        // C
+  },
+// 3F-LFE
+  {
+    { 1, 0, 0, 0, INV_SQRT_2, 0 },              // L
+    { 0, 1, 0, 0, 0, INV_SQRT_2 },              // R
+    { 0, 0, 1, 0, 0, 0 },                       // C
+    { 0, 0, 0, 1, 0, 0 }                        // LFE
+  },
+// 2F1
+  {
+    { 1, 0, INV_SQRT_2, 0, 0, 0 },              // L
+    { 0, 1, INV_SQRT_2, 0, 0, 0 },              // R
+    { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 }      // S
+  },
+// 2F1-LFE
+  {
+    { 1, 0, INV_SQRT_2, 0, 0, 0 },              // L
+    { 0, 1, INV_SQRT_2, 0, 0, 0 },              // R
+    { 0, 0, 0, 1, 0, 0 },                       // LFE
+    { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 }      // S
+  },
+// 3F1
+  {
+    { 1, 0, 0, 0, 0, 0 },                       // L
+    { 0, 1, 0, 0, 0, 0 },                       // R
+    { 0, 0, 1, 0, 0, 0 },                       // C
+    { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 }      // S
+  },
+// 3F1-LFE
+  {
+    { 1, 0, 0, 0, 0, 0 },                       // L
+    { 0, 1, 0, 0, 0, 0 },                       // R
+    { 0, 0, 1, 0, 0, 0 },                       // C
+    { 0, 0, 0, 1, 0, 0 },                       // LFE
+    { 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 }      // S
+  },
+// 2F2
+  {
+    { 1, 0, INV_SQRT_2, 0, 0, 0 },              // L
+    { 0, 1, INV_SQRT_2, 0, 0, 0 },              // R
+    { 0, 0, 0, 0, 1, 0 },                       // LS
+    { 0, 0, 0, 0, 0, 1 }                        // RS
+  },
+// 2F2-LFE
+  {
+    { 1, 0, INV_SQRT_2, 0, 0, 0 },              // L
+    { 0, 1, INV_SQRT_2, 0, 0, 0 },              // R
+    { 0, 0, 0, 1, 0, 0 },                       // LFE
+    { 0, 0, 0, 0, 1, 0 },                       // LS
+    { 0, 0, 0, 0, 0, 1 }                        // RS
+  }
+};
+
+/* Convert audio data from 3F2(-LFE) to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs. */
+template<typename T>
+bool
+downmix_3f2(T const * const in, unsigned long inframes, T * out, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+  if ((in_layout != CUBEB_LAYOUT_3F2 && in_layout != CUBEB_LAYOUT_3F2_LFE) ||
+      out_layout < CUBEB_LAYOUT_MONO || out_layout > CUBEB_LAYOUT_2F2_LFE) {
+    return false;
+  }
+
+  unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
+  unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
+
+  // Conversion from 3F2 to 2F2-LFE or 3F1-LFE is allowed, so we use '<=' instead of '<'.
+  assert(out_channels <= in_channels);
+
+  long out_index = 0;
+  auto & downmix_matrix = DOWNMIX_MATRIX_3F2_LFE[out_layout - CUBEB_LAYOUT_MONO]; // The matrix is started from mono.
+  for (unsigned long i = 0; i < inframes * in_channels; i += in_channels) {
+    for (unsigned int j = 0; j < out_channels; ++j) {
+      out[out_index + j] = 0; // Clear its value.
+      for (unsigned int k = 0 ; k < INPUT_CHANNEL_NUM ; ++k) {
+        // 3F2-LFE has 6 channels: L, R, C, LFE, LS, RS, while 3F2 has only 5
+        // channels: L, R, C, LS, RS. Thus, we need to append 0 to LFE(index 3)
+        // to simulate a 3F2-LFE data when input layout is 3F2.
+        T data = (in_layout == CUBEB_LAYOUT_3F2_LFE) ? in[i + k] : (k == 3) ? 0 : in[i + ((k < 3) ? k : k - 1)];
+        out[out_index + j] += downmix_matrix[j][k] * data;
+      }
+    }
+    out_index += out_channels;
+  }
+
+  return true;
+}
+
+/* Map the audio data by channel name. */
+template<class T>
+bool
+mix_remap(T const * const in, unsigned long inframes, T * out, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout) {
+  assert(in_layout != out_layout);
+  unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
+  unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
+
+  uint32_t in_layout_mask = 0;
+  for (unsigned int i = 0 ; i < in_channels ; ++i) {
+    in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
+  }
+
+  uint32_t out_layout_mask = 0;
+  for (unsigned int i = 0 ; i < out_channels ; ++i) {
+    out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
+  }
+
+  // If there is no matched channel, then do nothing.
+  if (!(out_layout_mask & in_layout_mask)) {
+    return false;
+  }
+
+  long out_index = 0;
+  for (unsigned long i = 0; i < inframes * in_channels; i += in_channels) {
+    for (unsigned int j = 0; j < out_channels; ++j) {
+      cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j];
+      uint32_t channel_mask = 1 << channel;
+      int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel];
+      if (in_layout_mask & channel_mask) {
+        assert(channel_index != -1);
+        out[out_index + j] = in[i + channel_index];
+      } else {
+        assert(channel_index == -1);
+        out[out_index + j] = 0;
+      }
+    }
+    out_index += out_channels;
+  }
+
+  return true;
+}
+
+/* Drop the extra channels beyond the provided output channels. */
+template<typename T>
+void
+downmix_fallback(T const * const in, unsigned long inframes, T * out, unsigned int in_channels, unsigned int out_channels)
+{
+  assert(in_channels >= out_channels);
+  long out_index = 0;
+  for (unsigned long i = 0; i < inframes * in_channels; i += in_channels) {
+    for (unsigned int j = 0; j < out_channels; ++j) {
+      out[out_index + j] = in[i + j];
+    }
+    out_index += out_channels;
+  }
+}
+
+
+template<typename T>
+void
+cubeb_downmix(T const * const in, long inframes, T * out,
+              unsigned int in_channels, unsigned int out_channels,
+              cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+  assert(in_channels >= out_channels && in_layout != CUBEB_LAYOUT_UNDEFINED);
+
+  // If the channel number is different from the layout's setting or it's not a
+  // valid audio 5.1 downmix, then we use fallback downmix mechanism.
+  if (out_channels == CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels &&
+      in_channels == CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels) {
+    if (downmix_3f2(in, inframes, out, in_layout, out_layout)) {
+      return;
+    }
+
+    if (mix_remap(in, inframes, out, in_layout, out_layout)) {
+      return;
+    }
+  }
+
+  downmix_fallback(in, inframes, out, in_channels, out_channels);
+}
+
+/* Upmix function, copies a mono channel into L and R. */
+template<typename T>
+void
+mono_to_stereo(T const * in, long insamples, T * out, unsigned int out_channels)
+{
+  for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) {
+    out[j] = out[j + 1] = in[i];
+  }
+}
+
+template<typename T>
+void
+cubeb_upmix(T const * in, long inframes, T * out,
+            unsigned int in_channels, unsigned int out_channels)
+{
+  assert(out_channels >= in_channels && in_channels > 0);
+
+  /* Either way, if we have 2 or more channels, the first two are L and R. */
+  /* 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, out_channels);
+  } else {
+    /* Copy through. */
+    for (unsigned int i = 0, o = 0; i < inframes * in_channels;
+        i += in_channels, o += out_channels) {
+      for (unsigned int j = 0; j < in_channels; ++j) {
+        out[o + j] = in[i + j];
+      }
+    }
+  }
+
+  /* Check if more channels. */
+  if (out_channels <= 2) {
+    return;
+  }
+
+  /* Put silence in remaining channels. */
+  for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
+    for (unsigned int j = 2; j < out_channels; ++j) {
+      out[o + j] = 0.0;
+    }
+  }
+}
+
+bool
+cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+{
+  return mixer->channels > stream->channels;
+}
+
+bool
+cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
+{
+  if (mixer->channels > stream->channels || mixer->layout == stream->layout) {
+    return false;
+  }
+
+  return mixer->channels < stream->channels ||
+         // When mixer.channels == stream.channels
+         mixer->layout == CUBEB_LAYOUT_UNDEFINED ||  // fallback downmix
+         (stream->layout == CUBEB_LAYOUT_3F2 &&        // 3f2 downmix
+          (mixer->layout == CUBEB_LAYOUT_2F2_LFE ||
+           mixer->layout == CUBEB_LAYOUT_3F1_LFE));
+}
+
+void
+cubeb_downmix_float(float * const in, long inframes, float * out,
+                    unsigned int in_channels, unsigned int out_channels,
+                    cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
+{
+  cubeb_downmix(in, inframes, out, in_channels, out_channels, in_layout, out_layout);
+}
+
+void
+cubeb_upmix_float(float * const in, long inframes, float * out,
+                  unsigned int in_channels, unsigned int out_channels)
+{
+  cubeb_upmix(in, inframes, out, in_channels, out_channels);
+}
new file mode 100644
--- /dev/null
+++ b/media/libcubeb/src/cubeb_mixer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license.  See the
+ * accompanying file LICENSE for details.
+ */
+
+#ifndef CUBEB_MIXER
+#define CUBEB_MIXER
+
+#include "cubeb/cubeb.h" // for cubeb_channel_layout ,CUBEB_CHANNEL_LAYOUT_MAPS and cubeb_stream_params.
+#include <stdbool.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum {
+  CHANNEL_INVALID = -1,
+  CHANNEL_MONO = 0,
+  CHANNEL_LEFT,
+  CHANNEL_RIGHT,
+  CHANNEL_CENTER,
+  CHANNEL_LS,
+  CHANNEL_RS,
+  CHANNEL_RLS,
+  CHANNEL_RCENTER,
+  CHANNEL_RRS,
+  CHANNEL_LFE,
+  CHANNEL_MAX // Max number of supported channels.
+} cubeb_channel;
+
+static cubeb_channel const CHANNEL_INDEX_TO_ORDER[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
+  { CHANNEL_INVALID },                                                                                            // UNDEFINED
+  { CHANNEL_LEFT, CHANNEL_RIGHT },                                                                                // DUAL_MONO
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE },                                                                   // DUAL_MONO_LFE
+  { CHANNEL_MONO },                                                                                               // MONO
+  { CHANNEL_MONO, CHANNEL_LFE },                                                                                  // MONO_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT },                                                                                // STEREO
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE },                                                                   // STEREO_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER },                                                                // 3F
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE },                                                   // 3F_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER },                                                               // 2F1
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER },                                                  // 2F1_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER },                                               // 3F1
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER },                                  // 3F1_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS },                                                        // 2F2
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS },                                           // 2F2_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LS, CHANNEL_RS },                                        // 3F2
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS },                           // 3F2_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER, CHANNEL_LS, CHANNEL_RS },          // 3F3R_LFE
+  { CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RLS, CHANNEL_RRS, CHANNEL_LS, CHANNEL_RS }  // 3F4_LFE
+};
+
+bool cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
+
+bool cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
+
+void cubeb_downmix_float(float * const in, long inframes, float * out,
+                         unsigned int in_channels, unsigned int out_channels,
+                         cubeb_channel_layout in_layout, cubeb_channel_layout out_layout);
+
+void cubeb_upmix_float(float * const in, long inframes, float * out,
+                       unsigned int in_channels, unsigned int out_channels);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // CUBEB_MIXER
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -1735,16 +1735,17 @@ opensl_stream_set_volume(cubeb_stream * 
 }
 
 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,
   .get_preferred_sample_rate = opensl_get_preferred_sample_rate,
+  .get_preferred_channel_layout = NULL,
   .enumerate_devices = NULL,
   .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
@@ -7,25 +7,26 @@
 #undef NDEBUG
 #include <assert.h>
 #include <dlfcn.h>
 #include <stdlib.h>
 #include <pulse/pulseaudio.h>
 #include <string.h>
 #include "cubeb/cubeb.h"
 #include "cubeb-internal.h"
+#include "cubeb_mixer.h"
 #include <stdio.h>
 
 #ifdef DISABLE_LIBPULSE_DLOPEN
 #define WRAP(x) x
 #else
 #define WRAP(x) cubeb_##x
 #define LIBPULSE_API_VISIT(X)                   \
   X(pa_channel_map_can_balance)                 \
-  X(pa_channel_map_init_auto)                   \
+  X(pa_channel_map_init)                        \
   X(pa_context_connect)                         \
   X(pa_context_disconnect)                      \
   X(pa_context_drain)                           \
   X(pa_context_get_server_info)                 \
   X(pa_context_get_sink_info_by_name)           \
   X(pa_context_get_sink_info_list)              \
   X(pa_context_get_sink_input_info)             \
   X(pa_context_get_source_info_list)            \
@@ -463,16 +464,123 @@ stream_update_timing_info(cubeb_stream *
       r = operation_wait(stm->context, stm->input_stream, o);
       WRAP(pa_operation_unref)(o);
     }
   }
 
   return r;
 }
 
+static pa_channel_position_t
+cubeb_channel_to_pa_channel(cubeb_channel channel)
+{
+  assert(channel != CHANNEL_INVALID);
+
+  // This variable may be used for multiple times, so we should avoid to
+  // allocate it in stack, or it will be created and removed repeatedly.
+  // Use static to allocate this local variable in data space instead of stack.
+  static pa_channel_position_t map[CHANNEL_MAX] = {
+    // PA_CHANNEL_POSITION_INVALID,      // CHANNEL_INVALID
+    PA_CHANNEL_POSITION_MONO,         // CHANNEL_MONO
+    PA_CHANNEL_POSITION_FRONT_LEFT,   // CHANNEL_LEFT
+    PA_CHANNEL_POSITION_FRONT_RIGHT,  // CHANNEL_RIGHT
+    PA_CHANNEL_POSITION_FRONT_CENTER, // CHANNEL_CENTER
+    PA_CHANNEL_POSITION_SIDE_LEFT,    // CHANNEL_LS
+    PA_CHANNEL_POSITION_SIDE_RIGHT,   // CHANNEL_RS
+    PA_CHANNEL_POSITION_REAR_LEFT,    // CHANNEL_RLS
+    PA_CHANNEL_POSITION_REAR_CENTER,  // CHANNEL_RCENTER
+    PA_CHANNEL_POSITION_REAR_RIGHT,   // CHANNEL_RRS
+    PA_CHANNEL_POSITION_LFE           // CHANNEL_LFE
+  };
+
+  return map[channel];
+}
+
+static cubeb_channel
+pa_channel_to_cubeb_channel(pa_channel_position_t channel)
+{
+  assert(channel != PA_CHANNEL_POSITION_INVALID);
+  switch(channel) {
+    case PA_CHANNEL_POSITION_MONO: return CHANNEL_MONO;
+    case PA_CHANNEL_POSITION_FRONT_LEFT: return CHANNEL_LEFT;
+    case PA_CHANNEL_POSITION_FRONT_RIGHT: return CHANNEL_RIGHT;
+    case PA_CHANNEL_POSITION_FRONT_CENTER: return CHANNEL_CENTER;
+    case PA_CHANNEL_POSITION_SIDE_LEFT: return CHANNEL_LS;
+    case PA_CHANNEL_POSITION_SIDE_RIGHT: return CHANNEL_RS;
+    case PA_CHANNEL_POSITION_REAR_LEFT: return CHANNEL_RLS;
+    case PA_CHANNEL_POSITION_REAR_CENTER: return CHANNEL_RCENTER;
+    case PA_CHANNEL_POSITION_REAR_RIGHT: return CHANNEL_RRS;
+    case PA_CHANNEL_POSITION_LFE: return CHANNEL_LFE;
+    default: return CHANNEL_INVALID;
+  }
+}
+
+static void
+layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
+{
+  assert(cm && layout != CUBEB_LAYOUT_UNDEFINED);
+
+  WRAP(pa_channel_map_init)(cm);
+  cm->channels = CUBEB_CHANNEL_LAYOUT_MAPS[layout].channels;
+  for (uint8_t i = 0 ; i < cm->channels ; ++i) {
+    cm->map[i] = cubeb_channel_to_pa_channel(CHANNEL_INDEX_TO_ORDER[layout][i]);
+  }
+}
+
+// DUAL_MONO(_LFE) is same as STEREO(_LFE).
+#define MASK_MONO         (1 << CHANNEL_MONO)
+#define MASK_MONO_LFE     (MASK_MONO | (1 << CHANNEL_LFE))
+#define MASK_STEREO       ((1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT))
+#define MASK_STEREO_LFE   (MASK_STEREO | (1 << CHANNEL_LFE))
+#define MASK_3F           (MASK_STEREO | (1 << CHANNEL_CENTER))
+#define MASK_3F_LFE       (MASK_3F | (1 << CHANNEL_LFE))
+#define MASK_2F1          (MASK_STEREO | (1 << CHANNEL_RCENTER))
+#define MASK_2F1_LFE      (MASK_2F1 | (1 << CHANNEL_LFE))
+#define MASK_3F1          (MASK_3F | (1 << CHANNEL_RCENTER))
+#define MASK_3F1_LFE      (MASK_3F1 | (1 << CHANNEL_LFE))
+#define MASK_2F2          (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS))
+#define MASK_2F2_LFE      (MASK_2F2 | (1 << CHANNEL_LFE))
+#define MASK_3F2          (MASK_2F2 | (1 << CHANNEL_CENTER))
+#define MASK_3F2_LFE      (MASK_3F2 | (1 << CHANNEL_LFE))
+#define MASK_3F3R_LFE     (MASK_3F2_LFE | (1 << CHANNEL_RCENTER))
+#define MASK_3F4_LFE      (MASK_3F2_LFE | (1 << CHANNEL_RLS) | (1 << CHANNEL_RRS))
+
+static cubeb_channel_layout
+channel_map_to_layout(pa_channel_map * cm)
+{
+  uint32_t channel_mask = 0;
+  for (uint8_t i = 0 ; i < cm->channels ; ++i) {
+    cubeb_channel channel = pa_channel_to_cubeb_channel(cm->map[i]);
+    if (channel == CHANNEL_INVALID) {
+      return CUBEB_LAYOUT_UNDEFINED;
+    }
+    channel_mask |= 1 << channel;
+  }
+
+  switch(channel_mask) {
+    case MASK_MONO: return CUBEB_LAYOUT_MONO;
+    case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE;
+    case MASK_STEREO: return CUBEB_LAYOUT_STEREO;
+    case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE;
+    case MASK_3F: return CUBEB_LAYOUT_3F;
+    case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE;
+    case MASK_2F1: return CUBEB_LAYOUT_2F1;
+    case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE;
+    case MASK_3F1: return CUBEB_LAYOUT_3F1;
+    case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE;
+    case MASK_2F2: return CUBEB_LAYOUT_2F2;
+    case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE;
+    case MASK_3F2: return CUBEB_LAYOUT_3F2;
+    case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE;
+    case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE;
+    case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE;
+    default: return CUBEB_LAYOUT_UNDEFINED;
+  }
+}
+
 static void pulse_context_destroy(cubeb * ctx);
 static void pulse_destroy(cubeb * ctx);
 
 static int
 pulse_context_init(cubeb * ctx)
 {
   if (ctx->context) {
     assert(ctx->error == 1);
@@ -592,16 +700,33 @@ pulse_get_preferred_sample_rate(cubeb * 
   WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
 
   *rate = ctx->default_sink_info->sample_spec.rate;
 
   return CUBEB_OK;
 }
 
 static int
+pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout)
+{
+  assert(ctx && layout);
+  (void)ctx;
+
+  WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
+  while (!ctx->default_sink_info) {
+    WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
+  }
+  WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
+
+  *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map);
+
+  return CUBEB_OK;
+}
+
+static int
 pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
 {
   (void)ctx;
   // According to PulseAudio developers, this is a safe minimum.
   *latency_frames = 25 * params.rate / 1000;
 
   return CUBEB_OK;
 }
@@ -667,26 +792,30 @@ to_pulse_format(cubeb_sample_format form
 }
 
 static int
 create_pa_stream(cubeb_stream * stm,
                  pa_stream ** pa_stm,
                  cubeb_stream_params * stream_params,
                  char const * stream_name)
 {
-  assert(stm && stream_params);
+  assert(stm && stream_params && stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
+         CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels);
   *pa_stm = NULL;
   pa_sample_spec ss;
   ss.format = to_pulse_format(stream_params->format);
   if (ss.format == PA_SAMPLE_INVALID)
     return CUBEB_ERROR_INVALID_FORMAT;
   ss.rate = stream_params->rate;
   ss.channels = stream_params->channels;
 
-  *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
+  pa_channel_map cm;
+  layout_to_channel_map(stream_params->layout, &cm);
+
+  *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
   return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
 }
 
 static pa_buffer_attr
 set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spec)
 {
   pa_buffer_attr battr;
   battr.maxlength = -1;
@@ -1405,16 +1534,17 @@ pulse_register_device_collection_changed
 }
 
 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,
   .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
+  .get_preferred_channel_layout = pulse_get_preferred_channel_layout,
   .enumerate_devices = pulse_enumerate_devices,
   .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_resampler.cpp
+++ b/media/libcubeb/src/cubeb_resampler.cpp
@@ -119,17 +119,16 @@ cubeb_resampler_speex<T, InputProcessor,
 {
   assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) &&
          output_buffer && output_frames_needed);
 
   long got = 0;
   T * out_unprocessed = nullptr;
   long output_frames_before_processing = 0;
 
-
   /* fill directly the input buffer of the output processor to save a copy */
   output_frames_before_processing =
     output_processor->input_needed_for_output(output_frames_needed);
 
   out_unprocessed =
     output_processor->input_buffer(output_frames_before_processing);
 
   got = data_callback(stream, user_ptr,
@@ -169,17 +168,16 @@ cubeb_resampler_speex<T, InputProcessor,
                            resampled_input, nullptr, resampled_frame_count);
 
   /* Return the number of initial input frames or part of it.
   * Since output_frames_needed == 0 in input scenario, the only
   * available number outside resampler is the initial number of frames. */
   return (*input_frames_count) * (got / resampled_frame_count);
 }
 
-
 template<typename T, typename InputProcessor, typename OutputProcessor>
 long
 cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
 ::fill_internal_duplex(T * in_buffer, long * input_frames_count,
                        T * out_buffer, long output_frames_needed)
 {
   /* The input data, after eventual resampling. This is passed to the callback. */
   T * resampled_input = nullptr;
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -361,16 +361,17 @@ sndio_stream_get_latency(cubeb_stream * 
 }
 
 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,
   .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
+  .get_preferred_channel_layout = NULL,
   .enumerate_devices = NULL,
   .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_utils.h
+++ b/media/libcubeb/src/cubeb_utils.h
@@ -41,16 +41,73 @@ void PodMove(T * destination, const T * 
 template<typename T>
 void PodZero(T * destination, size_t count)
 {
   static_assert(std::is_trivial<T>::value, "Requires trivial type");
   assert(destination);
   memset(destination, 0,  count * sizeof(T));
 }
 
+namespace {
+template<typename T, typename Trait>
+void Copy(T * destination, const T * source, size_t count, Trait)
+{
+  for (size_t i = 0; i < count; i++) {
+    destination[i] = source[i];
+  }
+}
+
+template<typename T>
+void Copy(T * destination, const T * source, size_t count, std::true_type)
+{
+  PodCopy(destination, source, count);
+}
+}
+
+/**
+ * This allows copying a number of elements from a `source` pointer to a
+ * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
+ * calls the constructors and destructors otherwise.
+ */
+template<typename T>
+void Copy(T * destination, const T * source, size_t count)
+{
+  assert(destination && source);
+  Copy(destination, source, count, typename std::is_trivial<T>::type());
+}
+
+namespace {
+template<typename T, typename Trait>
+void ConstructDefault(T * destination, size_t count, Trait)
+{
+  for (size_t i = 0; i < count; i++) {
+    destination[i] = T();
+  }
+}
+
+template<typename T>
+void ConstructDefault(T * destination,
+                      size_t count, std::true_type)
+{
+  PodZero(destination, count);
+}
+}
+
+/**
+ * This allows zeroing (using memset) or default-constructing a number of
+ * elements calling the constructors and destructors if necessary.
+ */
+template<typename T>
+void ConstructDefault(T * destination, size_t count)
+{
+  assert(destination);
+  ConstructDefault(destination, count,
+                   typename std::is_arithmetic<T>::type());
+}
+
 template<typename T>
 class auto_array
 {
 public:
   explicit auto_array(uint32_t capacity = 0)
     : data_(capacity ? new T[capacity] : nullptr)
     , capacity_(capacity)
     , length_(0)
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -1,14 +1,19 @@
 /*
  * Copyright © 2013 Mozilla Foundation
  *
  * This program is made available under an ISC-style license.  See the
  * accompanying file LICENSE for details.
  */
+// Explicitly define NTDDI_VERSION rather than letting the value be derived
+// from _WIN32_WINNT because we depend on values defined for XP SP2 and WS03
+// SP1.
+#define _WIN32_WINNT 0x0502
+#define NTDDI_VERSION 0x05020100
 #define NOMINMAX
 
 #include <initguid.h>
 #include <windows.h>
 #include <mmdeviceapi.h>
 #include <windef.h>
 #include <audioclient.h>
 #include <devicetopology.h>
@@ -22,16 +27,17 @@
 #include <algorithm>
 #include <memory>
 #include <limits>
 #include <atomic>
 #include <vector>
 
 #include "cubeb/cubeb.h"
 #include "cubeb-internal.h"
+#include "cubeb_mixer.h"
 #include "cubeb_resampler.h"
 #include "cubeb_utils.h"
 
 #ifndef PKEY_Device_FriendlyName
 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,    0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);    // DEVPROP_TYPE_STRING
 #endif
 #ifndef PKEY_Device_InstanceId
 DEFINE_PROPERTYKEY(PKEY_Device_InstanceId,      0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); //    VT_LPWSTR
@@ -193,22 +199,22 @@ class wasapi_endpoint_notification_clien
  */
 typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
 
 struct cubeb_stream {
   cubeb * context = nullptr;
   /* Mixer pameters. We need to convert the input stream to this
      samplerate/channel layout, as WASAPI does not resample nor upmix
      itself. */
-  cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
-  cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
+  cubeb_stream_params input_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+  cubeb_stream_params output_mix_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
   /* Stream parameters. This is what the client requested,
    * and what will be presented in the callback. */
-  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
-  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0 };
+  cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
+  cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
   /* The input and output device, or NULL for default. */
   std::unique_ptr<const wchar_t[]> input_device;
   std::unique_ptr<const wchar_t[]> output_device;
   /* The latency initially requested for this stream, in frames. */
   unsigned latency = 0;
   cubeb_state_callback state_callback = nullptr;
   cubeb_data_callback data_callback = nullptr;
   wasapi_refill_callback refill_callback = nullptr;
@@ -387,31 +393,99 @@ bool has_input(cubeb_stream * stm)
   return stm->input_stream_params.rate != 0;
 }
 
 bool has_output(cubeb_stream * stm)
 {
   return stm->output_stream_params.rate != 0;
 }
 
-bool should_upmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
-{
-  return mixer.channels > stream.channels;
-}
-
-bool should_downmix(cubeb_stream_params & stream, cubeb_stream_params & mixer)
-{
-  return mixer.channels < stream.channels;
-}
-
 double stream_to_mix_samplerate_ratio(cubeb_stream_params & stream, cubeb_stream_params & mixer)
 {
   return double(stream.rate) / mixer.rate;
 }
 
+/* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG.
+   See more: https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx */
+#define MASK_DUAL_MONO      (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
+#define MASK_DUAL_MONO_LFE  (MASK_DUAL_MONO | SPEAKER_LOW_FREQUENCY)
+#define MASK_MONO           (KSAUDIO_SPEAKER_MONO)
+#define MASK_MONO_LFE       (MASK_MONO | SPEAKER_LOW_FREQUENCY)
+#define MASK_STEREO         (KSAUDIO_SPEAKER_STEREO)
+#define MASK_STEREO_LFE     (MASK_STEREO | SPEAKER_LOW_FREQUENCY)
+#define MASK_3F             (MASK_STEREO | SPEAKER_FRONT_CENTER)
+#define MASK_3F_LFE         (MASK_3F | SPEAKER_LOW_FREQUENCY)
+#define MASK_2F1            (MASK_STEREO | SPEAKER_BACK_CENTER)
+#define MASK_2F1_LFE        (MASK_2F1 | SPEAKER_LOW_FREQUENCY)
+#define MASK_3F1            (KSAUDIO_SPEAKER_SURROUND)
+#define MASK_3F1_LFE        (MASK_3F1 | SPEAKER_LOW_FREQUENCY)
+#define MASK_2F2            (MASK_STEREO | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
+#define MASK_2F2_LFE        (MASK_2F2 | SPEAKER_LOW_FREQUENCY)
+#define MASK_3F2            (MASK_3F | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
+#define MASK_3F2_LFE        (KSAUDIO_SPEAKER_5POINT1_SURROUND)
+#define MASK_3F3R_LFE       (MASK_3F2_LFE | SPEAKER_BACK_CENTER)
+#define MASK_3F4_LFE        (KSAUDIO_SPEAKER_7POINT1_SURROUND)
+
+static DWORD
+channel_layout_to_mask(cubeb_channel_layout layout)
+{
+  XASSERT(layout > CUBEB_LAYOUT_UNDEFINED && layout < CUBEB_LAYOUT_MAX &&
+          "This mask conversion is not allowed.");
+
+  // This variable may be used for multiple times, so we should avoid to
+  // allocate it in stack, or it will be created and removed repeatedly.
+  // Use static to allocate this local variable in data space instead of stack.
+  static DWORD map[CUBEB_LAYOUT_MAX] = {
+    0,                    // CUBEB_LAYOUT_UNDEFINED (this won't be used.)
+    MASK_DUAL_MONO,       // CUBEB_LAYOUT_DUAL_MONO
+    MASK_DUAL_MONO_LFE,   // CUBEB_LAYOUT_DUAL_MONO_LFE
+    MASK_MONO,            // CUBEB_LAYOUT_MONO
+    MASK_MONO_LFE,        // CUBEB_LAYOUT_MONO_LFE
+    MASK_STEREO,          // CUBEB_LAYOUT_STEREO
+    MASK_STEREO_LFE,      // CUBEB_LAYOUT_STEREO_LFE
+    MASK_3F,              // CUBEB_LAYOUT_3F
+    MASK_3F_LFE,          // CUBEB_LAYOUT_3F_LFE
+    MASK_2F1,             // CUBEB_LAYOUT_2F1
+    MASK_2F1_LFE,         // CUBEB_LAYOUT_2F1_LFE
+    MASK_3F1,             // CUBEB_LAYOUT_3F1
+    MASK_3F1_LFE,         // CUBEB_LAYOUT_3F1_LFE
+    MASK_2F2,             // CUBEB_LAYOUT_2F2
+    MASK_2F2_LFE,         // CUBEB_LAYOUT_2F2_LFE
+    MASK_3F2,             // CUBEB_LAYOUT_3F2
+    MASK_3F2_LFE,         // CUBEB_LAYOUT_3F2_LFE
+    MASK_3F3R_LFE,        // CUBEB_LAYOUT_3F3R_LFE
+    MASK_3F4_LFE,         // CUBEB_LAYOUT_3F4_LFE
+  };
+  return map[layout];
+}
+
+cubeb_channel_layout
+mask_to_channel_layout(DWORD mask)
+{
+  switch (mask) {
+    // MASK_DUAL_MONO(_LFE) is same as STEREO(_LFE), so we skip it.
+    case MASK_MONO: return CUBEB_LAYOUT_MONO;
+    case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE;
+    case MASK_STEREO: return CUBEB_LAYOUT_STEREO;
+    case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE;
+    case MASK_3F: return CUBEB_LAYOUT_3F;
+    case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE;
+    case MASK_2F1: return CUBEB_LAYOUT_2F1;
+    case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE;
+    case MASK_3F1: return CUBEB_LAYOUT_3F1;
+    case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE;
+    case MASK_2F2: return CUBEB_LAYOUT_2F2;
+    case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE;
+    case MASK_3F2: return CUBEB_LAYOUT_3F2;
+    case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE;
+    case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE;
+    case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE;
+    default: return CUBEB_LAYOUT_UNDEFINED;
+  }
+}
 
 uint32_t
 get_rate(cubeb_stream * stm)
 {
   return has_input(stm) ? stm->input_stream_params.rate
                         : stm->output_stream_params.rate;
 }
 
@@ -434,98 +508,40 @@ hns_to_frames(uint32_t rate, REFERENCE_T
 }
 
 REFERENCE_TIME
 frames_to_hns(cubeb_stream * stm, uint32_t frames)
 {
    return frames * 1000 / get_rate(stm);
 }
 
-/* Upmix function, copies a mono channel into L and R */
-template<typename T>
-void
-mono_to_stereo(T * in, long insamples, T * out, int32_t out_channels)
-{
-  for (int i = 0, j = 0; i < insamples; ++i, j += out_channels) {
-    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)
-{
-  XASSERT(out_channels >= in_channels && in_channels > 0);
-
-  /* Either way, if we have 2 or more channels, the first two are L and R. */
-  /* 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, out_channels);
-  } else {
-    /* Copy through. */
-    for (int i = 0, o = 0; i < inframes * in_channels;
-        i += in_channels, o += out_channels) {
-      for (int j = 0; j < in_channels; ++j) {
-        out[o + j] = in[i + j];
-      }
-    }
-  }
-
-  /* Check if more channels. */
-  if (out_channels <= 2) {
-    return;
-  }
-
-  /* Put silence in remaining channels. */
-  for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
-    for (int j = 2; j < out_channels; ++j) {
-      out[o + j] = 0.0;
-    }
-  }
-}
-
-template<typename T>
-void
-downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channels)
-{
-  XASSERT(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. */
-  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
 frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
 {
+  // This is called only when we has a output client.
+  XASSERT(has_output(stm));
   size_t stream_frame_size = stm->output_stream_params.channels * sizeof(float);
   return stream_frame_size * frames;
 }
 
 /* This function handles the processing of the input and output audio,
  * converting it to rate and channel layout specified at initialization.
  * It then calls the data callback, via the resampler. */
 long
 refill(cubeb_stream * stm, float * input_buffer, long input_frames_count,
        float * output_buffer, long output_frames_needed)
 {
   /* If we need to upmix after resampling, resample into the mix buffer to
      avoid a copy. */
   float * dest = nullptr;
   if (has_output(stm)) {
-    if (should_upmix(stm->output_stream_params, stm->output_mix_params) ||
-        should_downmix(stm->output_stream_params, stm->output_mix_params)) {
+    if (cubeb_should_upmix(&stm->output_stream_params, &stm->output_mix_params) ||
+        cubeb_should_downmix(&stm->output_stream_params, &stm->output_mix_params)) {
       dest = stm->mix_buffer.data();
     } else {
       dest = output_buffer;
     }
   }
 
   long out_frames = cubeb_resampler_fill(stm->resampler.get(),
                                          input_buffer,
@@ -546,22 +562,23 @@ refill(cubeb_stream * stm, float * input
     stm->draining = true;
   }
 
   /* If this is not true, there will be glitches.
      It is alright to have produced less frames if we are draining, though. */
   XASSERT(out_frames == output_frames_needed || stm->draining || !has_output(stm));
 
   if (has_output(stm)) {
-    if (should_upmix(stm->output_stream_params, stm->output_mix_params)) {
-      upmix(dest, out_frames, output_buffer,
-            stm->output_stream_params.channels, stm->output_mix_params.channels);
-    } else if (should_downmix(stm->output_stream_params, stm->output_mix_params)) {
-      downmix(dest, out_frames, output_buffer,
-              stm->output_stream_params.channels, stm->output_mix_params.channels);
+    if (cubeb_should_upmix(&stm->output_stream_params, &stm->output_mix_params)) {
+      cubeb_upmix_float(dest, out_frames, output_buffer,
+                        stm->output_stream_params.channels, stm->output_mix_params.channels);
+    } else if (cubeb_should_downmix(&stm->output_stream_params, &stm->output_mix_params)) {
+      cubeb_downmix_float(dest, out_frames, output_buffer,
+                          stm->output_stream_params.channels, stm->output_mix_params.channels,
+                          stm->output_stream_params.layout, stm->output_mix_params.layout);
     }
   }
 
   return out_frames;
 }
 
 /* This helper grabs all the frames available from a capture client, put them in
  * linear_input_buffer. linear_input_buffer should be cleared before the
@@ -610,33 +627,35 @@ bool get_input_buffer(cubeb_stream * stm
       LOG("GetBuffer failed for capture: %lx", hr);
       return false;
     }
     XASSERT(packet_size == next);
     if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
       LOG("insert silence: ps=%u", packet_size);
       stm->linear_input_buffer.push_silence(packet_size * stm->input_stream_params.channels);
     } else {
-      if (should_upmix(stm->input_mix_params, stm->input_stream_params)) {
+      if (cubeb_should_upmix(&stm->input_mix_params, &stm->input_stream_params)) {
         bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
                                                    packet_size * stm->input_stream_params.channels);
         XASSERT(ok);
-        upmix(reinterpret_cast<float*>(input_packet), packet_size,
-              stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
-              stm->input_mix_params.channels,
-              stm->input_stream_params.channels);
+        cubeb_upmix_float(reinterpret_cast<float*>(input_packet), packet_size,
+                          stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
+                          stm->input_mix_params.channels,
+                          stm->input_stream_params.channels);
         stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
-      } else if (should_downmix(stm->input_mix_params, stm->input_stream_params)) {
+      } else if (cubeb_should_downmix(&stm->input_mix_params, &stm->input_stream_params)) {
         bool ok = stm->linear_input_buffer.reserve(stm->linear_input_buffer.length() +
                                                    packet_size * stm->input_stream_params.channels);
         XASSERT(ok);
-        downmix(reinterpret_cast<float*>(input_packet), packet_size,
-                stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
-                stm->input_mix_params.channels,
-                stm->input_stream_params.channels);
+        cubeb_downmix_float(reinterpret_cast<float*>(input_packet), packet_size,
+                            stm->linear_input_buffer.data() + stm->linear_input_buffer.length(),
+                            stm->input_mix_params.channels,
+                            stm->input_stream_params.channels,
+                            stm->input_mix_params.layout,
+                            stm->input_stream_params.layout);
         stm->linear_input_buffer.set_length(stm->linear_input_buffer.length() + packet_size * stm->input_stream_params.channels);
       } else {
         stm->linear_input_buffer.push(reinterpret_cast<float*>(input_packet),
                                       packet_size * stm->input_stream_params.channels);
       }
     }
     hr = stm->capture_client->ReleaseBuffer(packet_size);
     if (FAILED(hr)) {
@@ -776,17 +795,18 @@ refill_callback_input(cubeb_stream * stm
   LOGV("Input callback: input frames: %Iu", stm->linear_input_buffer.length());
 
   long read = refill(stm,
                      stm->linear_input_buffer.data(),
                      stm->linear_input_buffer.length(),
                      nullptr,
                      0);
 
-  consumed_all_buffer = read == stm->linear_input_buffer.length();
+  XASSERT(read >= 0);
+  consumed_all_buffer = (unsigned long) read == stm->linear_input_buffer.length();
 
   stm->linear_input_buffer.clear();
 
   return consumed_all_buffer;
 }
 
 bool
 refill_callback_output(cubeb_stream * stm)
@@ -812,25 +832,25 @@ refill_callback_output(cubeb_stream * st
                     0,
                     output_buffer,
                     output_frames);
 
   LOGV("Output callback: output frames requested: %Iu, got %ld",
        output_frames, got);
 
   XASSERT(got >= 0);
-  XASSERT(got == output_frames || stm->draining);
+  XASSERT((unsigned long) got == output_frames || stm->draining);
 
   hr = stm->render_client->ReleaseBuffer(got, 0);
   if (FAILED(hr)) {
     LOG("failed to release buffer: %lx", hr);
     return false;
   }
 
-  return got == output_frames || stm->draining;
+  return (unsigned long) got == output_frames || stm->draining;
 }
 
 static unsigned int __stdcall
 wasapi_stream_render_loop(LPVOID stream)
 {
   cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
   std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
 
@@ -855,17 +875,16 @@ wasapi_stream_render_loop(LPVOID stream)
      maybe WebRTC. */
   mmcss_handle =
     stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
   if (!mmcss_handle) {
     /* This is not fatal, but we might glitch under heavy load. */
     LOG("Unable to use mmcss to bump the render thread priority: %lx", GetLastError());
   }
 
-
   /* WaitForMultipleObjects timeout can trigger in cases where we don't want to
      treat it as a timeout, such as across a system sleep/wake cycle.  Trigger
      the timeout error handling only when the timeout_limit is reached, which is
      reset on each successful loop. */
   unsigned timeout_count = 0;
   const unsigned timeout_limit = 5;
   while (is_playing) {
     DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
@@ -1206,23 +1225,28 @@ bool stop_and_join_render_thread(cubeb_s
     rv = false;
   }
   if (r == WAIT_FAILED) {
     *(stm->emergency_bailout) = true;
     LOG("Destroy WaitForSingleObject on thread failed: %lx", GetLastError());
     rv = false;
   }
 
-  LOG("Closing thread.");
 
-  CloseHandle(stm->thread);
-  stm->thread = NULL;
+  // Only attempts to close and null out the thread and event if the
+  // WaitForSingleObject above succeeded, so that calling this function again
+  // attemps to clean up the thread and event each time.
+  if (rv) {
+    LOG("Closing thread.");
+    CloseHandle(stm->thread);
+    stm->thread = NULL;
 
-  CloseHandle(stm->shutdown_event);
-  stm->shutdown_event = 0;
+    CloseHandle(stm->shutdown_event);
+    stm->shutdown_event = 0;
+  }
 
   return rv;
 }
 
 void wasapi_destroy(cubeb * context)
 {
   if (context->mmcss_module) {
     FreeLibrary(context->mmcss_module);
@@ -1354,56 +1378,80 @@ wasapi_get_preferred_sample_rate(cubeb *
 
   *rate = mix_format->nSamplesPerSec;
 
   LOG("Preferred sample rate for output: %u", *rate);
 
   return CUBEB_OK;
 }
 
+int
+wasapi_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout)
+{
+  HRESULT hr;
+  auto_com com;
+  if (!com.ok()) {
+    return CUBEB_ERROR;
+  }
+
+  com_ptr<IMMDevice> device;
+  hr = get_default_endpoint(device, eRender);
+  if (FAILED(hr)) {
+    return CUBEB_ERROR;
+  }
+
+  com_ptr<IAudioClient> client;
+  hr = device->Activate(__uuidof(IAudioClient),
+                        CLSCTX_INPROC_SERVER,
+                        NULL, client.receive_vpp());
+  if (FAILED(hr)) {
+    return CUBEB_ERROR;
+  }
+
+  WAVEFORMATEX * tmp = nullptr;
+  hr = client->GetMixFormat(&tmp);
+  if (FAILED(hr)) {
+    return CUBEB_ERROR;
+  }
+  com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
+
+  WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+  *layout = mask_to_channel_layout(format_pcm->dwChannelMask);
+
+  LOG("Preferred channel layout: %s", CUBEB_CHANNEL_LAYOUT_MAPS[*layout].name);
+
+  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,  com_heap_ptr<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) {
-    return;
-  }
-
+  XASSERT(stream_params->layout != CUBEB_LAYOUT_UNDEFINED);
   /* 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;
   }
 
   WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
 
   /* Stash a copy of the original mix format in case we need to restore it later. */
   WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
 
-  /* The hardware is in surround mode, we want to only use front left and front
-     right. Try that, and check if it works. */
-  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:
-      XASSERT(false && "Channel layout not supported.");
-      break;
-  }
+  /* Get the channel mask by the channel layout.
+     If the layout is not supported, we will get a closest settings below. */
+  format_pcm->dwChannelMask = channel_layout_to_mask(stream_params->layout);
+
   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. */
@@ -1494,26 +1542,49 @@ int setup_wasapi_stream_one_side(cubeb_s
   hr = audio_client->GetMixFormat(&tmp);
   if (FAILED(hr)) {
     LOG("Could not fetch current mix format from the audio"
         " client for %s: error: %lx", DIRECTION_NAME, hr);
     return CUBEB_ERROR;
   }
   com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
 
-  handle_channel_layout(stm, mix_format, stream_params);
+  /* Set channel layout only when there're more than two channels. Otherwise,
+   * use the default setting retrieved from the stream format of the audio
+   * engine's internal processing by GetMixFormat. */
+  if (mix_format->nChannels > 2) {
+    /* Currently, we only support mono and stereo for capture stream. */
+    if (direction == eCapture) {
+      XASSERT(false && "Multichannel recording is not supported.");
+    }
+
+    handle_channel_layout(stm, mix_format, stream_params);
+  }
 
   /* Shared mode WASAPI always supports float32 sample format, so this
    * is safe. */
   mix_params->format = CUBEB_SAMPLE_FLOAT32NE;
   mix_params->rate = mix_format->nSamplesPerSec;
   mix_params->channels = mix_format->nChannels;
-  LOG("Setup requested=[f=%d r=%u c=%u] mix=[f=%d r=%u c=%u]",
+  WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
+  mix_params->layout = mask_to_channel_layout(format_pcm->dwChannelMask);
+  if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) {
+    LOG("Output using undefined layout!\n");
+  } else if (mix_format->nChannels != CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels) {
+    // The CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels may be
+    // different from the mix_params->channels. 6 channel ouput with stereo
+    // layout is acceptable in Windows. If this happens, it should not downmix
+    // audio according to layout.
+    LOG("Channel count is different from the layout standard!\n");
+  }
+  LOG("Setup requested=[f=%d r=%u c=%u l=%s] mix=[f=%d r=%u c=%u l=%s]",
       stream_params->format, stream_params->rate, stream_params->channels,
-      mix_params->format, mix_params->rate, mix_params->channels);
+      CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].name,
+      mix_params->format, mix_params->rate, mix_params->channels,
+      CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].name);
 
   hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
                                 AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
                                 AUDCLNT_STREAMFLAGS_NOPERSIST,
                                 frames_to_hns(stm, stm->latency),
                                 0,
                                 mix_format.get(),
                                 NULL);
@@ -1525,18 +1596,18 @@ int setup_wasapi_stream_one_side(cubeb_s
   hr = audio_client->GetBufferSize(buffer_frame_count);
   if (FAILED(hr)) {
     LOG("Could not get the buffer size from the client"
         " for %s %lx.", DIRECTION_NAME, hr);
     return CUBEB_ERROR;
   }
   // Input is up/down mixed when depacketized in get_input_buffer.
   if (has_output(stm) &&
-      (should_upmix(*stream_params, *mix_params) ||
-       should_downmix(*stream_params, *mix_params))) {
+      (cubeb_should_upmix(stream_params, mix_params) ||
+       cubeb_should_downmix(stream_params, mix_params))) {
     stm->mix_buffer.resize(frames_to_bytes_before_mix(stm, *buffer_frame_count));
   }
 
   hr = audio_client->SetEventHandle(event);
   if (FAILED(hr)) {
     LOG("Could set the event handle for the %s client %lx.",
         DIRECTION_NAME, hr);
     return CUBEB_ERROR;
@@ -1707,20 +1778,24 @@ wasapi_stream_init(cubeb * context, cube
 
   stm->context = context;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
   if (input_stream_params) {
     stm->input_stream_params = *input_stream_params;
     stm->input_device = utf8_to_wstr(reinterpret_cast<char const *>(input_device));
+    // Make sure the layout matches the channel count.
+    XASSERT(stm->input_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->input_stream_params.layout].channels);
   }
   if (output_stream_params) {
     stm->output_stream_params = *output_stream_params;
     stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
+    // Make sure the layout matches the channel count.
+    XASSERT(stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels);
   }
 
   stm->latency = latency_frames;
 
   stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
   if (!stm->reconfigure_event) {
     LOG("Can't create the reconfigure event, error: %lx", GetLastError());
     return CUBEB_ERROR;
@@ -1916,17 +1991,16 @@ int wasapi_stream_stop(cubeb_stream * st
     if (stm->input_client) {
       hr = stm->input_client->Stop();
       if (FAILED(hr)) {
         LOG("could not stop AudioClient (input)");
         return CUBEB_ERROR;
       }
     }
 
-
     stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
   }
 
   if (stop_and_join_render_thread(stm)) {
     if (stm->emergency_bailout.load()) {
       delete stm->emergency_bailout.load();
       stm->emergency_bailout = nullptr;
     }
@@ -2247,16 +2321,17 @@ wasapi_enumerate_devices(cubeb * context
 }
 
 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_min_latency =*/ wasapi_get_min_latency,
   /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
+  /*.get_preferred_channel_layout =*/ wasapi_get_preferred_channel_layout,
   /*.enumerate_devices =*/ wasapi_enumerate_devices,
   /*.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
@@ -507,17 +507,16 @@ winmm_stream_init(cubeb * context, cubeb
   }
 
   r = waveOutPause(stm->waveout);
   if (r != MMSYSERR_NOERROR) {
     winmm_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
-
   for (i = 0; i < NBUFS; ++i) {
     WAVEHDR * hdr = &stm->buffers[i];
 
     hdr->lpData = calloc(1, bufsz);
     XASSERT(hdr->lpData);
     hdr->dwBufferLength = bufsz;
     hdr->dwFlags = 0;
 
@@ -764,17 +763,16 @@ winmm_calculate_device_rate(cubeb_device
     if (info->min_rate == 0) {
       info->min_rate = 96000;
       info->default_rate = 96000;
     }
     info->max_rate = 96000;
   }
 }
 
-
 #define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \
     WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
 static int
 winmm_query_supported_formats(UINT devid, DWORD formats,
     cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt)
 {
   WAVEFORMATEXTENSIBLE wfx;
 
@@ -1045,16 +1043,17 @@ winmm_enumerate_devices(cubeb * context,
 }
 
 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,
   /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
+  /*.get_preferred_channel_layout =*/ NULL,
   /*.enumerate_devices =*/ winmm_enumerate_devices,
   /*.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,
--- a/media/libcubeb/src/moz.build
+++ b/media/libcubeb/src/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DEFINES['CUBEB_GECKO_BUILD'] = True
 
 Library('cubeb')
 
 SOURCES += [
     'cubeb.c',
+    'cubeb_mixer.cpp',
     'cubeb_panner.cpp'
 ]
 
 if CONFIG['MOZ_ALSA']:
     SOURCES += [
         'cubeb_alsa.c',
     ]
     DEFINES['USE_ALSA'] = True
--- a/media/libcubeb/update.sh
+++ b/media/libcubeb/update.sh
@@ -26,27 +26,30 @@ cp $1/src/cubeb_resampler.h src
 cp $1/src/cubeb_resampler_internal.h src
 cp $1/src/cubeb_ring_array.h src
 cp $1/src/cubeb_sndio.c src
 cp $1/src/cubeb_utils.h src
 cp $1/src/cubeb_utils_unix.h src
 cp $1/src/cubeb_utils_win.h src
 cp $1/src/cubeb_wasapi.cpp src
 cp $1/src/cubeb_winmm.c src
+cp $1/src/cubeb_mixer.h src
+cp $1/src/cubeb_mixer.cpp src
 cp $1/test/common.h gtest
 cp $1/test/test_audio.cpp gtest
 cp $1/test/test_devices.cpp gtest
 cp $1/test/test_duplex.cpp gtest
 cp $1/test/test_latency.cpp gtest
 cp $1/test/test_record.cpp gtest
 cp $1/test/test_resampler.cpp gtest
 cp $1/test/test_ring_array.cpp gtest
 cp $1/test/test_sanity.cpp gtest
 cp $1/test/test_tone.cpp gtest
 cp $1/test/test_utils.cpp gtest
+cp $1/test/test_mixer.cpp gtest
 
 if [ -d $1/.git ]; then
   rev=$(cd $1 && git rev-parse --verify HEAD)
   dirty=$(cd $1 && git diff-index --name-only HEAD)
 fi
 
 if [ -n "$rev" ]; then
   version=$rev