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