Bug 1432779 - P9. Rework cubeb_mixer. r?padenot,r?kamidphish draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Thu, 08 Mar 2018 22:17:12 +0100
changeset 768206 813987980b95ec1d54a0c6d2487a46f09cbae1ae
parent 768205 328e8710f8482c487800b8fcb07868fa95ee8932
child 768207 a26e00c6102edda8db60dcfaa651a50cabb7edec
push id102819
push userbmo:jyavenard@mozilla.com
push dateThu, 15 Mar 2018 19:46:59 +0000
reviewerspadenot, kamidphish
bugs1432779
milestone61.0a1
Bug 1432779 - P9. Rework cubeb_mixer. r?padenot,r?kamidphish This completely replace the existing remixer which had serious limitations: 1- Had no memory bound checks 2- Could only downmix 5.1 and 7.1 to stereo. This mixer allows to convert from any sane layout to any other and work directly on interleaved samples. This cubeb_mixer doesn't have an API compatible with the previous one. This commit is non-fonctional, and was split for ease of review. MozReview-Commit-ID: ECsGE7VMzbE
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb_mixer.cpp
media/libcubeb/src/cubeb_mixer.h
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -152,73 +152,76 @@ 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   RC
- * 2F1-LFE        L   R   LFE  RC
- * 3F1            L   R   C    RC
- * 3F1-LFE        L   R   C    LFE  RC
- * 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 {
+  CHANNEL_UNKNOWN = 0,
+  CHANNEL_FRONT_LEFT = 1 << 0,
+  CHANNEL_FRONT_RIGHT = 1 << 1,
+  CHANNEL_FRONT_CENTER = 1 << 2,
+  CHANNEL_LOW_FREQUENCY = 1 << 3,
+  CHANNEL_BACK_LEFT = 1 << 4,
+  CHANNEL_BACK_RIGHT = 1 << 5,
+  CHANNEL_FRONT_LEFT_OF_CENTER = 1 << 6,
+  CHANNEL_FRONT_RIGHT_OF_CENTER = 1 << 7,
+  CHANNEL_BACK_CENTER = 1 << 8,
+  CHANNEL_SIDE_LEFT = 1 << 9,
+  CHANNEL_SIDE_RIGHT = 1 << 10,
+  CHANNEL_TOP_CENTER = 1 << 11,
+  CHANNEL_TOP_FRONT_LEFT = 1 << 12,
+  CHANNEL_TOP_FRONT_CENTER = 1 << 13,
+  CHANNEL_TOP_FRONT_RIGHT = 1 << 14,
+  CHANNEL_TOP_BACK_LEFT = 1 << 15,
+  CHANNEL_TOP_BACK_CENTER = 1 << 16,
+  CHANNEL_TOP_BACK_RIGHT = 1 << 17
+} cubeb_channel;
 
