AudioMixer: Preserve stereo volume in multi-channel mixing

CrOS exposes 4-channel output for ARC++ and mixes samples in CRAS
server to real channel format, but AudioMixer will discard right
channel volume when mixing a 2-channel track to 4-channel output.
This behavior will cause ARC++ fail on Audio Frequency Line Test
in CTS-V since the right channel can't be tested.

The code is also updated to work for 3, 5, 6, 7, 8 canonical output
channel position masks, as well as continue the mono volume
handling for output channel index masks.

Bug: 110551766
Test: Run CTS-V Audio Frequency Line Test
Test: atest mixerops_benchmark
Change-Id: I4c6ee86d30bb8296f0e32f9a17b1135e1313fd64
diff --git a/media/libaudioprocessing/AudioMixerOps.h b/media/libaudioprocessing/AudioMixerOps.h
index f33e361..d3a26ad 100644
--- a/media/libaudioprocessing/AudioMixerOps.h
+++ b/media/libaudioprocessing/AudioMixerOps.h
@@ -19,21 +19,10 @@
 
 namespace android {
 
-/* Behavior of is_same<>::value is true if the types are identical,
- * false otherwise. Identical to the STL std::is_same.
- */
-template<typename T, typename U>
-struct is_same
-{
-    static const bool value = false;
-};
-
-template<typename T>
-struct is_same<T, T>  // partial specialization
-{
-    static const bool value = true;
-};
-
+// Hack to make static_assert work in a constexpr
+// https://en.cppreference.com/w/cpp/language/if
+template <int N>
+inline constexpr bool dependent_false = false;
 
 /* MixMul is a multiplication operator to scale an audio input signal
  * by a volume gain, with the formula:
@@ -179,7 +168,7 @@
 
 template <typename TO, typename TI>
 inline void MixAccum(TO *auxaccum, TI value) {
-    if (!is_same<TO, TI>::value) {
+    if (!std::is_same_v<TO, TI>) {
         LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n",
                 sizeof(TO), sizeof(TI));
     }
@@ -228,9 +217,69 @@
     MIXTYPE_MULTI_SAVEONLY,
     MIXTYPE_MULTI_MONOVOL,
     MIXTYPE_MULTI_SAVEONLY_MONOVOL,
+    MIXTYPE_MULTI_STEREOVOL,
+    MIXTYPE_MULTI_SAVEONLY_STEREOVOL,
 };
 
 /*
+ * TODO: We should work on non-interleaved streams - the
+ * complexity of working on interleaved streams is now getting
+ * too high, and likely limits compiler optimization.
+ */
+template <int MIXTYPE, int NCHAN,
+        typename TO, typename TI, typename TV,
+        typename F>
+void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) {
+    static_assert(NCHAN > 0 && NCHAN <= 8);
+    static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+            || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL);
+    auto proc = [](auto& a, const auto& b) {
+        if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL) {
+            a += b;
+        } else {
+            a = b;
+        }
+    };
+    // HALs should only expose the canonical channel masks.
+    proc(*out++, f(*in++, vol[0])); // front left
+    if constexpr (NCHAN == 1) return;
+    proc(*out++, f(*in++, vol[1])); // front right
+    if constexpr (NCHAN == 2)  return;
+    if constexpr (NCHAN == 4) {
+        proc(*out++, f(*in++, vol[0])); // back left
+        proc(*out++, f(*in++, vol[1])); // back right
+        return;
+    }
+
+    // TODO: Precompute center volume if not ramping.
+    std::decay_t<TV> center;
+    if constexpr (std::is_floating_point_v<TV>) {
+        center = (vol[0] + vol[1]) * 0.5;       // do not use divide
+    } else {
+        center = (vol[0] >> 1) + (vol[1] >> 1); // rounds to 0.
+    }
+    proc(*out++, f(*in++, center)); // center (or 2.1 LFE)
+    if constexpr (NCHAN == 3) return;
+    if constexpr (NCHAN == 5) {
+        proc(*out++, f(*in++, vol[0]));  // back left
+        proc(*out++, f(*in++, vol[1]));  // back right
+        return;
+    }
+
+    proc(*out++, f(*in++, center)); // lfe
+    proc(*out++, f(*in++, vol[0])); // back left
+    proc(*out++, f(*in++, vol[1])); // back right
+    if constexpr (NCHAN == 6) return;
+    if constexpr (NCHAN == 7) {
+        proc(*out++, f(*in++, center)); // back center
+        return;
+    }
+    // NCHAN == 8
+    proc(*out++, f(*in++, vol[0])); // side left
+    proc(*out++, f(*in++, vol[1])); // side right
+}
+
+/*
  * The volumeRampMulti and volumeRamp functions take a MIXTYPE
  * which indicates the per-frame mixing and accumulation strategy.
  *
@@ -271,6 +320,12 @@
  * MIXTYPE_MULTI_SAVEONLY_MONOVOL:
  *   Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0].
  *
+ * MIXTYPE_MULTI_STEREOVOL:
+ *   Same as MIXTYPE_MULTI, but uses only volume[0] and volume[1].
+ *
+ * MIXTYPE_MULTI_SAVEONLY_STEREOVOL:
+ *   Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0] and volume[1].
+ *
  */
 
 template <int MIXTYPE, int NCHAN,
