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/AudioMixerBase.cpp b/media/libaudioprocessing/AudioMixerBase.cpp
index 75c077d..a169db9 100644
--- a/media/libaudioprocessing/AudioMixerBase.cpp
+++ b/media/libaudioprocessing/AudioMixerBase.cpp
@@ -643,8 +643,16 @@
if (n & NEEDS_RESAMPLE) {
all16BitsStereoNoResample = false;
resampling = true;
- t->hook = TrackBase::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,
- t->mMixerInFormat, t->mMixerFormat);
+ if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2
+ && t->useStereoVolume()) {
+ t->hook = TrackBase::getTrackHook(
+ TRACKTYPE_RESAMPLESTEREO, t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
+ } else {
+ t->hook = TrackBase::getTrackHook(
+ TRACKTYPE_RESAMPLE, t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
+ }
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix + resample", name);
} else {
@@ -658,8 +666,11 @@
all16BitsStereoNoResample = false;
}
if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
- t->hook = TrackBase::getTrackHook(TRACKTYPE_NORESAMPLE, t->mMixerChannelCount,
- t->mMixerInFormat, t->mMixerFormat);
+ t->hook = TrackBase::getTrackHook(
+ t->useStereoVolume() ? TRACKTYPE_NORESAMPLESTEREO
+ : TRACKTYPE_NORESAMPLE,
+ t->mMixerChannelCount, t->mMixerInFormat,
+ t->mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix", name);
}
@@ -691,7 +702,8 @@
// special case handling due to implicit channel duplication.
// Stereo or Multichannel should actually be fine here.
mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
- t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
+ t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat,
+ t->useStereoVolume());
}
}
}
@@ -726,7 +738,8 @@
const std::shared_ptr<TrackBase> &t = mTracks[mEnabled[0]];
// Muted single tracks handled by allMuted above.
mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
- t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
+ t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat,
+ t->useStereoVolume());
}
}
}
@@ -1450,7 +1463,7 @@
}
const size_t outFrames = b.frameCount;
- t->volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, false /* ADJUSTVOL */> (
+ t->volumeMix<MIXTYPE, std::is_same_v<TI, float> /* USEFLOATVOL */, false /* ADJUSTVOL */> (
out, outFrames, in, aux, ramp);
out += outFrames * channels;
@@ -1463,7 +1476,7 @@
t->bufferProvider->releaseBuffer(&b);
}
if (ramp) {
- t->adjustVolumeRamp(aux != NULL, is_same<TI, float>::value);
+ t->adjustVolumeRamp(aux != NULL, std::is_same_v<TI, float>);
}
}
@@ -1489,7 +1502,7 @@
memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(TO));
mResampler->resample((int32_t*)temp, outFrameCount, bufferProvider);
- volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
+ volumeMix<MIXTYPE, std::is_same_v<TI, float> /* USEFLOATVOL */, true /* ADJUSTVOL */>(
out, outFrameCount, temp, aux, ramp);
} else { // constant volume gain
@@ -1513,7 +1526,7 @@
ALOGVV("track__NoResample\n");
const TI *in = static_cast<const TI *>(mIn);
- volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
+ volumeMix<MIXTYPE, std::is_same_v<TI, float> /* USEFLOATVOL */, true /* ADJUSTVOL */>(
out, frameCount, in, aux, needsRamp());
// MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels.
@@ -1601,6 +1614,21 @@
break;
}
break;
+ case TRACKTYPE_RESAMPLESTEREO:
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return (AudioMixerBase::hook_t) &TrackBase::track__Resample<
+ MIXTYPE_MULTI_STEREOVOL, float /*TO*/, float /*TI*/,
+ TYPE_AUX>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return (AudioMixerBase::hook_t) &TrackBase::track__Resample<
+ MIXTYPE_MULTI_STEREOVOL, int32_t /*TO*/, int16_t /*TI*/,
+ TYPE_AUX>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+ break;
case TRACKTYPE_NORESAMPLEMONO:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
@@ -1627,6 +1655,21 @@
break;
}
break;
+ case TRACKTYPE_NORESAMPLESTEREO:
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return (AudioMixerBase::hook_t) &TrackBase::track__NoResample<
+ MIXTYPE_MULTI_STEREOVOL, float /*TO*/, float /*TI*/,
+ TYPE_AUX>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return (AudioMixerBase::hook_t) &TrackBase::track__NoResample<
+ MIXTYPE_MULTI_STEREOVOL, int32_t /*TO*/, int16_t /*TI*/,
+ TYPE_AUX>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
+ break;
default:
LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
break;
@@ -1644,7 +1687,8 @@
/* static */
AudioMixerBase::process_hook_t AudioMixerBase::getProcessHook(
int processType, uint32_t channelCount,
- audio_format_t mixerInFormat, audio_format_t mixerOutFormat)
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat,
+ bool stereoVolume)
{
if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK
LOG_ALWAYS_FATAL("bad processType: %d", processType);
@@ -1654,36 +1698,79 @@
return &AudioMixerBase::process__oneTrack16BitsStereoNoResampling;
}
LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS);
- switch (mixerInFormat) {
- case AUDIO_FORMAT_PCM_FLOAT:
- switch (mixerOutFormat) {
+
+ if (stereoVolume) { // templated arguments require explicit values.
+ switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return &AudioMixerBase::process__noResampleOneTrack<
- MIXTYPE_MULTI_SAVEONLY, float /*TO*/, float /*TI*/, TYPE_AUX>;
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY_STEREOVOL, float /*TO*/,
+ float /*TI*/, TYPE_AUX>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY_STEREOVOL, int16_t /*TO*/,
+ float /*TI*/, TYPE_AUX>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
case AUDIO_FORMAT_PCM_16_BIT:
- return &AudioMixerBase::process__noResampleOneTrack<
- MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, float /*TI*/, TYPE_AUX>;
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY_STEREOVOL, float /*TO*/,
+ int16_t /*TI*/, TYPE_AUX>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY_STEREOVOL, int16_t /*TO*/,
+ int16_t /*TI*/, TYPE_AUX>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
default:
- LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
break;
}
- break;
- case AUDIO_FORMAT_PCM_16_BIT:
- switch (mixerOutFormat) {
- case AUDIO_FORMAT_PCM_FLOAT:
- return &AudioMixerBase::process__noResampleOneTrack<
- MIXTYPE_MULTI_SAVEONLY, float /*TO*/, int16_t /*TI*/, TYPE_AUX>;
- case AUDIO_FORMAT_PCM_16_BIT:
- return &AudioMixerBase::process__noResampleOneTrack<
- MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
- default:
- LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
- break;
- }
- break;
- default:
- LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
- break;
+ } else {
+ switch (mixerInFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY, float /*TO*/,
+ float /*TI*/, TYPE_AUX>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/,
+ float /*TI*/, TYPE_AUX>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ switch (mixerOutFormat) {
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY, float /*TO*/,
+ int16_t /*TI*/, TYPE_AUX>;
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return &AudioMixerBase::process__noResampleOneTrack<
+ MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/,
+ int16_t /*TI*/, TYPE_AUX>;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
+ break;
+ }
+ break;
+ default:
+ LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
+ break;
+ }
}
return NULL;
}
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);
}
diff --git a/media/libaudioprocessing/include/media/AudioMixerBase.h b/media/libaudioprocessing/include/media/AudioMixerBase.h
index 805b6d0..e0fb424 100644
--- a/media/libaudioprocessing/include/media/AudioMixerBase.h
+++ b/media/libaudioprocessing/include/media/AudioMixerBase.h
@@ -188,13 +188,20 @@
enum {
TRACKTYPE_NOP,
TRACKTYPE_RESAMPLE,
+ TRACKTYPE_RESAMPLESTEREO,
TRACKTYPE_NORESAMPLE,
TRACKTYPE_NORESAMPLEMONO,
+ TRACKTYPE_NORESAMPLESTEREO,
};
// process hook functionality
using process_hook_t = void(AudioMixerBase::*)();
+ static bool isAudioChannelPositionMask(audio_channel_mask_t channelMask) {
+ return audio_channel_mask_get_representation(channelMask)
+ == AUDIO_CHANNEL_REPRESENTATION_POSITION;
+ }
+
struct TrackBase;
using hook_t = void(TrackBase::*)(
int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
@@ -219,6 +226,9 @@
size_t getUnreleasedFrames() const { return mResampler.get() != nullptr ?
mResampler->getUnreleasedFrames() : 0; };
+ bool useStereoVolume() const { return channelMask == AUDIO_CHANNEL_OUT_STEREO
+ && isAudioChannelPositionMask(mMixerChannelMask); }
+
static hook_t getTrackHook(int trackType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
@@ -327,7 +337,8 @@
void process__noResampleOneTrack();
static process_hook_t getProcessHook(int processType, uint32_t channelCount,
- audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat,
+ bool useStereoVolume);
static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount);
diff --git a/media/libaudioprocessing/tests/Android.bp b/media/libaudioprocessing/tests/Android.bp
index 20c2c2c..18acef7 100644
--- a/media/libaudioprocessing/tests/Android.bp
+++ b/media/libaudioprocessing/tests/Android.bp
@@ -55,3 +55,26 @@
srcs: ["test-resampler.cpp"],
static_libs: ["libsndfile"],
}
+
+//
+// build mixerops objdump
+//
+// This is used to verify proper optimization of the code.
+//
+// For example, use:
+// ./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-objdump
+// -d --source ./out/target/product/crosshatch/symbols/system/bin/mixerops_objdump
+//
+cc_binary {
+ name: "mixerops_objdump",
+ srcs: ["mixerops_objdump.cpp"],
+}
+
+//
+// build mixerops benchmark
+//
+cc_benchmark {
+ name: "mixerops_benchmark",
+ srcs: ["mixerops_benchmark.cpp"],
+ static_libs: ["libgoogle-benchmark"],
+}
diff --git a/media/libaudioprocessing/tests/mixerops_benchmark.cpp b/media/libaudioprocessing/tests/mixerops_benchmark.cpp
new file mode 100644
index 0000000..86f5429
--- /dev/null
+++ b/media/libaudioprocessing/tests/mixerops_benchmark.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <type_traits>
+#include "../../../../system/media/audio_utils/include/audio_utils/primitives.h"
+#define LOG_ALWAYS_FATAL(...)
+
+#include <../AudioMixerOps.h>
+
+#include <benchmark/benchmark.h>
+
+using namespace android;
+
+template <int MIXTYPE, int NCHAN>
+static void BM_VolumeRampMulti(benchmark::State& state) {
+ constexpr size_t FRAME_COUNT = 1000;
+ constexpr size_t SAMPLE_COUNT = FRAME_COUNT * NCHAN;
+
+ // data inialized to 0.
+ float out[SAMPLE_COUNT]{};
+ float in[SAMPLE_COUNT]{};
+ float aux[FRAME_COUNT]{};
+
+ // volume initialized to 0
+ float vola = 0.f;
+ float vol[2] = {0.f, 0.f};
+
+ // some volume increment
+ float volainc = 0.01f;
+ float volinc[2] = {0.01f, 0.01f};
+
+ while (state.KeepRunning()) {
+ benchmark::DoNotOptimize(out);
+ benchmark::DoNotOptimize(in);
+ volumeRampMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vol, volinc, &vola, volainc);
+ benchmark::ClobberMemory();
+ }
+}
+
+template <int MIXTYPE, int NCHAN>
+static void BM_VolumeMulti(benchmark::State& state) {
+ constexpr size_t FRAME_COUNT = 1000;
+ constexpr size_t SAMPLE_COUNT = FRAME_COUNT * NCHAN;
+
+ // data inialized to 0.
+ float out[SAMPLE_COUNT]{};
+ float in[SAMPLE_COUNT]{};
+ float aux[FRAME_COUNT]{};
+
+ // volume initialized to 0
+ float vola = 0.f;
+ float vol[2] = {0.f, 0.f};
+
+
+ while (state.KeepRunning()) {
+ benchmark::DoNotOptimize(out);
+ benchmark::DoNotOptimize(in);
+ volumeMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vol, vola);
+ benchmark::ClobberMemory();
+ }
+}
+
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 2);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 2);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 2);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 2);
+
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 4);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 4);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 4);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 4);
+
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 5);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 5);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 5);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 5);
+
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI, 8);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY, 8);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_STEREOVOL, 8);
+BENCHMARK_TEMPLATE(BM_VolumeRampMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 8);
+
+BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI, 8);
+BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_SAVEONLY, 8);
+BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_STEREOVOL, 8);
+BENCHMARK_TEMPLATE(BM_VolumeMulti, MIXTYPE_MULTI_SAVEONLY_STEREOVOL, 8);
+
+BENCHMARK_MAIN();
diff --git a/media/libaudioprocessing/tests/mixerops_objdump.cpp b/media/libaudioprocessing/tests/mixerops_objdump.cpp
new file mode 100644
index 0000000..ee7e837
--- /dev/null
+++ b/media/libaudioprocessing/tests/mixerops_objdump.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <type_traits>
+#include "../../../../system/media/audio_utils/include/audio_utils/primitives.h"
+#define LOG_ALWAYS_FATAL(...)
+
+#include <../AudioMixerOps.h>
+
+using namespace android;
+
+template <int MIXTYPE, int NCHAN>
+static void checkVolumeRampMulti() {
+ constexpr size_t FRAME_COUNT = 1000;
+ constexpr size_t SAMPLE_COUNT = FRAME_COUNT * NCHAN;
+
+ // data inialized to 0.
+ float out[SAMPLE_COUNT]{};
+ float in[SAMPLE_COUNT]{};
+ float aux[FRAME_COUNT]{};
+
+ // volume initialized to 0
+ float vola = 0.f;
+ float vol[2] = {0.f, 0.f};
+
+ // some volume increment
+ float volainc = 0.01f;
+ float volinc[2] = {0.01f, 0.01f};
+
+ // try the multi ramp code.
+ volumeRampMulti<MIXTYPE, NCHAN>(out, FRAME_COUNT, in, aux, vol, volinc, &vola, volainc);
+}
+
+// Use this to check the objdump to ensure reasonable code.
+int main() {
+ checkVolumeRampMulti<MIXTYPE_MULTI_STEREOVOL, 5>();
+ return EXIT_SUCCESS;
+}