-typedef enum {
-  CUBEB_LAYOUT_UNDEFINED, // Indicate the speaker's layout is undefined.
-  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_QUAD,
-  CUBEB_LAYOUT_QUAD_LFE,
-  CUBEB_LAYOUT_3F2,
-  CUBEB_LAYOUT_3F2_LFE,
-  CUBEB_LAYOUT_3F3R_LFE,
-  CUBEB_LAYOUT_3F4_LFE,
-  CUBEB_LAYOUT_MAX
-} cubeb_channel_layout;
+typedef uint32_t cubeb_channel_layout;
+// Some common layout definitions.
+enum {
+  CUBEB_LAYOUT_UNDEFINED = 0, // Indicate the speaker's layout is undefined.
+  CUBEB_LAYOUT_MONO = CHANNEL_FRONT_CENTER,
+  CUBEB_LAYOUT_MONO_LFE = CUBEB_LAYOUT_MONO | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
+  CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_3F =
+    CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
+  CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_2F1 =
+    CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
+  CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+                     CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
+  CUBEB_LAYOUT_3F1_LFE = CUBEB_LAYOUT_3F1 | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_2F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+                     CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+  CUBEB_LAYOUT_2F2_LFE = CUBEB_LAYOUT_2F2 | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_QUAD = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+                      CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT,
+  CUBEB_LAYOUT_QUAD_LFE = CUBEB_LAYOUT_QUAD | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_3F2 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+                     CHANNEL_FRONT_CENTER | CHANNEL_SIDE_LEFT |
+                     CHANNEL_SIDE_RIGHT,
+  CUBEB_LAYOUT_3F2_LFE = CUBEB_LAYOUT_3F2 | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_3F2_BACK = CUBEB_LAYOUT_QUAD | 1 << CHANNEL_FRONT_CENTER,
+  CUBEB_LAYOUT_3F2_LFE_BACK = CUBEB_LAYOUT_3F2_BACK | CHANNEL_LOW_FREQUENCY,
+  CUBEB_LAYOUT_3F3R_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+                          CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+                          CHANNEL_BACK_CENTER | CHANNEL_SIDE_LEFT |
+                          CHANNEL_SIDE_RIGHT,
+  CUBEB_LAYOUT_3F4_LFE = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+                         CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY |
+                         CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+                         CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT,
+};
 
 /** Miscellaneous stream preferences. */
 typedef enum {
   CUBEB_STREAM_PREF_NONE     = 0x00, /**< No stream preferences are requested. */
   CUBEB_STREAM_PREF_LOOPBACK = 0x01 /**< Request a loopback stream. Should be
                                          specified on the input params and an
                                          output device to loopback from should
                                          be passed in place of an input device. */
--- a/media/libcubeb/src/cubeb_mixer.cpp
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -1,590 +1,601 @@
 /*
  * Copyright © 2016 Mozilla Foundation
  *
  * This program is made available under an ISC-style license.  See the
  * accompanying file LICENSE for details.
+ *
+ * Adapted from code based on libswresample's rematrix.c
  */
 
+#define NOMINMAX
+
+#include <algorithm>
 #include <cassert>
+#include <cmath>
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+#include <memory>
+#include <type_traits>
 #include "cubeb-internal.h"
 #include "cubeb_mixer.h"
+#include "cubeb_utils.h"
 
-#define MASK_MONO         (1 << CHANNEL_CENTER)
-#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_RLS) | (1 << CHANNEL_RRS))
-#define MASK_2F2_LFE      (MASK_2F2 | (1 << CHANNEL_LFE))
-#define MASK_QUAD         (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS))
-#define MASK_QUAD_LFE     (MASK_QUAD | (1 << CHANNEL_LFE))
-#define MASK_3F2          (MASK_QUAD | (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))
+#ifndef FF_ARRAY_ELEMS
+#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#define CHANNELS_MAX 32
+#define FRONT_LEFT             0
+#define FRONT_RIGHT            1
+#define FRONT_CENTER           2
+#define LOW_FREQUENCY          3
+#define BACK_LEFT              4
+#define BACK_RIGHT             5
+#define FRONT_LEFT_OF_CENTER   6
+#define FRONT_RIGHT_OF_CENTER  7
+#define BACK_CENTER            8
+#define SIDE_LEFT              9
+#define SIDE_RIGHT             10
+#define TOP_CENTER             11
+#define TOP_FRONT_LEFT         12
+#define TOP_FRONT_CENTER       13
+#define TOP_FRONT_RIGHT        14
+#define TOP_BACK_LEFT          15
+#define TOP_BACK_CENTER        16
+#define TOP_BACK_RIGHT         17
+#define NUM_NAMED_CHANNELS     18
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2      0.70710678118654752440  /* 1/sqrt(2) */
+#endif
+#ifndef M_SQRT2
+#define M_SQRT2        1.41421356237309504880  /* sqrt(2) */
+#endif
+#define SQRT3_2      1.22474487139158904909  /* sqrt(3/2) */
 
-cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map)
+#define  C30DB  M_SQRT2
+#define  C15DB  1.189207115
+#define C__0DB  1.0
+#define C_15DB  0.840896415
+#define C_30DB  M_SQRT1_2
+#define C_45DB  0.594603558
+#define C_60DB  0.5
+
+unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
 {
-  uint32_t channel_mask = 0;
-  for (uint8_t i = 0 ; i < channel_map->channels ; ++i) {
-    if (channel_map->map[i] == CHANNEL_INVALID ||
-        channel_map->map[i] == CHANNEL_UNMAPPED) {
-      return CUBEB_LAYOUT_UNDEFINED;
-    }
-    channel_mask |= 1 << channel_map->map[i];
+#if __GNUC__ || __clang__
+  return __builtin_popcount (x);
+#elif _MSC_VER
+  return __popcnt(x);
+#else
+  x -= (x >> 1) & 0x55555555;
+  x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+  x = (x + (x >> 4)) & 0x0F0F0F0F;
+  x += x >> 8;
+  return (x + (x >> 16)) & 0x3F;
+#endif
+}
+struct MixerContext {
+  MixerContext(cubeb_sample_format f,
+               cubeb_channel_layout in,
+               cubeb_channel_layout out)
+    : _format(f)
+    , _in_ch_layout(in)
+    , _out_ch_layout(out)
+    , _in_ch_count(cubeb_channel_layout_nb_channels(in))
+    , _out_ch_count(cubeb_channel_layout_nb_channels(out))
+  {
+    _valid = init() >= 0;
   }
 
-  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_QUAD: return CUBEB_LAYOUT_QUAD;
-    case MASK_QUAD_LFE: return CUBEB_LAYOUT_QUAD_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;
-  }
-}
-
-cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX] = {
-  { "undefined",      0,  CUBEB_LAYOUT_UNDEFINED },
-  { "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 },
-  { "quad",           4,  CUBEB_LAYOUT_QUAD },
-  { "quad lfe",       5,  CUBEB_LAYOUT_QUAD_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
-  {  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,  -1,  -1,    2,  -1,    3,  -1 }, // 2F2
-  { -1,  0,  1, -1,  -1,  -1,    3,   4,   -1,   2 }, // 2F2_LFE
-  { -1,  0,  1, -1,   2,   3,   -1,  -1,   -1,  -1 }, // QUAD
-  { -1,  0,  1, -1,   3,   4,   -1,  -1,   -1,   2 }, // QUAD_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, QUAD and their LFEs.
-unsigned int const SUPPORTED_LAYOUT_NUM = 14;
-// 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 or QUAD_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
+  static bool even(cubeb_channel_layout layout)
   {
-    { 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
-  },
-// QUAD
-  {
-    { 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
-  },
-// QUAD-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.
-//
-// ITU-R BS.775-3[1] provides spec for downmixing 3F2 data to 1F, 2F, 3F, 2F1,
-// 3F1, 2F2 data. We simply add LFE to its defined matrix. If both the input
-// and output have LFE channel, then we pass it's data. If only input or output
-// has LFE, then we either drop it or append 0 to the LFE channel.
-//
-// Fig. 1:
-// |<-------------- 1 -------------->|<-------------- 2 -------------->|
-// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+
-// | L0 | R0 | C0 | LFE0 | LS0 | RS0 | L1 | R1 | C1 | LFE1 | LS1 | RS1 | ...
-// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+
-//
-// Fig. 2:
-// |<-- 1 -->|<-- 2 -->|
-// +----+----+----+----+
-// | L0 | R0 | L1 | R1 | ...
-// +----+----+----+----+
-//
-// The figures above shows an example for downmixing from 3F2-LFE(Fig. 1) to
-// to stereo(Fig. 2), where L0 = L0 + 0.707 * (C0 + LS0),
-// R0 = R0 + 0.707 * (C0 + RS0), L1 = L1 + 0.707 * (C1 + LS1),
-// R1 = R1 + 0.707 * (C1 + RS1), ...
-//
-// Nevertheless, the downmixing method is a little bit different on OSX.
-// The audio rendering mechanism on OS X will drop the extra channels beyond
-// the channels that audio device can provide. The trick here is that OSX allows
-// us to set the layout containing other channels that the output device can
-// NOT provide. For example, setting 3F2-LFE layout to a stereo device is fine.
-// Therefore, OSX expects we fill the buffer for playing sound by the defined
-// layout, so there are some will-be-dropped data in the buffer:
-//
-// +---+---+---+-----+----+----+
-// | L | R | C | LFE | LS | RS | ...
-// +---+---+---+-----+----+----+
-//           ^    ^    ^    ^
-//           The data for these four channels will be dropped!
-//
-// To keep all the information, we need to downmix the data before it's dropped.
-// The figure below shows an example for downmixing from 3F2-LFE(Fig. 1)
-// to stereo(Fig. 3) on OSX, where the LO, R0, L1, R0 are same as above.
-//
-// Fig. 3:
-// |<---------- 1 ---------->|<---------- 2 ---------->|
-// +----+----+---+---+---+---+----+----+---+---+---+---+
-// | L0 | R0 | x | x | x | x | L1 | R1 | x | x | x | x | ...
-// +----+----+---+---+---+---+----+----+---+---+---+---+
-//           |<--  dummy  -->|         |<--  dummy  -->|
-template<typename T>
-bool
-downmix_3f2(unsigned long inframes,
-            T const * const in, unsigned long in_len,
-            T * out, unsigned long out_len,
-            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_3F2) {
+    if (!layout) {
+      return true;
+    }
+    if (layout & (layout - 1)) {
+      return true;
+    }
     return false;
   }
 
-  unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
-  unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
+  // Ensure that the layout is sane (that is have symmetrical left/right
+  // channels), if not, layout will be treated as mono.
+  static cubeb_channel_layout clean_layout(cubeb_channel_layout layout)
+  {
+    if (layout && layout != CHANNEL_FRONT_LEFT && !(layout & (layout - 1))) {
+      LOG("Treating layout as mono");
+      return CHANNEL_FRONT_CENTER;
+    }
+
+    return layout;
+  }
 
-  // Conversion from 3F2 to 2F2-LFE or 3F1-LFE is allowed, so we use '<=' instead of '<'.
-  assert(out_channels <= in_channels);
+  static bool sane_layout(cubeb_channel_layout layout)
+  {
+    if (!(layout & CUBEB_LAYOUT_3F)) { // at least 1 front speaker
+      return false;
+    }
+    if (!even(layout & (CHANNEL_FRONT_LEFT |
+                        CHANNEL_FRONT_RIGHT))) { // no asymetric front
+      return false;
+    }
+    if (!even(layout &
+              (CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT))) { // no asymetric side
+      return false;
+    }
+    if (!even(layout & (CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT))) {
+      return false;
+    }
+    if (!even(layout &
+              (CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER))) {
+      return false;
+    }
+    if (cubeb_channel_layout_nb_channels(layout) >= CHANNELS_MAX) {
+      return false;
+    }
+    return true;
+  }
+
+  int auto_matrix();
+  int init();
 
-  auto & downmix_matrix = DOWNMIX_MATRIX_3F2_LFE[out_layout - CUBEB_LAYOUT_MONO]; // The matrix is started from mono.
-  unsigned 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) {
-      T sample = 0;
-      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.
-        assert((in_layout == CUBEB_LAYOUT_3F2_LFE || k < 3) ? (i + k < in_len) : (k == 3) ? true : (i + k - 1 < in_len));
-        T data = (in_layout == CUBEB_LAYOUT_3F2_LFE) ? in[i + k] : (k == 3) ? 0 : in[i + ((k < 3) ? k : k - 1)];
-        sample += downmix_matrix[j][k] * data;
+  const cubeb_sample_format _format;
+  const cubeb_channel_layout _in_ch_layout;              ///< input channel layout
+  const cubeb_channel_layout _out_ch_layout;             ///< output channel layout
+  const uint32_t _in_ch_count;                           ///< input channel count
+  const uint32_t _out_ch_count;                          ///< output channel count
+  const float _surround_mix_level = C_30DB;              ///< surround mixing level
+  const float _center_mix_level = C_30DB;                ///< center mixing level
+  const float _lfe_mix_level = 1;                        ///< LFE mixing level
+  double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }};        ///< floating point rematrixing coefficients
+  float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }};     ///< single precision floating point rematrixing coefficients
+  int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }};     ///< 17.15 fixed point rematrixing coefficients
+  uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX+1] = {{ 0 }};  ///< Lists of input channels per output channel that have non zero rematrixing coefficients
+  bool _clipping = false;                          ///< Set to true if clipping detection is required
+  bool _valid = false;                             ///< Set to true if context is valid.
+};
+
+int MixerContext::auto_matrix()
+{
+  double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = { { 0 } };
+  double maxcoef = 0;
+  float maxval;
+
+  cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
+  cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
+
+  if (!sane_layout(in_ch_layout)) {
+    // Channel Not Supported
+    LOG("Input Layout %x is not supported", _in_ch_layout);
+    return -1;
+  }
+
+  if (!sane_layout(out_ch_layout)) {
+    LOG("Output Layout %x is not supported", _out_ch_layout);
+    return -1;
+  }
+
+  for (uint32_t i = 0; i < FF_ARRAY_ELEMS(matrix); i++) {
+    if (in_ch_layout & out_ch_layout & (1U << i)) {
+      matrix[i][i] = 1.0;
+    }
+  }
+
+  cubeb_channel_layout unaccounted = in_ch_layout & ~out_ch_layout;
+
+  // Rematrixing is done via a matrix of coefficient that should be applied to
+  // all channels. Channels are treated as pair and must be symmetrical (if a
+  // left channel exists, the corresponding right should exist too) unless the
+  // output layout has similar layout. Channels are then mixed toward the front
+  // center or back center if they exist with a slight bias toward the front.
+
+  if (unaccounted & CHANNEL_FRONT_CENTER) {
+    if ((out_ch_layout & CUBEB_LAYOUT_STEREO) == CUBEB_LAYOUT_STEREO) {
+      if (in_ch_layout & CUBEB_LAYOUT_STEREO) {
+        matrix[FRONT_LEFT][FRONT_CENTER] += _center_mix_level;
+        matrix[FRONT_RIGHT][FRONT_CENTER] += _center_mix_level;
+      } else {
+        matrix[FRONT_LEFT][FRONT_CENTER] += M_SQRT1_2;
+        matrix[FRONT_RIGHT][FRONT_CENTER] += M_SQRT1_2;
       }
-      assert(out_index + j < out_len);
-      out[out_index + j] = sample;
     }
-#if defined(USE_AUDIOUNIT)
-    out_index += in_channels;
-#else
-    out_index += out_channels;
-#endif
+  }
+  if (unaccounted & CUBEB_LAYOUT_STEREO) {
+    if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+      matrix[FRONT_CENTER][FRONT_LEFT] += M_SQRT1_2;
+      matrix[FRONT_CENTER][FRONT_RIGHT] += M_SQRT1_2;
+      if (in_ch_layout & CHANNEL_FRONT_CENTER)
+        matrix[FRONT_CENTER][FRONT_CENTER] = _center_mix_level * M_SQRT2;
+    }
   }
 