@@ -284,41 +339,42 @@
     if (aux != NULL) {
         do {
             TA auxaccum = 0;
-            switch (MIXTYPE) {
-            case MIXTYPE_MULTI:
+            if constexpr (MIXTYPE == MIXTYPE_MULTI) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
                     vol[i] += volinc[i];
                 }
-                break;
-            case MIXTYPE_MONOEXPAND:
+            } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
                     vol[i] += volinc[i];
                 }
                 in++;
-                break;
-            case MIXTYPE_MULTI_SAVEONLY:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
                     vol[i] += volinc[i];
                 }
-                break;
-            case MIXTYPE_MULTI_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
                 }
                 vol[0] += volinc[0];
-                break;
-            case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
                 }
                 vol[0] += volinc[0];
-                break;
-            default:
-                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
-                break;
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+                    || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL) {
+                stereoVolumeHelper<MIXTYPE, NCHAN>(
+                        out, in, vol, [&auxaccum] (auto &a, const auto &b) {
+                    return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
+                });
+                vol[0] += volinc[0];
+                vol[1] += volinc[1];
+            } else /* constexpr */ {
+                static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
             }
             auxaccum /= NCHAN;
             *aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
@@ -326,41 +382,41 @@
         } while (--frameCount);
     } else {
         do {
-            switch (MIXTYPE) {
-            case MIXTYPE_MULTI:
+            if constexpr (MIXTYPE == MIXTYPE_MULTI) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
                     vol[i] += volinc[i];
                 }
-                break;
-            case MIXTYPE_MONOEXPAND:
+            } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
                     vol[i] += volinc[i];
                 }
                 in++;
-                break;
-            case MIXTYPE_MULTI_SAVEONLY:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
                     vol[i] += volinc[i];
                 }
-                break;
-            case MIXTYPE_MULTI_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
                 }
                 vol[0] += volinc[0];
-                break;
-            case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
                 }
                 vol[0] += volinc[0];
-                break;
-            default:
-                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
-                break;
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+                    || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL) {
+                stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
+                    return MixMul<TO, TI, TV>(a, b);
+                });
+                vol[0] += volinc[0];
+                vol[1] += volinc[1];
+            } else /* constexpr */ {
+                static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
             }
         } while (--frameCount);
     }
@@ -377,72 +433,69 @@
     if (aux != NULL) {
         do {
             TA auxaccum = 0;
-            switch (MIXTYPE) {
-            case MIXTYPE_MULTI:
+            if constexpr (MIXTYPE == MIXTYPE_MULTI) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
                 }
-                break;
-            case MIXTYPE_MONOEXPAND:
+            } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMulAux<TO, TI, TV, TA>(*in, vol[i], &auxaccum);
                 }
                 in++;
-                break;
-            case MIXTYPE_MULTI_SAVEONLY:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
                 }
-                break;
-            case MIXTYPE_MULTI_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
                 }
-                break;
-            case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
                 }
-                break;
-            default:
-                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
-                break;
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+                    || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL) {
+                stereoVolumeHelper<MIXTYPE, NCHAN>(
+                        out, in, vol, [&auxaccum] (auto &a, const auto &b) {
+                    return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
+                });
+            } else /* constexpr */ {
+                static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
             }
             auxaccum /= NCHAN;
             *aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
         } while (--frameCount);
     } else {
         do {
-            switch (MIXTYPE) {
-            case MIXTYPE_MULTI:
+            if constexpr (MIXTYPE == MIXTYPE_MULTI) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
                 }
-                break;
-            case MIXTYPE_MONOEXPAND:
+            } else if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMul<TO, TI, TV>(*in, vol[i]);
                 }
                 in++;
-                break;
-            case MIXTYPE_MULTI_SAVEONLY:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
                 }
-                break;
-            case MIXTYPE_MULTI_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
                 }
-                break;
-            case MIXTYPE_MULTI_SAVEONLY_MONOVOL:
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
                 for (int i = 0; i < NCHAN; ++i) {
                     *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
                 }
-                break;
-            default:
-                LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE);
-                break;
+            } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
+                    || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL) {
+                stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
+                    return MixMul<TO, TI, TV>(a, b);
+                });
+            } else /* constexpr */ {
+                static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
             }
         } while (--frameCount);
     }