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