-  return true;
-}
-
-/* Map the audio data by channel name. */
-template<class T>
-bool
-mix_remap(long inframes,
-          T const * const in, unsigned long in_len,
-          T * out, unsigned long out_len,
-          cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
-{
-  assert(in_layout != out_layout && inframes >= 0);
-
-  // We might overwrite the data before we copied them to the mapped index
-  // (e.g. upmixing from stereo to 2F1 or mapping [L, R] to [R, L])
-  if (in == out) {
-    return false;
+  if (unaccounted & CHANNEL_BACK_CENTER) {
+    if (out_ch_layout & CHANNEL_BACK_LEFT) {
+      matrix[BACK_LEFT][BACK_CENTER] += M_SQRT1_2;
+      matrix[BACK_RIGHT][BACK_CENTER] += M_SQRT1_2;
+    } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+      matrix[SIDE_LEFT][BACK_CENTER] += M_SQRT1_2;
+      matrix[SIDE_RIGHT][BACK_CENTER] += M_SQRT1_2;
+    } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+      if (unaccounted & (CHANNEL_BACK_LEFT | CHANNEL_SIDE_LEFT)) {
+        matrix[FRONT_LEFT][BACK_CENTER] -= _surround_mix_level * M_SQRT1_2;
+        matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
+      } else {
+        matrix[FRONT_LEFT][BACK_CENTER] -= _surround_mix_level;
+        matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level;
+      }
+    } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+      matrix[FRONT_CENTER][BACK_CENTER] +=
+        _surround_mix_level * M_SQRT1_2;
+    }
+  }
+  if (unaccounted & CHANNEL_BACK_LEFT) {
+    if (out_ch_layout & CHANNEL_BACK_CENTER) {
+      matrix[BACK_CENTER][BACK_LEFT] += M_SQRT1_2;
+      matrix[BACK_CENTER][BACK_RIGHT] += M_SQRT1_2;
+    } else if (out_ch_layout & CHANNEL_SIDE_LEFT) {
+      if (in_ch_layout & CHANNEL_SIDE_LEFT) {
+        matrix[SIDE_LEFT][BACK_LEFT] += M_SQRT1_2;
+        matrix[SIDE_RIGHT][BACK_RIGHT] += M_SQRT1_2;
+      } else {
+        matrix[SIDE_LEFT][BACK_LEFT] += 1.0;
+        matrix[SIDE_RIGHT][BACK_RIGHT] += 1.0;
+      }
+    } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+      matrix[FRONT_LEFT][BACK_LEFT] -= _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_LEFT][BACK_RIGHT] -= _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_RIGHT][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_RIGHT][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+    } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+      matrix[FRONT_CENTER][BACK_LEFT] += _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_CENTER][BACK_RIGHT] += _surround_mix_level * M_SQRT1_2;
+    }
   }
 
