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);
}