• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef ANDROID_AUDIO_MIXER_OPS_H
18 #define ANDROID_AUDIO_MIXER_OPS_H
19 
20 #include <system/audio.h>
21 
22 namespace android {
23 
24 // Hack to make static_assert work in a constexpr
25 // https://en.cppreference.com/w/cpp/language/if
26 template <int N>
27 inline constexpr bool dependent_false = false;
28 
29 /* MixMul is a multiplication operator to scale an audio input signal
30  * by a volume gain, with the formula:
31  *
32  * O(utput) = I(nput) * V(olume)
33  *
34  * The output, input, and volume may have different types.
35  * There are 27 variants, of which 14 are actually defined in an
36  * explicitly templated class.
37  *
38  * The following type variables and the underlying meaning:
39  *
40  * Output type       TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
41  * Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
42  * Volume type       TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
43  *
44  * For high precision audio, only the <TO, TI, TV> = <float, float, float>
45  * needs to be accelerated. This is perhaps the easiest form to do quickly as well.
46  *
47  * A generic version is NOT defined to catch any mistake of using it.
48  */
49 
50 template <typename TO, typename TI, typename TV>
51 TO MixMul(TI value, TV volume);
52 
53 template <>
54 inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
55     return value * volume;
56 }
57 
58 template <>
59 inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
60     return (value >> 12) * volume;
61 }
62 
63 template <>
64 inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
65     return value * (volume >> 16);
66 }
67 
68 template <>
69 inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
70     return (value >> 12) * (volume >> 16);
71 }
72 
73 template <>
74 inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
75     static const float norm = 1. / (1 << 12);
76     return value * volume * norm;
77 }
78 
79 template <>
80 inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
81     static const float norm = 1. / (1 << 28);
82     return value * volume * norm;
83 }
84 
85 template <>
86 inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
87     return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
88 }
89 
90 template <>
91 inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
92     return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
93 }
94 
95 template <>
96 inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
97     static const float norm = 1. / (1 << (15 + 12));
98     return static_cast<float>(value) * static_cast<float>(volume) * norm;
99 }
100 
101 template <>
102 inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
103     static const float norm = 1. / (1ULL << (15 + 28));
104     return static_cast<float>(value) * static_cast<float>(volume) * norm;
105 }
106 
107 template <>
108 inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
109     return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
110 }
111 
112 template <>
113 inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
114     return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
115 }
116 
117 template <>
118 inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
119     return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
120 }
121 
122 template <>
123 inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
124     return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
125 }
126 
127 /* Required for floating point volume.  Some are needed for compilation but
128  * are not needed in execution and should be removed from the final build by
129  * an optimizing compiler.
130  */
131 template <>
132 inline float MixMul<float, float, float>(float value, float volume) {
133     return value * volume;
134 }
135 
136 template <>
137 inline float MixMul<float, int16_t, float>(int16_t value, float volume) {
138     static const float float_from_q_15 = 1. / (1 << 15);
139     return value * volume * float_from_q_15;
140 }
141 
142 template <>
143 inline int32_t MixMul<int32_t, int32_t, float>(int32_t value, float volume) {
144     LOG_ALWAYS_FATAL("MixMul<int32_t, int32_t, float> Runtime Should not be here");
145     return value * volume;
146 }
147 
148 template <>
149 inline int32_t MixMul<int32_t, int16_t, float>(int16_t value, float volume) {
150     LOG_ALWAYS_FATAL("MixMul<int32_t, int16_t, float> Runtime Should not be here");
151     static const float u4_12_from_float = (1 << 12);
152     return value * volume * u4_12_from_float;
153 }
154 
155 template <>
156 inline int16_t MixMul<int16_t, int16_t, float>(int16_t value, float volume) {
157     LOG_ALWAYS_FATAL("MixMul<int16_t, int16_t, float> Runtime Should not be here");
158     return clamp16_from_float(MixMul<float, int16_t, float>(value, volume));
159 }
160 
161 template <>
162 inline int16_t MixMul<int16_t, float, float>(float value, float volume) {
163     return clamp16_from_float(value * volume);
164 }
165 
166 /*
167  * MixAccum is used to add into an accumulator register of a possibly different
168  * type. The TO and TI types are the same as MixMul.
169  */
170 
171 template <typename TO, typename TI>
MixAccum(TO * auxaccum,TI value)172 inline void MixAccum(TO *auxaccum, TI value) {
173     if (!std::is_same_v<TO, TI>) {
174         LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n",
175                 sizeof(TO), sizeof(TI));
176     }
177     *auxaccum += value;
178 }
179 
180 template<>
181 inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
182     static constexpr float norm = 1. / (1 << 15);
183     *auxaccum += norm * value;
184 }
185 
186 template<>
187 inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
188     static constexpr float norm = 1. / (1 << 27);
189     *auxaccum += norm * value;
190 }
191 
192 template<>
193 inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
194     *auxaccum += value << 12;
195 }
196 
197 template<>
198 inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
199     *auxaccum += clampq4_27_from_float(value);
200 }
201 
202 /* MixMulAux is just like MixMul except it combines with
203  * an accumulator operation MixAccum.
204  */
205 
206 template <typename TO, typename TI, typename TV, typename TA>
MixMulAux(TI value,TV volume,TA * auxaccum)207 inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
208     MixAccum<TA, TI>(auxaccum, value);
209     return MixMul<TO, TI, TV>(value, volume);
210 }
211 
212 /* MIXTYPE is used to determine how the samples in the input frame
213  * are mixed with volume gain into the output frame.
214  * See the volumeRampMulti functions below for more details.
215  */
216 enum {
217     MIXTYPE_MULTI,
218     MIXTYPE_MONOEXPAND,
219     MIXTYPE_MULTI_SAVEONLY,
220     MIXTYPE_MULTI_MONOVOL,
221     MIXTYPE_MULTI_SAVEONLY_MONOVOL,
222     MIXTYPE_MULTI_STEREOVOL,
223     MIXTYPE_MULTI_SAVEONLY_STEREOVOL,
224     MIXTYPE_STEREOEXPAND,
225 };
226 
227 /*
228  * TODO: We should work on non-interleaved streams - the
229  * complexity of working on interleaved streams is now getting
230  * too high, and likely limits compiler optimization.
231  */
232 template <int MIXTYPE, int NCHAN,
233         typename TO, typename TI, typename TV,
234         typename F>
stereoVolumeHelper(TO * & out,const TI * & in,const TV * vol,F f)235 void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) {
236     static_assert(NCHAN > 0 && NCHAN <= FCC_LIMIT);
237     static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL
238             || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
239             || MIXTYPE == MIXTYPE_STEREOEXPAND
240             || MIXTYPE == MIXTYPE_MONOEXPAND);
241     auto proc = [](auto& a, const auto& b) {
242         if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
243                 || MIXTYPE == MIXTYPE_STEREOEXPAND
244                 || MIXTYPE == MIXTYPE_MONOEXPAND) {
245             a += b;
246         } else {
247             a = b;
248         }
249     };
250     auto inp = [&in]() -> const TI& {
251         if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND
252                 || MIXTYPE == MIXTYPE_MONOEXPAND) {
253             return *in;
254         } else {
255             return *in++;
256         }
257     };
258 
259     // HALs should only expose the canonical channel masks.
260     proc(*out++, f(inp(), vol[0])); // front left
261     if constexpr (NCHAN == 1) return;
262     proc(*out++, f(inp(), vol[1])); // front right
263     if constexpr (NCHAN == 2)  return;
264     if constexpr (NCHAN == 4) {
265         proc(*out++, f(inp(), vol[0])); // back left
266         proc(*out++, f(inp(), vol[1])); // back right
267         return;
268     }
269 
270     // TODO: Precompute center volume if not ramping.
271     std::decay_t<TV> center;
272     if constexpr (std::is_floating_point_v<TV>) {
273         center = (vol[0] + vol[1]) * 0.5;       // do not use divide
274     } else {
275         center = (vol[0] >> 1) + (vol[1] >> 1); // rounds to 0.
276     }
277     proc(*out++, f(inp(), center)); // center (or 2.1 LFE)
278     if constexpr (NCHAN == 3) return;
279     if constexpr (NCHAN == 5) {
280         proc(*out++, f(inp(), vol[0]));  // back left
281         proc(*out++, f(inp(), vol[1]));  // back right
282         return;
283     }
284 
285     proc(*out++, f(inp(), center)); // lfe
286     proc(*out++, f(inp(), vol[0])); // back left
287     proc(*out++, f(inp(), vol[1])); // back right
288     if constexpr (NCHAN == 6) return;
289     if constexpr (NCHAN == 7) {
290         proc(*out++, f(inp(), center)); // back center
291         return;
292     }
293     // NCHAN == 8
294     proc(*out++, f(inp(), vol[0])); // side left
295     proc(*out++, f(inp(), vol[1])); // side right
296     if constexpr (NCHAN > FCC_8) {
297         // Mutes to zero extended surround channels.
298         // 7.1.4 has the correct behavior.
299         // 22.2 has the behavior that FLC and FRC will be mixed instead
300         // of SL and SR and LFE will be center, not left.
301         for (int i = 8; i < NCHAN; ++i) {
302             // TODO: Consider using android::audio_utils::channels::kSideFromChannelIdx
303             proc(*out++, f(inp(), 0.f));
304         }
305     }
306 }
307 
308 /*
309  * The volumeRampMulti and volumeRamp functions take a MIXTYPE
310  * which indicates the per-frame mixing and accumulation strategy.
311  *
312  * MIXTYPE_MULTI:
313  *   NCHAN represents number of input and output channels.
314  *   TO: int32_t (Q4.27) or float
315  *   TI: int32_t (Q4.27) or int16_t (Q0.15) or float
316  *   TA: int32_t (Q4.27) or float
317  *   TV: int32_t (U4.28) or int16_t (U4.12) or float
318  *   vol: represents a volume array.
319  *
320  *   This accumulates into the out pointer.
321  *
322  * MIXTYPE_MONOEXPAND:
323  *   Single input channel. NCHAN represents number of output channels.
324  *   TO: int32_t (Q4.27) or float
325  *   TI: int32_t (Q4.27) or int16_t (Q0.15) or float
326  *   TA: int32_t (Q4.27) or float
327  *   TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float
328  *   Input channel count is 1.
329  *   vol: represents volume array.
330  *   This uses stereo balanced volume vol[0] and vol[1].
331  *   Before R, this was a full volume array but was called only for channels <= 2.
332  *
333  *   This accumulates into the out pointer.
334  *
335  * MIXTYPE_MULTI_SAVEONLY:
336  *   NCHAN represents number of input and output channels.
337  *   TO: int16_t (Q.15) or float
338  *   TI: int32_t (Q4.27) or int16_t (Q0.15) or float
339  *   TA: int32_t (Q4.27) or float
340  *   TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float
341  *   vol: represents a volume array.
342  *
343  *   MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
344  *
345  * MIXTYPE_MULTI_MONOVOL:
346  *   Same as MIXTYPE_MULTI, but uses only volume[0].
347  *
348  * MIXTYPE_MULTI_SAVEONLY_MONOVOL:
349  *   Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0].
350  *
351  * MIXTYPE_MULTI_STEREOVOL:
352  *   Same as MIXTYPE_MULTI, but uses only volume[0] and volume[1].
353  *
354  * MIXTYPE_MULTI_SAVEONLY_STEREOVOL:
355  *   Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0] and volume[1].
356  *
357  * MIXTYPE_STEREOEXPAND:
358  *   Stereo input channel. NCHAN represents number of output channels.
359  *   Expand size 2 array "in" and "vol" to multi-channel output. Note
360  *   that the 2 array is assumed to have replicated L+R.
361  *
362  */
363 
364 template <int MIXTYPE, int NCHAN,
365         typename TO, typename TI, typename TV, typename TA, typename TAV>
volumeRampMulti(TO * out,size_t frameCount,const TI * in,TA * aux,TV * vol,const TV * volinc,TAV * vola,TAV volainc)366 inline void volumeRampMulti(TO* out, size_t frameCount,
367         const TI* in, TA* aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc)
368 {
369 #ifdef ALOGVV
370     ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
371 #endif
372     if (aux != NULL) {
373         do {
374             TA auxaccum = 0;
375             if constexpr (MIXTYPE == MIXTYPE_MULTI) {
376                 static_assert(NCHAN <= 2);
377                 for (int i = 0; i < NCHAN; ++i) {
378                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
379                     vol[i] += volinc[i];
380                 }
381             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
382                 static_assert(NCHAN <= 2);
383                 for (int i = 0; i < NCHAN; ++i) {
384                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
385                     vol[i] += volinc[i];
386                 }
387             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
388                 for (int i = 0; i < NCHAN; ++i) {
389                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
390                 }
391                 vol[0] += volinc[0];
392             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
393                 for (int i = 0; i < NCHAN; ++i) {
394                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
395                 }
396                 vol[0] += volinc[0];
397             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
398                     || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
399                     || MIXTYPE == MIXTYPE_MONOEXPAND
400                     || MIXTYPE == MIXTYPE_STEREOEXPAND) {
401                 stereoVolumeHelper<MIXTYPE, NCHAN>(
402                         out, in, vol, [&auxaccum] (auto &a, const auto &b) {
403                     return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
404                 });
405                 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
406                 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
407                 vol[0] += volinc[0];
408                 vol[1] += volinc[1];
409             } else /* constexpr */ {
410                 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
411             }
412             auxaccum /= NCHAN;
413             *aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
414             vola[0] += volainc;
415         } while (--frameCount);
416     } else {
417         do {
418             if constexpr (MIXTYPE == MIXTYPE_MULTI) {
419                 static_assert(NCHAN <= 2);
420                 for (int i = 0; i < NCHAN; ++i) {
421                     *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
422                     vol[i] += volinc[i];
423                 }
424             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
425                 static_assert(NCHAN <= 2);
426                 for (int i = 0; i < NCHAN; ++i) {
427                     *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
428                     vol[i] += volinc[i];
429                 }
430             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
431                 for (int i = 0; i < NCHAN; ++i) {
432                     *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
433                 }
434                 vol[0] += volinc[0];
435             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
436                 for (int i = 0; i < NCHAN; ++i) {
437                     *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
438                 }
439                 vol[0] += volinc[0];
440             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
441                     || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
442                     || MIXTYPE == MIXTYPE_MONOEXPAND
443                     || MIXTYPE == MIXTYPE_STEREOEXPAND) {
444                 stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
445                     return MixMul<TO, TI, TV>(a, b);
446                 });
447                 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
448                 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
449                 vol[0] += volinc[0];
450                 vol[1] += volinc[1];
451             } else /* constexpr */ {
452                 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
453             }
454         } while (--frameCount);
455     }
456 }
457 
458 template <int MIXTYPE, int NCHAN,
459         typename TO, typename TI, typename TV, typename TA, typename TAV>
volumeMulti(TO * out,size_t frameCount,const TI * in,TA * aux,const TV * vol,TAV vola)460 inline void volumeMulti(TO* out, size_t frameCount,
461         const TI* in, TA* aux, const TV *vol, TAV vola)
462 {
463 #ifdef ALOGVV
464     ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
465 #endif
466     if (aux != NULL) {
467         do {
468             TA auxaccum = 0;
469             if constexpr (MIXTYPE == MIXTYPE_MULTI) {
470                 static_assert(NCHAN <= 2);
471                 for (int i = 0; i < NCHAN; ++i) {
472                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
473                 }
474             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
475                 static_assert(NCHAN <= 2);
476                 for (int i = 0; i < NCHAN; ++i) {
477                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
478                 }
479             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
480                 for (int i = 0; i < NCHAN; ++i) {
481                     *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
482                 }
483             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
484                 for (int i = 0; i < NCHAN; ++i) {
485                     *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
486                 }
487             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
488                     || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
489                     || MIXTYPE == MIXTYPE_MONOEXPAND
490                     || MIXTYPE == MIXTYPE_STEREOEXPAND) {
491                 stereoVolumeHelper<MIXTYPE, NCHAN>(
492                         out, in, vol, [&auxaccum] (auto &a, const auto &b) {
493                     return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
494                 });
495                 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
496                 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
497             } else /* constexpr */ {
498                 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
499             }
500             auxaccum /= NCHAN;
501             *aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
502         } while (--frameCount);
503     } else {
504         do {
505             // ALOGD("Mixtype:%d NCHAN:%d", MIXTYPE, NCHAN);
506             if constexpr (MIXTYPE == MIXTYPE_MULTI) {
507                 static_assert(NCHAN <= 2);
508                 for (int i = 0; i < NCHAN; ++i) {
509                     *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
510                 }
511             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
512                 static_assert(NCHAN <= 2);
513                 for (int i = 0; i < NCHAN; ++i) {
514                     *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
515                 }
516             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
517                 for (int i = 0; i < NCHAN; ++i) {
518                     *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
519                 }
520             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
521                 for (int i = 0; i < NCHAN; ++i) {
522                     *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
523                 }
524             } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
525                     || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
526                     || MIXTYPE == MIXTYPE_MONOEXPAND
527                     || MIXTYPE == MIXTYPE_STEREOEXPAND) {
528                 stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
529                     return MixMul<TO, TI, TV>(a, b);
530                 });
531                 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
532                 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
533             } else /* constexpr */ {
534                 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
535             }
536         } while (--frameCount);
537     }
538 }
539 
540 };
541 
542 #endif /* ANDROID_AUDIO_MIXER_OPS_H */
543