-  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];
+  if (unaccounted & CHANNEL_SIDE_LEFT) {
+    if (out_ch_layout & CHANNEL_BACK_LEFT) {
+      /* if back channels do not exist in the input, just copy side
+         channels to back channels, otherwise mix side into back */
+      if (in_ch_layout & CHANNEL_BACK_LEFT) {
+        matrix[BACK_LEFT][SIDE_LEFT] += M_SQRT1_2;
+        matrix[BACK_RIGHT][SIDE_RIGHT] += M_SQRT1_2;
+      } else {
+        matrix[BACK_LEFT][SIDE_LEFT] += 1.0;
+        matrix[BACK_RIGHT][SIDE_RIGHT] += 1.0;
+      }
+    } else if (out_ch_layout & CHANNEL_BACK_CENTER) {
+      matrix[BACK_CENTER][SIDE_LEFT] += M_SQRT1_2;
+      matrix[BACK_CENTER][SIDE_RIGHT] += M_SQRT1_2;
+    } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+      matrix[FRONT_LEFT][SIDE_LEFT] -= _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_LEFT][SIDE_RIGHT] -= _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_RIGHT][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_RIGHT][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+    } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+      matrix[FRONT_CENTER][SIDE_LEFT] += _surround_mix_level * M_SQRT1_2;
+      matrix[FRONT_CENTER][SIDE_RIGHT] += _surround_mix_level * M_SQRT1_2;
+    }
   }
 
-  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 (unaccounted & CHANNEL_FRONT_LEFT_OF_CENTER) {
+    if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+      matrix[FRONT_LEFT][FRONT_LEFT_OF_CENTER] += 1.0;
+      matrix[FRONT_RIGHT][FRONT_RIGHT_OF_CENTER] += 1.0;
+    } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+      matrix[FRONT_CENTER][FRONT_LEFT_OF_CENTER] += M_SQRT1_2;
+      matrix[FRONT_CENTER][FRONT_RIGHT_OF_CENTER] += M_SQRT1_2;
+    }
+  }
+  /* mix LFE into front left/right or center */
+  if (unaccounted & CHANNEL_LOW_FREQUENCY) {
+    if (out_ch_layout & CHANNEL_FRONT_CENTER) {
+      matrix[FRONT_CENTER][LOW_FREQUENCY] += _lfe_mix_level;
+    } else if (out_ch_layout & CHANNEL_FRONT_LEFT) {
+      matrix[FRONT_LEFT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+      matrix[FRONT_RIGHT][LOW_FREQUENCY] += _lfe_mix_level * M_SQRT1_2;
+    }
   }
 
-  // If there is no matched channel, then do nothing.
-  if (!(out_layout_mask & in_layout_mask)) {
-    return false;
+  // Normalize the conversion matrix.
+  for (uint32_t out_i = 0, i = 0; i < CHANNELS_MAX; i++) {
+    double sum = 0;
+    int in_i = 0;
+    if ((out_ch_layout & (1U << i)) == 0) {
+      continue;
+    }
+    for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+      if ((in_ch_layout & (1U << j)) == 0) {
+        continue;
+      }
+      if (i < FF_ARRAY_ELEMS(matrix) && j < FF_ARRAY_ELEMS(matrix[0])) {
+        _matrix[out_i][in_i] = matrix[i][j];
+      } else {
+        _matrix[out_i][in_i] =
+          i == j && (in_ch_layout & out_ch_layout & (1U << i));
+      }
+      sum += fabs(_matrix[out_i][in_i]);
+      in_i++;
+    }
+    maxcoef = std::max(maxcoef, sum);
+    out_i++;
   }
 
-  for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_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];
-      assert(channel_index >= -1);
-      assert(out_index + j < out_len);
-      if (in_layout_mask & channel_mask) {
-        assert(channel_index != -1);
-        assert(i + channel_index < in_len);
-        out[out_index + j] = in[i + channel_index];
-      } else {
-        assert(channel_index == -1);
-        out[out_index + j] = 0;
+  if (_format == CUBEB_SAMPLE_S16NE) {
+    maxval = 1.0;
+  } else {
+    maxval = INT_MAX;
+  }
+
+  // Normalize matrix if needed.
+  if (maxcoef > maxval) {
+    maxcoef /= maxval;
+    for (uint32_t i = 0; i < CHANNELS_MAX; i++)
+      for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+        _matrix[i][j] /= maxcoef;
+      }
+  }
+
+  if (_format == CUBEB_SAMPLE_FLOAT32NE) {
+    for (uint32_t i = 0; i < FF_ARRAY_ELEMS(_matrix); i++) {
+      for (uint32_t j = 0; j < FF_ARRAY_ELEMS(_matrix[0]); j++) {
+        _matrix_flt[i][j] = _matrix[i][j];
       }
     }
   }
 
-  return true;
+  return 0;
 }
 
-/* Drop the extra channels beyond the provided output channels. */
-template<typename T>
-void
-downmix_fallback(long inframes,
-                 T const * const in, unsigned long in_len,
-                 T * out, unsigned long out_len,
-                 unsigned int in_channels, unsigned int out_channels)
+int MixerContext::init()
 {
-  assert(in_channels >= out_channels && inframes >= 0);
+  int r = auto_matrix();
+  if (r) {
+    return r;
+  }
+
+  // Determine if matrix operation would overflow
+  if (_format == CUBEB_SAMPLE_S16NE) {
+    int maxsum = 0;
+    for (uint32_t i = 0; i < _out_ch_count; i++) {
+      double rem = 0;
+      int sum = 0;
 
-  if (in_channels == out_channels && in == out) {
-    return;
+      for (uint32_t j = 0; j < _in_ch_count; j++) {
+        double target = _matrix[i][j] * 32768 + rem;
+        int value = lrintf(target);
+        rem += target - value;
+        sum += std::abs(value);
+      }
+      maxsum = std::max(maxsum, sum);
+    }
+    if (maxsum > 32768) {
+      _clipping = true;
+    }
   }
 
-  for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) {
-    for (unsigned int j = 0; j < out_channels; ++j) {
-      assert(i + j < in_len && out_index + j < out_len);
-      out[out_index + j] = in[i + j];
+  // FIXME quantize for integers
+  for (uint32_t i = 0; i < CHANNELS_MAX; i++) {
+    int ch_in = 0;
+    for (uint32_t j = 0; j < CHANNELS_MAX; j++) {
+      _matrix32[i][j] = lrintf(_matrix[i][j] * 32768);
+      if (_matrix[i][j]) {
+        _matrix_ch[i][++ch_in] = j;
+      }
     }
+    _matrix_ch[i][0] = ch_in;
+  }
+
+  return 0;
+}
+
+template<typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
+void
+sum2(TYPE_SAMPLE * out,
+     uint32_t stride_out,
+     const TYPE_SAMPLE * in1,
+     const TYPE_SAMPLE * in2,
+     uint32_t stride_in,
+     TYPE_COEFF coeff1,
+     TYPE_COEFF coeff2,
+     F&& operand,
+     uint32_t frames)
+{
+  static_assert(
+    std::is_same<TYPE_COEFF,
+                 typename std::result_of<F(TYPE_COEFF)>::type>::value,
+    "function must return the same type as used by matrix_coeff");
+  for (uint32_t i = 0; i < frames; i++) {
+    *out = operand(coeff1 * *in1 + coeff2 * *in2);
+    out += stride_out;
+    in1 += stride_in;
+    in2 += stride_in;
   }
 }
 
-
-template<typename T>
+template<typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
 void
-cubeb_downmix(long inframes,
-              T const * const in, unsigned long in_len,
-              T * out, unsigned long out_len,
-              cubeb_stream_params const * stream_params,
-              cubeb_stream_params const * mixer_params)
+copy(TYPE_SAMPLE * out,
+     uint32_t stride_out,
+     const TYPE_SAMPLE * in,
+     uint32_t stride_in,
+     TYPE_COEFF coeff,
+     F&& operand,
+     uint32_t frames)
 {
-  assert(in && out);
-  assert(inframes);
-  assert(stream_params->channels >= mixer_params->channels &&
-         mixer_params->channels > 0);
-  assert(stream_params->layout != CUBEB_LAYOUT_UNDEFINED);
-
-  unsigned int in_channels = stream_params->channels;
-  cubeb_channel_layout in_layout = stream_params->layout;
-
-  unsigned int out_channels = mixer_params->channels;
-  cubeb_channel_layout out_layout = mixer_params->layout;
-
-  // If the channel number is different from the layout's setting,
-  // 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(inframes, in, in_len, out, out_len, in_layout, out_layout)) {
-      return;
-    }
-
-#if defined(USE_AUDIOUNIT)
-  // We only support downmix for audio 5.1 on OS X currently.
-  return;
-#endif
-
-    if (mix_remap(inframes, in, in_len, out, out_len, in_layout, out_layout)) {
-      return;
-    }
-  }
-
-  downmix_fallback(inframes, in, in_len, out, out_len, in_channels, out_channels);
-}
-
-/* Upmix function, copies a mono channel into L and R. */
-template<typename T>
-void
-mono_to_stereo(long insamples, T const * in, unsigned long in_len,
-               T * out, unsigned long out_len, unsigned int out_channels)
-{
-  for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) {
-    assert((unsigned long)i < in_len && (unsigned long)j + 1 < out_len);
-    out[j] = out[j + 1] = in[i];
+  static_assert(
+    std::is_same<TYPE_COEFF,
+                 typename std::result_of<F(TYPE_COEFF)>::type>::value,
+    "function must return the same type as used by matrix_coeff");
+  for (uint32_t i = 0; i < frames; i++) {
+    *out = operand(coeff * *in);
+    out += stride_out;
+    in += stride_in;
   }
 }
 
-template<typename T>
-void
-cubeb_upmix(long inframes,
-            T const * const in, unsigned long in_len,
-            T * out, unsigned long out_len,
-            cubeb_stream_params const * stream_params,
-            cubeb_stream_params const * mixer_params)
+template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
+static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
+                    const TYPE_COEFF (&matrix_coeff)[COLS][COLS],
+                    F&& aF, uint32_t frames)
 {
-  assert(in && out);
-  assert(inframes);
-  assert(mixer_params->channels >= stream_params->channels &&
-         stream_params->channels > 0);
+  static_assert(
+    std::is_same<TYPE_COEFF,
+                 typename std::result_of<F(TYPE_COEFF)>::type>::value,
+    "function must return the same type as used by matrix_coeff");
+
+  for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
+    TYPE* out = aOut + out_i;
+    switch (s->_matrix_ch[out_i][0]) {
+      case 0:
+        for (uint32_t i = 0; i < frames; i++) {
+          out[i * s->_out_ch_count] = 0;
+        }
+        break;
+      case 1: {
+        int in_i = s->_matrix_ch[out_i][1];
+        copy(out,
+             s->_out_ch_count,
+             aIn + in_i,
+             s->_in_ch_count,
+             matrix_coeff[out_i][in_i],
+             aF,
+             frames);
+      } break;
+      case 2:
+        sum2(out,
+             s->_out_ch_count,
+             aIn + s->_matrix_ch[out_i][1],
+             aIn + s->_matrix_ch[out_i][2],
+             s->_in_ch_count,
+             matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
+             matrix_coeff[out_i][s->_matrix_ch[out_i][2]],
+             aF,
+             frames);
+        break;
+      default:
+        for (uint32_t i = 0; i < frames; i++) {
+          TYPE_COEFF v = 0;
+          for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
+            uint32_t in_i = s->_matrix_ch[out_i][1 + j];
+            v +=
+              *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
+          }
+          out[i * s->_out_ch_count] = aF(v);
+        }
+        break;
+    }
+  }
+  return 0;
+}
+
+struct cubeb_mixer
+{
+  cubeb_mixer(cubeb_sample_format format,
+              cubeb_channel_layout in_layout,
+              cubeb_channel_layout out_layout)
+    : _context(format, in_layout, out_layout)
+  {
+  }
 
-  unsigned int in_channels = stream_params->channels;
-  unsigned int out_channels = mixer_params->channels;
+  int mix(unsigned long frames,
+          void * input_buffer,
+          unsigned long input_buffer_size,
+          void * output_buffer,
+          unsigned long output_buffer_size) const
+  {
+    if (!valid()) {
+      return -1;
+    }
+    if (frames <= 0) {
+      return 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(inframes, in, in_len, out, out_len, 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) {
-        assert(i + j < in_len && o + j < out_len);
-        out[o + j] = in[i + j];
+    // Check if output buffer is of sufficient size.
+    size_t size_read_needed =
+      frames * _context._in_ch_count * cubeb_sample_size(_context._format);
+    if (input_buffer_size < size_read_needed) {
+      // We don't have enough data to read!
+      return -1;
+    }
+    if (output_buffer_size * _context._in_ch_count <
+        size_read_needed * _context._out_ch_count) {
+      return -1;
+    }
+    switch (_context._format)
+    {
+      case CUBEB_SAMPLE_FLOAT32NE: {
+        auto f = [](float x) { return x; };
+        return rematrix(&_context,
+                        static_cast<float*>(output_buffer),
+                        static_cast<const float*>(input_buffer),
+                        _context._matrix_flt,
+                        f,
+                        frames);
       }
+      case CUBEB_SAMPLE_S16NE:
+        if (_context._clipping) {
+          auto f = [](int x) {
+            int y = (x + 16384) >> 15;
+            // clip the signed integer value into the -32768,32767 range.
+            if ((y + 0x8000U) & ~0xFFFF) {
+              return (y >> 31) ^ 0x7FFF;
+            }
+            return y;
+          };
+          return rematrix(&_context,
+                          static_cast<int16_t*>(output_buffer),
+                          static_cast<const int16_t*>(input_buffer),
+                          _context._matrix32,
+                          f,
+                          frames);
+        } else {
+          auto f = [](int x) { return (x + 16384) >> 15; };
+          return rematrix(&_context,
+                          static_cast<int16_t*>(output_buffer),
+                          static_cast<const int16_t*>(input_buffer),
+                          _context._matrix32,
+                          f,
+                          frames);
+        }
+        break;
+      default:
+        assert(false);
+        break;
     }
   }
 
-  /* 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 = in_channels; j < out_channels; ++j) {
-      assert((unsigned long)o + j < out_len);
-      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 valid() const { return _context._valid; }
 
-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;
-  }
+  virtual ~cubeb_mixer(){};
 
-  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_QUAD_LFE ||
-           mixer->layout == CUBEB_LAYOUT_3F1_LFE));
-}
-
-bool
-cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
-{
-  return stream->layout != CUBEB_LAYOUT_UNDEFINED &&
-         (cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer));
-}
-
-struct cubeb_mixer {
-  virtual void mix(long frames,
-                   void * input_buffer, unsigned long input_buffer_length,
-                   void * output_buffer, unsigned long output_buffer_length,
-                   cubeb_stream_params const * stream_params,
-                   cubeb_stream_params const * mixer_params) = 0;
-  virtual ~cubeb_mixer() {};
+  MixerContext _context;
 };
 
-template<typename T>
-struct cubeb_mixer_impl : public cubeb_mixer {
-  explicit cubeb_mixer_impl(unsigned int d)
-    : direction(d)
-  {
+cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format,
+                                cubeb_channel_layout in_layout,
+                                cubeb_channel_layout out_layout)
+{
+  if (!MixerContext::sane_layout(in_layout) ||
+      !MixerContext::sane_layout(out_layout)) {
+    return nullptr;
   }
-
-  void mix(long frames,
-           void * input_buffer, unsigned long input_buffer_length,
-           void * output_buffer, unsigned long output_buffer_length,
-           cubeb_stream_params const * stream_params,
-           cubeb_stream_params const * mixer_params)
-  {
-    if (frames <= 0) {
-      return;
-    }
-
-    T * in = static_cast<T*>(input_buffer);
-    T * out = static_cast<T*>(output_buffer);
-
-    if ((direction & CUBEB_MIXER_DIRECTION_DOWNMIX) &&
-        cubeb_should_downmix(stream_params, mixer_params)) {
-      cubeb_downmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params);
-    } else if ((direction & CUBEB_MIXER_DIRECTION_UPMIX) &&
-               cubeb_should_upmix(stream_params, mixer_params)) {
-      cubeb_upmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params);
-    }
-  }
-
-  ~cubeb_mixer_impl() {};
-
-  unsigned char const direction;
-};
-
-cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
-                                 unsigned char direction)
-{
-  assert(direction & CUBEB_MIXER_DIRECTION_DOWNMIX ||
-         direction & CUBEB_MIXER_DIRECTION_UPMIX);
-  switch(format) {
-    case CUBEB_SAMPLE_S16NE:
-      return new cubeb_mixer_impl<short>(direction);
-    case CUBEB_SAMPLE_FLOAT32NE:
-      return new cubeb_mixer_impl<float>(direction);
-    default:
-      assert(false);
-      return nullptr;
-  }
+  return new cubeb_mixer(format, in_layout, out_layout);
 }
 
 void cubeb_mixer_destroy(cubeb_mixer * mixer)
 {
   delete mixer;
 }
 
-void cubeb_mixer_mix(cubeb_mixer * mixer, long frames,
-                     void * input_buffer, unsigned long input_buffer_length,
-                     void * output_buffer, unsigned long output_buffer_length,
-                     cubeb_stream_params const * stream_params,
-                     cubeb_stream_params const * mixer_params)
+int cubeb_mixer_mix(cubeb_mixer * mixer,
+                    unsigned long frames,
+                    void* input_buffer,
+                    unsigned long input_buffer_size,
+                    void* output_buffer,
+                    unsigned long output_buffer_size)
 {
-  assert(mixer);
-  mixer->mix(frames, input_buffer, input_buffer_length, output_buffer, output_buffer_length,
-             stream_params, mixer_params);
+  return mixer->mix(
+    frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size);
 }
--- a/media/libcubeb/src/cubeb_mixer.h
+++ b/media/libcubeb/src/cubeb_mixer.h
@@ -3,85 +3,33 @@
  *
  * 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>
+#include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
 
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
-typedef enum {
-  CHANNEL_INVALID = -1,
-  CHANNEL_LEFT = 0,
-  CHANNEL_RIGHT,
-  CHANNEL_CENTER,
-  CHANNEL_LS,
-  CHANNEL_RS,
-  CHANNEL_RLS,
-  CHANNEL_RCENTER,
-  CHANNEL_RRS,
-  CHANNEL_LFE,
-  CHANNEL_UNMAPPED,
-  CHANNEL_MAX = 256 // Max number of supported channels.
-} cubeb_channel;
-
-static cubeb_channel const CHANNEL_INDEX_TO_ORDER[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
-  { CHANNEL_INVALID },                                                                                            // UNDEFINED
-  { CHANNEL_CENTER },                                                                                             // MONO
-  { CHANNEL_CENTER, 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
-  // When more channels are present, the stream is considered unmapped to a
-  // particular speaker set.
-};
-
-typedef struct {
-  unsigned int channels;
-  cubeb_channel map[CHANNEL_MAX];
-} cubeb_channel_map;
-
-cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map);
-
-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);
-
-bool cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
-
-typedef enum {
-  CUBEB_MIXER_DIRECTION_DOWNMIX = 0x01,
-  CUBEB_MIXER_DIRECTION_UPMIX   = 0x02,
-} cubeb_mixer_direction;
-
 typedef struct cubeb_mixer cubeb_mixer;
 cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
-                                 unsigned char direction);
+                                 cubeb_channel_layout in_layout,
+                                 cubeb_channel_layout out_layout);
 void cubeb_mixer_destroy(cubeb_mixer * mixer);
-void cubeb_mixer_mix(cubeb_mixer * mixer, long frames,
-                     void * input_buffer, unsigned long input_buffer_length,
-                     void * output_buffer, unsigned long output_buffer_length,
-                     cubeb_stream_params const * stream_params,
-                     cubeb_stream_params const * mixer_params);
+int cubeb_mixer_mix(cubeb_mixer * mixer,
+                    unsigned long frames,
+                    void * input_buffer,
+                    unsigned long input_buffer_size,
+                    void * output_buffer,
+                    unsigned long output_buffer_size);
+
+unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
 
 #if defined(__cplusplus)
 }
 #endif
 
 #endif // CUBEB_MIXER