1 /*
2  * Copyright (C) 2012 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 #define LOG_TAG "EffectDownmix"
18 //#define LOG_NDEBUG 0
19 
20 #include <inttypes.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include <log/log.h>
26 
27 #include "EffectDownmix.h"
28 
29 // Do not submit with DOWNMIX_TEST_CHANNEL_INDEX defined, strictly for testing
30 //#define DOWNMIX_TEST_CHANNEL_INDEX 0
31 // Do not submit with DOWNMIX_ALWAYS_USE_GENERIC_DOWNMIXER defined, strictly for testing
32 //#define DOWNMIX_ALWAYS_USE_GENERIC_DOWNMIXER 0
33 
34 #define MINUS_3_DB_IN_Q19_12 2896 // -3dB = 0.707 * 2^12 = 2896
35 
36 // subset of possible audio_channel_mask_t values, and AUDIO_CHANNEL_OUT_* renamed to CHANNEL_MASK_*
37 typedef enum {
38     CHANNEL_MASK_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD_BACK,
39     CHANNEL_MASK_QUAD_SIDE = AUDIO_CHANNEL_OUT_QUAD_SIDE,
40     CHANNEL_MASK_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1_BACK,
41     CHANNEL_MASK_5POINT1_SIDE = AUDIO_CHANNEL_OUT_5POINT1_SIDE,
42     CHANNEL_MASK_7POINT1 = AUDIO_CHANNEL_OUT_7POINT1,
43 } downmix_input_channel_mask_t;
44 
45 // effect_handle_t interface implementation for downmix effect
46 const struct effect_interface_s gDownmixInterface = {
47         Downmix_Process,
48         Downmix_Command,
49         Downmix_GetDescriptor,
50         NULL /* no process_reverse function, no reference stream needed */
51 };
52 
53 // This is the only symbol that needs to be exported
54 __attribute__ ((visibility ("default")))
55 audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
56     .tag = AUDIO_EFFECT_LIBRARY_TAG,
57     .version = EFFECT_LIBRARY_API_VERSION,
58     .name = "Downmix Library",
59     .implementor = "The Android Open Source Project",
60     .create_effect = DownmixLib_Create,
61     .release_effect = DownmixLib_Release,
62     .get_descriptor = DownmixLib_GetDescriptor,
63 };
64 
65 
66 // AOSP insert downmix UUID: 93f04452-e4fe-41cc-91f9-e475b6d1d69f
67 static const effect_descriptor_t gDownmixDescriptor = {
68         EFFECT_UIID_DOWNMIX__, //type
69         {0x93f04452, 0xe4fe, 0x41cc, 0x91f9, {0xe4, 0x75, 0xb6, 0xd1, 0xd6, 0x9f}}, // uuid
70         EFFECT_CONTROL_API_VERSION,
71         EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST,
72         0, //FIXME what value should be reported? // cpu load
73         0, //FIXME what value should be reported? // memory usage
74         "Multichannel Downmix To Stereo", // human readable effect name
75         "The Android Open Source Project" // human readable effect implementor name
76 };
77 
78 // gDescriptors contains pointers to all defined effect descriptor in this library
79 static const effect_descriptor_t * const gDescriptors[] = {
80         &gDownmixDescriptor
81 };
82 
83 // number of effects in this library
84 const int kNbEffects = sizeof(gDescriptors) / sizeof(const effect_descriptor_t *);
85 
86 
87 /*----------------------------------------------------------------------------
88  * Test code
89  *--------------------------------------------------------------------------*/
90 #ifdef DOWNMIX_TEST_CHANNEL_INDEX
91 // strictly for testing, logs the indices of the channels for a given mask,
92 // uses the same code as Downmix_foldGeneric()
Downmix_testIndexComputation(uint32_t mask)93 void Downmix_testIndexComputation(uint32_t mask) {
94     ALOGI("Testing index computation for 0x%" PRIx32 ":", mask);
95     // check against unsupported channels
96     if (mask & kUnsupported) {
97         ALOGE("Unsupported channels (top or front left/right of center)");
98         return;
99     }
100     // verify has FL/FR
101     if ((mask & AUDIO_CHANNEL_OUT_STEREO) != AUDIO_CHANNEL_OUT_STEREO) {
102         ALOGE("Front channels must be present");
103         return;
104     }
105     // verify uses SIDE as a pair (ok if not using SIDE at all)
106     bool hasSides = false;
107     if ((mask & kSides) != 0) {
108         if ((mask & kSides) != kSides) {
109             ALOGE("Side channels must be used as a pair");
110             return;
111         }
112         hasSides = true;
113     }
114     // verify uses BACK as a pair (ok if not using BACK at all)
115     bool hasBacks = false;
116     if ((mask & kBacks) != 0) {
117         if ((mask & kBacks) != kBacks) {
118             ALOGE("Back channels must be used as a pair");
119             return;
120         }
121         hasBacks = true;
122     }
123 
124     const int numChan = audio_channel_count_from_out_mask(mask);
125     const bool hasFC = ((mask & AUDIO_CHANNEL_OUT_FRONT_CENTER) == AUDIO_CHANNEL_OUT_FRONT_CENTER);
126     const bool hasLFE =
127             ((mask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY);
128     const bool hasBC = ((mask & AUDIO_CHANNEL_OUT_BACK_CENTER) == AUDIO_CHANNEL_OUT_BACK_CENTER);
129     // compute at what index each channel is: samples will be in the following order:
130     //   FL FR FC LFE BL BR BC SL SR
131     // when a channel is not present, its index is set to the same as the index of the preceding
132     // channel
133     const int indexFC  = hasFC    ? 2            : 1;        // front center
134     const int indexLFE = hasLFE   ? indexFC + 1  : indexFC;  // low frequency
135     const int indexBL  = hasBacks ? indexLFE + 1 : indexLFE; // back left
136     const int indexBR  = hasBacks ? indexBL + 1  : indexBL;  // back right
137     const int indexBC  = hasBC    ? indexBR + 1  : indexBR;  // back center
138     const int indexSL  = hasSides ? indexBC + 1  : indexBC;  // side left
139     const int indexSR  = hasSides ? indexSL + 1  : indexSL;  // side right
140 
141     ALOGI("  FL FR FC LFE BL BR BC SL SR");
142     ALOGI("   %d  %d  %d   %d  %d  %d  %d  %d  %d",
143             0, 1, indexFC, indexLFE, indexBL, indexBR, indexBC, indexSL, indexSR);
144 }
145 #endif
146 
Downmix_validChannelMask(uint32_t mask)147 static bool Downmix_validChannelMask(uint32_t mask)
148 {
149     if (!mask) {
150         return false;
151     }
152     // check against unsupported channels
153     if (mask & kUnsupported) {
154         ALOGE("Unsupported channels (top or front left/right of center)");
155         return false;
156     }
157     // verify has FL/FR
158     if ((mask & AUDIO_CHANNEL_OUT_STEREO) != AUDIO_CHANNEL_OUT_STEREO) {
159         ALOGE("Front channels must be present");
160         return false;
161     }
162     // verify uses SIDE as a pair (ok if not using SIDE at all)
163     if ((mask & kSides) != 0) {
164         if ((mask & kSides) != kSides) {
165             ALOGE("Side channels must be used as a pair");
166             return false;
167         }
168     }
169     // verify uses BACK as a pair (ok if not using BACK at all)
170     if ((mask & kBacks) != 0) {
171         if ((mask & kBacks) != kBacks) {
172             ALOGE("Back channels must be used as a pair");
173             return false;
174         }
175     }
176     return true;
177 }
178 
179 /*----------------------------------------------------------------------------
180  * Effect API implementation
181  *--------------------------------------------------------------------------*/
182 
183 /*--- Effect Library Interface Implementation ---*/
184 
DownmixLib_Create(const effect_uuid_t * uuid,int32_t sessionId __unused,int32_t ioId __unused,effect_handle_t * pHandle)185 int32_t DownmixLib_Create(const effect_uuid_t *uuid,
186         int32_t sessionId __unused,
187         int32_t ioId __unused,
188         effect_handle_t *pHandle) {
189     int ret;
190     int i;
191     downmix_module_t *module;
192     const effect_descriptor_t *desc;
193 
194     ALOGV("DownmixLib_Create()");
195 
196 #ifdef DOWNMIX_TEST_CHANNEL_INDEX
197     // should work (won't log an error)
198     ALOGI("DOWNMIX_TEST_CHANNEL_INDEX: should work:");
199     Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_FRONT_RIGHT |
200                     AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_BACK_CENTER);
201     Downmix_testIndexComputation(CHANNEL_MASK_QUAD_SIDE | CHANNEL_MASK_QUAD_BACK);
202     Downmix_testIndexComputation(CHANNEL_MASK_5POINT1_SIDE | AUDIO_CHANNEL_OUT_BACK_CENTER);
203     Downmix_testIndexComputation(CHANNEL_MASK_5POINT1_BACK | AUDIO_CHANNEL_OUT_BACK_CENTER);
204     // shouldn't work (will log an error, won't display channel indices)
205     ALOGI("DOWNMIX_TEST_CHANNEL_INDEX: should NOT work:");
206     Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_FRONT_RIGHT |
207                         AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_BACK_LEFT);
208     Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_FRONT_RIGHT |
209                             AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_SIDE_LEFT);
210     Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT |
211                         AUDIO_CHANNEL_OUT_BACK_LEFT | AUDIO_CHANNEL_OUT_BACK_RIGHT);
212     Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT |
213                             AUDIO_CHANNEL_OUT_SIDE_LEFT | AUDIO_CHANNEL_OUT_SIDE_RIGHT);
214 #endif
215 
216     if (pHandle == NULL || uuid == NULL) {
217         return -EINVAL;
218     }
219 
220     for (i = 0 ; i < kNbEffects ; i++) {
221         desc = gDescriptors[i];
222         if (memcmp(uuid, &desc->uuid, sizeof(effect_uuid_t)) == 0) {
223             break;
224         }
225     }
226 
227     if (i == kNbEffects) {
228         return -ENOENT;
229     }
230 
231     module = malloc(sizeof(downmix_module_t));
232 
233     module->itfe = &gDownmixInterface;
234 
235     module->context.state = DOWNMIX_STATE_UNINITIALIZED;
236 
237     ret = Downmix_Init(module);
238     if (ret < 0) {
239         ALOGW("DownmixLib_Create() init failed");
240         free(module);
241         return ret;
242     }
243 
244     *pHandle = (effect_handle_t) module;
245 
246     ALOGV("DownmixLib_Create() %p , size %zu", module, sizeof(downmix_module_t));
247 
248     return 0;
249 }
250 
251 
DownmixLib_Release(effect_handle_t handle)252 int32_t DownmixLib_Release(effect_handle_t handle) {
253     downmix_module_t *pDwmModule = (downmix_module_t *)handle;
254 
255     ALOGV("DownmixLib_Release() %p", handle);
256     if (handle == NULL) {
257         return -EINVAL;
258     }
259 
260     pDwmModule->context.state = DOWNMIX_STATE_UNINITIALIZED;
261 
262     free(pDwmModule);
263     return 0;
264 }
265 
266 
DownmixLib_GetDescriptor(const effect_uuid_t * uuid,effect_descriptor_t * pDescriptor)267 int32_t DownmixLib_GetDescriptor(const effect_uuid_t *uuid, effect_descriptor_t *pDescriptor) {
268     ALOGV("DownmixLib_GetDescriptor()");
269     int i;
270 
271     if (pDescriptor == NULL || uuid == NULL){
272         ALOGE("DownmixLib_Create() called with NULL pointer");
273         return -EINVAL;
274     }
275     ALOGV("DownmixLib_GetDescriptor() nb effects=%d", kNbEffects);
276     for (i = 0; i < kNbEffects; i++) {
277         ALOGV("DownmixLib_GetDescriptor() i=%d", i);
278         if (memcmp(uuid, &gDescriptors[i]->uuid, sizeof(effect_uuid_t)) == 0) {
279             memcpy(pDescriptor, gDescriptors[i], sizeof(effect_descriptor_t));
280             ALOGV("EffectGetDescriptor - UUID matched downmix type %d, UUID = %" PRIx32,
281                  i, gDescriptors[i]->uuid.timeLow);
282             return 0;
283         }
284     }
285 
286     return -EINVAL;
287 }
288 
289 
290 /*--- Effect Control Interface Implementation ---*/
291 
Downmix_Process(effect_handle_t self,audio_buffer_t * inBuffer,audio_buffer_t * outBuffer)292 static int Downmix_Process(effect_handle_t self,
293         audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) {
294 
295     downmix_object_t *pDownmixer;
296     int16_t *pSrc, *pDst;
297     downmix_module_t *pDwmModule = (downmix_module_t *)self;
298 
299     if (pDwmModule == NULL) {
300         return -EINVAL;
301     }
302 
303     if (inBuffer == NULL || inBuffer->raw == NULL ||
304         outBuffer == NULL || outBuffer->raw == NULL ||
305         inBuffer->frameCount != outBuffer->frameCount) {
306         return -EINVAL;
307     }
308 
309     pDownmixer = (downmix_object_t*) &pDwmModule->context;
310 
311     if (pDownmixer->state == DOWNMIX_STATE_UNINITIALIZED) {
312         ALOGE("Downmix_Process error: trying to use an uninitialized downmixer");
313         return -EINVAL;
314     } else if (pDownmixer->state == DOWNMIX_STATE_INITIALIZED) {
315         ALOGE("Downmix_Process error: trying to use a non-configured downmixer");
316         return -ENODATA;
317     }
318 
319     pSrc = inBuffer->s16;
320     pDst = outBuffer->s16;
321     size_t numFrames = outBuffer->frameCount;
322 
323     const bool accumulate =
324             (pDwmModule->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE);
325     const uint32_t downmixInputChannelMask = pDwmModule->config.inputCfg.channels;
326 
327     switch(pDownmixer->type) {
328 
329       case DOWNMIX_TYPE_STRIP:
330           if (accumulate) {
331               while (numFrames) {
332                   pDst[0] = clamp16(pDst[0] + pSrc[0]);
333                   pDst[1] = clamp16(pDst[1] + pSrc[1]);
334                   pSrc += pDownmixer->input_channel_count;
335                   pDst += 2;
336                   numFrames--;
337               }
338           } else {
339               while (numFrames) {
340                   pDst[0] = pSrc[0];
341                   pDst[1] = pSrc[1];
342                   pSrc += pDownmixer->input_channel_count;
343                   pDst += 2;
344                   numFrames--;
345               }
346           }
347           break;
348 
349       case DOWNMIX_TYPE_FOLD:
350 #ifdef DOWNMIX_ALWAYS_USE_GENERIC_DOWNMIXER
351           // bypass the optimized downmix routines for the common formats
352           if (!Downmix_foldGeneric(
353                   downmixInputChannelMask, pSrc, pDst, numFrames, accumulate)) {
354               ALOGE("Multichannel configuration 0x%" PRIx32 " is not supported", downmixInputChannelMask);
355               return -EINVAL;
356           }
357           break;
358 #endif
359         // optimize for the common formats
360         switch((downmix_input_channel_mask_t)downmixInputChannelMask) {
361         case CHANNEL_MASK_QUAD_BACK:
362         case CHANNEL_MASK_QUAD_SIDE:
363             Downmix_foldFromQuad(pSrc, pDst, numFrames, accumulate);
364             break;
365         case CHANNEL_MASK_5POINT1_BACK:
366         case CHANNEL_MASK_5POINT1_SIDE:
367             Downmix_foldFrom5Point1(pSrc, pDst, numFrames, accumulate);
368             break;
369         case CHANNEL_MASK_7POINT1:
370             Downmix_foldFrom7Point1(pSrc, pDst, numFrames, accumulate);
371             break;
372         default:
373             if (!Downmix_foldGeneric(
374                     downmixInputChannelMask, pSrc, pDst, numFrames, accumulate)) {
375                 ALOGE("Multichannel configuration 0x%" PRIx32 " is not supported", downmixInputChannelMask);
376                 return -EINVAL;
377             }
378             break;
379         }
380         break;
381 
382       default:
383         return -EINVAL;
384     }
385 
386     return 0;
387 }
388 
389 
Downmix_Command(effect_handle_t self,uint32_t cmdCode,uint32_t cmdSize,void * pCmdData,uint32_t * replySize,void * pReplyData)390 static int Downmix_Command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
391         void *pCmdData, uint32_t *replySize, void *pReplyData) {
392 
393     downmix_module_t *pDwmModule = (downmix_module_t *) self;
394     downmix_object_t *pDownmixer;
395 
396     if (pDwmModule == NULL || pDwmModule->context.state == DOWNMIX_STATE_UNINITIALIZED) {
397         return -EINVAL;
398     }
399 
400     pDownmixer = (downmix_object_t*) &pDwmModule->context;
401 
402     ALOGV("Downmix_Command command %" PRIu32 " cmdSize %" PRIu32, cmdCode, cmdSize);
403 
404     switch (cmdCode) {
405     case EFFECT_CMD_INIT:
406         if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) {
407             return -EINVAL;
408         }
409         *(int *) pReplyData = Downmix_Init(pDwmModule);
410         break;
411 
412     case EFFECT_CMD_SET_CONFIG:
413         if (pCmdData == NULL || cmdSize != sizeof(effect_config_t)
414                 || pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) {
415             return -EINVAL;
416         }
417         *(int *) pReplyData = Downmix_Configure(pDwmModule,
418                 (effect_config_t *)pCmdData, false);
419         break;
420 
421     case EFFECT_CMD_RESET:
422         Downmix_Reset(pDownmixer, false);
423         break;
424 
425     case EFFECT_CMD_GET_PARAM:
426         ALOGV("Downmix_Command EFFECT_CMD_GET_PARAM pCmdData %p, *replySize %" PRIu32 ", pReplyData: %p",
427                 pCmdData, *replySize, pReplyData);
428         if (pCmdData == NULL || cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t)) ||
429                 pReplyData == NULL || replySize == NULL ||
430                 *replySize < (int) sizeof(effect_param_t) + 2 * sizeof(int32_t)) {
431             return -EINVAL;
432         }
433         effect_param_t *rep = (effect_param_t *) pReplyData;
434         memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + sizeof(int32_t));
435         ALOGV("Downmix_Command EFFECT_CMD_GET_PARAM param %" PRId32 ", replySize %" PRIu32,
436                 *(int32_t *)rep->data, rep->vsize);
437         rep->status = Downmix_getParameter(pDownmixer, *(int32_t *)rep->data, &rep->vsize,
438                 rep->data + sizeof(int32_t));
439         *replySize = sizeof(effect_param_t) + sizeof(int32_t) + rep->vsize;
440         break;
441 
442     case EFFECT_CMD_SET_PARAM:
443         ALOGV("Downmix_Command EFFECT_CMD_SET_PARAM cmdSize %d pCmdData %p, *replySize %" PRIu32
444                 ", pReplyData %p", cmdSize, pCmdData, *replySize, pReplyData);
445         if (pCmdData == NULL || (cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t)))
446                 || pReplyData == NULL || replySize == NULL || *replySize != (int)sizeof(int32_t)) {
447             return -EINVAL;
448         }
449         effect_param_t *cmd = (effect_param_t *) pCmdData;
450         *(int *)pReplyData = Downmix_setParameter(pDownmixer, *(int32_t *)cmd->data,
451                 cmd->vsize, cmd->data + sizeof(int32_t));
452         break;
453 
454     case EFFECT_CMD_SET_PARAM_DEFERRED:
455         //FIXME implement
456         ALOGW("Downmix_Command command EFFECT_CMD_SET_PARAM_DEFERRED not supported, FIXME");
457         break;
458 
459     case EFFECT_CMD_SET_PARAM_COMMIT:
460         //FIXME implement
461         ALOGW("Downmix_Command command EFFECT_CMD_SET_PARAM_COMMIT not supported, FIXME");
462         break;
463 
464     case EFFECT_CMD_ENABLE:
465         if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) {
466             return -EINVAL;
467         }
468         if (pDownmixer->state != DOWNMIX_STATE_INITIALIZED) {
469             return -ENOSYS;
470         }
471         pDownmixer->state = DOWNMIX_STATE_ACTIVE;
472         ALOGV("EFFECT_CMD_ENABLE() OK");
473         *(int *)pReplyData = 0;
474         break;
475 
476     case EFFECT_CMD_DISABLE:
477         if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) {
478             return -EINVAL;
479         }
480         if (pDownmixer->state != DOWNMIX_STATE_ACTIVE) {
481             return -ENOSYS;
482         }
483         pDownmixer->state = DOWNMIX_STATE_INITIALIZED;
484         ALOGV("EFFECT_CMD_DISABLE() OK");
485         *(int *)pReplyData = 0;
486         break;
487 
488     case EFFECT_CMD_SET_DEVICE:
489         if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t)) {
490             return -EINVAL;
491         }
492         // FIXME change type if playing on headset vs speaker
493         ALOGV("Downmix_Command EFFECT_CMD_SET_DEVICE: 0x%08" PRIx32, *(uint32_t *)pCmdData);
494         break;
495 
496     case EFFECT_CMD_SET_VOLUME: {
497         // audio output is always stereo => 2 channel volumes
498         if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t) * 2) {
499             return -EINVAL;
500         }
501         // FIXME change volume
502         ALOGW("Downmix_Command command EFFECT_CMD_SET_VOLUME not supported, FIXME");
503         float left = (float)(*(uint32_t *)pCmdData) / (1 << 24);
504         float right = (float)(*((uint32_t *)pCmdData + 1)) / (1 << 24);
505         ALOGV("Downmix_Command EFFECT_CMD_SET_VOLUME: left %f, right %f ", left, right);
506         break;
507     }
508 
509     case EFFECT_CMD_SET_AUDIO_MODE:
510         if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t)) {
511             return -EINVAL;
512         }
513         ALOGV("Downmix_Command EFFECT_CMD_SET_AUDIO_MODE: %" PRIu32, *(uint32_t *)pCmdData);
514         break;
515 
516     case EFFECT_CMD_SET_CONFIG_REVERSE:
517     case EFFECT_CMD_SET_INPUT_DEVICE:
518         // these commands are ignored by a downmix effect
519         break;
520 
521     default:
522         ALOGW("Downmix_Command invalid command %" PRIu32, cmdCode);
523         return -EINVAL;
524     }
525 
526     return 0;
527 }
528 
529 
Downmix_GetDescriptor(effect_handle_t self,effect_descriptor_t * pDescriptor)530 int Downmix_GetDescriptor(effect_handle_t self, effect_descriptor_t *pDescriptor)
531 {
532     downmix_module_t *pDwnmxModule = (downmix_module_t *) self;
533 
534     if (pDwnmxModule == NULL ||
535             pDwnmxModule->context.state == DOWNMIX_STATE_UNINITIALIZED) {
536         return -EINVAL;
537     }
538 
539     memcpy(pDescriptor, &gDownmixDescriptor, sizeof(effect_descriptor_t));
540 
541     return 0;
542 }
543 
544 
545 /*----------------------------------------------------------------------------
546  * Downmix internal functions
547  *--------------------------------------------------------------------------*/
548 
549 /*----------------------------------------------------------------------------
550  * Downmix_Init()
551  *----------------------------------------------------------------------------
552  * Purpose:
553  * Initialize downmix context and apply default parameters
554  *
555  * Inputs:
556  *  pDwmModule    pointer to downmix effect module
557  *
558  * Outputs:
559  *
560  * Returns:
561  *  0             indicates success
562  *
563  * Side Effects:
564  *  updates:
565  *           pDwmModule->context.type
566  *           pDwmModule->context.apply_volume_correction
567  *           pDwmModule->config.inputCfg
568  *           pDwmModule->config.outputCfg
569  *           pDwmModule->config.inputCfg.samplingRate
570  *           pDwmModule->config.outputCfg.samplingRate
571  *           pDwmModule->context.state
572  *  doesn't set:
573  *           pDwmModule->itfe
574  *
575  *----------------------------------------------------------------------------
576  */
577 
Downmix_Init(downmix_module_t * pDwmModule)578 int Downmix_Init(downmix_module_t *pDwmModule) {
579 
580     ALOGV("Downmix_Init module %p", pDwmModule);
581     int ret = 0;
582 
583     memset(&pDwmModule->context, 0, sizeof(downmix_object_t));
584 
585     pDwmModule->config.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
586     pDwmModule->config.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
587     pDwmModule->config.inputCfg.channels = AUDIO_CHANNEL_OUT_7POINT1;
588     pDwmModule->config.inputCfg.bufferProvider.getBuffer = NULL;
589     pDwmModule->config.inputCfg.bufferProvider.releaseBuffer = NULL;
590     pDwmModule->config.inputCfg.bufferProvider.cookie = NULL;
591     pDwmModule->config.inputCfg.mask = EFFECT_CONFIG_ALL;
592 
593     pDwmModule->config.inputCfg.samplingRate = 44100;
594     pDwmModule->config.outputCfg.samplingRate = pDwmModule->config.inputCfg.samplingRate;
595 
596     // set a default value for the access mode, but should be overwritten by caller
597     pDwmModule->config.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE;
598     pDwmModule->config.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
599     pDwmModule->config.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
600     pDwmModule->config.outputCfg.bufferProvider.getBuffer = NULL;
601     pDwmModule->config.outputCfg.bufferProvider.releaseBuffer = NULL;
602     pDwmModule->config.outputCfg.bufferProvider.cookie = NULL;
603     pDwmModule->config.outputCfg.mask = EFFECT_CONFIG_ALL;
604 
605     ret = Downmix_Configure(pDwmModule, &pDwmModule->config, true);
606     if (ret != 0) {
607         ALOGV("Downmix_Init error %d on module %p", ret, pDwmModule);
608     } else {
609         pDwmModule->context.state = DOWNMIX_STATE_INITIALIZED;
610     }
611 
612     return ret;
613 }
614 
615 
616 /*----------------------------------------------------------------------------
617  * Downmix_Configure()
618  *----------------------------------------------------------------------------
619  * Purpose:
620  *  Set input and output audio configuration.
621  *
622  * Inputs:
623  *  pDwmModule  pointer to downmix effect module
624  *  pConfig     pointer to effect_config_t structure containing input
625  *                  and output audio parameters configuration
626  *  init        true if called from init function
627  *
628  * Outputs:
629  *
630  * Returns:
631  *  0           indicates success
632  *
633  * Side Effects:
634  *
635  *----------------------------------------------------------------------------
636  */
637 
Downmix_Configure(downmix_module_t * pDwmModule,effect_config_t * pConfig,bool init)638 int Downmix_Configure(downmix_module_t *pDwmModule, effect_config_t *pConfig, bool init) {
639 
640     downmix_object_t *pDownmixer = &pDwmModule->context;
641 
642     // Check configuration compatibility with build options, and effect capabilities
643     if (pConfig->inputCfg.samplingRate != pConfig->outputCfg.samplingRate
644         || pConfig->outputCfg.channels != DOWNMIX_OUTPUT_CHANNELS
645         || pConfig->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT
646         || pConfig->outputCfg.format != AUDIO_FORMAT_PCM_16_BIT) {
647         ALOGE("Downmix_Configure error: invalid config");
648         return -EINVAL;
649     }
650 
651     if (&pDwmModule->config != pConfig) {
652         memcpy(&pDwmModule->config, pConfig, sizeof(effect_config_t));
653     }
654 
655     if (init) {
656         pDownmixer->type = DOWNMIX_TYPE_FOLD;
657         pDownmixer->apply_volume_correction = false;
658         pDownmixer->input_channel_count = 8; // matches default input of AUDIO_CHANNEL_OUT_7POINT1
659     } else {
660         // when configuring the effect, do not allow a blank or unsupported channel mask
661         if (!Downmix_validChannelMask(pConfig->inputCfg.channels)) {
662             ALOGE("Downmix_Configure error: input channel mask(0x%x) not supported",
663                                                         pConfig->inputCfg.channels);
664             return -EINVAL;
665         }
666         pDownmixer->input_channel_count =
667                 audio_channel_count_from_out_mask(pConfig->inputCfg.channels);
668     }
669 
670     Downmix_Reset(pDownmixer, init);
671 
672     return 0;
673 }
674 
675 
676 /*----------------------------------------------------------------------------
677  * Downmix_Reset()
678  *----------------------------------------------------------------------------
679  * Purpose:
680  *  Reset internal states.
681  *
682  * Inputs:
683  *  pDownmixer   pointer to downmix context
684  *  init         true if called from init function
685  *
686  * Outputs:
687 *
688  * Returns:
689  *  0            indicates success
690  *
691  * Side Effects:
692  *
693  *----------------------------------------------------------------------------
694  */
695 
Downmix_Reset(downmix_object_t * pDownmixer __unused,bool init __unused)696 int Downmix_Reset(downmix_object_t *pDownmixer __unused, bool init __unused) {
697     // nothing to do here
698     return 0;
699 }
700 
701 
702 /*----------------------------------------------------------------------------
703  * Downmix_setParameter()
704  *----------------------------------------------------------------------------
705  * Purpose:
706  * Set a Downmix parameter
707  *
708  * Inputs:
709  *  pDownmixer    handle to instance data
710  *  param         parameter
711  *  pValue        pointer to parameter value
712  *  size          value size
713  *
714  * Outputs:
715  *
716  * Returns:
717  *  0             indicates success
718  *
719  * Side Effects:
720  *
721  *----------------------------------------------------------------------------
722  */
Downmix_setParameter(downmix_object_t * pDownmixer,int32_t param,uint32_t size,void * pValue)723 int Downmix_setParameter(downmix_object_t *pDownmixer, int32_t param, uint32_t size, void *pValue) {
724 
725     int16_t value16;
726     ALOGV("Downmix_setParameter, context %p, param %" PRId32 ", value16 %" PRId16 ", value32 %" PRId32,
727             pDownmixer, param, *(int16_t *)pValue, *(int32_t *)pValue);
728 
729     switch (param) {
730 
731       case DOWNMIX_PARAM_TYPE:
732         if (size != sizeof(downmix_type_t)) {
733             ALOGE("Downmix_setParameter(DOWNMIX_PARAM_TYPE) invalid size %" PRIu32 ", should be %zu",
734                     size, sizeof(downmix_type_t));
735             return -EINVAL;
736         }
737         value16 = *(int16_t *)pValue;
738         ALOGV("set DOWNMIX_PARAM_TYPE, type %" PRId16, value16);
739         if (!((value16 > DOWNMIX_TYPE_INVALID) && (value16 <= DOWNMIX_TYPE_LAST))) {
740             ALOGE("Downmix_setParameter invalid DOWNMIX_PARAM_TYPE value %" PRId16, value16);
741             return -EINVAL;
742         } else {
743             pDownmixer->type = (downmix_type_t) value16;
744         break;
745 
746       default:
747         ALOGE("Downmix_setParameter unknown parameter %" PRId32, param);
748         return -EINVAL;
749     }
750 }
751 
752     return 0;
753 } /* end Downmix_setParameter */
754 
755 
756 /*----------------------------------------------------------------------------
757  * Downmix_getParameter()
758  *----------------------------------------------------------------------------
759  * Purpose:
760  * Get a Downmix parameter
761  *
762  * Inputs:
763  *  pDownmixer    handle to instance data
764  *  param         parameter
765  *  pValue        pointer to variable to hold retrieved value
766  *  pSize         pointer to value size: maximum size as input
767  *
768  * Outputs:
769  *  *pValue updated with parameter value
770  *  *pSize updated with actual value size
771  *
772  * Returns:
773  *  0             indicates success
774  *
775  * Side Effects:
776  *
777  *----------------------------------------------------------------------------
778  */
Downmix_getParameter(downmix_object_t * pDownmixer,int32_t param,uint32_t * pSize,void * pValue)779 int Downmix_getParameter(downmix_object_t *pDownmixer, int32_t param, uint32_t *pSize, void *pValue) {
780     int16_t *pValue16;
781 
782     switch (param) {
783 
784     case DOWNMIX_PARAM_TYPE:
785       if (*pSize < sizeof(int16_t)) {
786           ALOGE("Downmix_getParameter invalid parameter size %" PRIu32 " for DOWNMIX_PARAM_TYPE", *pSize);
787           return -EINVAL;
788       }
789       pValue16 = (int16_t *)pValue;
790       *pValue16 = (int16_t) pDownmixer->type;
791       *pSize = sizeof(int16_t);
792       ALOGV("Downmix_getParameter DOWNMIX_PARAM_TYPE is %" PRId16, *pValue16);
793       break;
794 
795     default:
796       ALOGE("Downmix_getParameter unknown parameter %" PRId16, param);
797       return -EINVAL;
798     }
799 
800     return 0;
801 } /* end Downmix_getParameter */
802 
803 
804 /*----------------------------------------------------------------------------
805  * Downmix_foldFromQuad()
806  *----------------------------------------------------------------------------
807  * Purpose:
808  * downmix a quad signal to stereo
809  *
810  * Inputs:
811  *  pSrc       quad audio samples to downmix
812  *  numFrames  the number of quad frames to downmix
813  *  accumulate whether to mix (when true) the result of the downmix with the contents of pDst,
814  *               or overwrite pDst (when false)
815  *
816  * Outputs:
817  *  pDst       downmixed stereo audio samples
818  *
819  *----------------------------------------------------------------------------
820  */
Downmix_foldFromQuad(int16_t * pSrc,int16_t * pDst,size_t numFrames,bool accumulate)821 void Downmix_foldFromQuad(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) {
822     // sample at index 0 is FL
823     // sample at index 1 is FR
824     // sample at index 2 is RL
825     // sample at index 3 is RR
826     if (accumulate) {
827         while (numFrames) {
828             // FL + RL
829             pDst[0] = clamp16(pDst[0] + ((pSrc[0] + pSrc[2]) >> 1));
830             // FR + RR
831             pDst[1] = clamp16(pDst[1] + ((pSrc[1] + pSrc[3]) >> 1));
832             pSrc += 4;
833             pDst += 2;
834             numFrames--;
835         }
836     } else { // same code as above but without adding and clamping pDst[i] to itself
837         while (numFrames) {
838             // FL + RL
839             pDst[0] = clamp16((pSrc[0] + pSrc[2]) >> 1);
840             // FR + RR
841             pDst[1] = clamp16((pSrc[1] + pSrc[3]) >> 1);
842             pSrc += 4;
843             pDst += 2;
844             numFrames--;
845         }
846     }
847 }
848 
849 
850 /*----------------------------------------------------------------------------
851  * Downmix_foldFrom5Point1()
852  *----------------------------------------------------------------------------
853  * Purpose:
854  * downmix a 5.1 signal to stereo
855  *
856  * Inputs:
857  *  pSrc       5.1 audio samples to downmix
858  *  numFrames  the number of 5.1 frames to downmix
859  *  accumulate whether to mix (when true) the result of the downmix with the contents of pDst,
860  *               or overwrite pDst (when false)
861  *
862  * Outputs:
863  *  pDst       downmixed stereo audio samples
864  *
865  *----------------------------------------------------------------------------
866  */
Downmix_foldFrom5Point1(int16_t * pSrc,int16_t * pDst,size_t numFrames,bool accumulate)867 void Downmix_foldFrom5Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) {
868     int32_t lt, rt, centerPlusLfeContrib; // samples in Q19.12 format
869     // sample at index 0 is FL
870     // sample at index 1 is FR
871     // sample at index 2 is FC
872     // sample at index 3 is LFE
873     // sample at index 4 is RL
874     // sample at index 5 is RR
875     // code is mostly duplicated between the two values of accumulate to avoid repeating the test
876     // for every sample
877     if (accumulate) {
878         while (numFrames) {
879             // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB)
880             centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12)
881                     + (pSrc[3] * MINUS_3_DB_IN_Q19_12);
882             // FL + centerPlusLfeContrib + RL
883             lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[4] << 12);
884             // FR + centerPlusLfeContrib + RR
885             rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[5] << 12);
886             // accumulate in destination
887             pDst[0] = clamp16(pDst[0] + (lt >> 13));
888             pDst[1] = clamp16(pDst[1] + (rt >> 13));
889             pSrc += 6;
890             pDst += 2;
891             numFrames--;
892         }
893     } else { // same code as above but without adding and clamping pDst[i] to itself
894         while (numFrames) {
895             // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB)
896             centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12)
897                     + (pSrc[3] * MINUS_3_DB_IN_Q19_12);
898             // FL + centerPlusLfeContrib + RL
899             lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[4] << 12);
900             // FR + centerPlusLfeContrib + RR
901             rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[5] << 12);
902             // store in destination
903             pDst[0] = clamp16(lt >> 13); // differs from when accumulate is true above
904             pDst[1] = clamp16(rt >> 13); // differs from when accumulate is true above
905             pSrc += 6;
906             pDst += 2;
907             numFrames--;
908         }
909     }
910 }
911 
912 
913 /*----------------------------------------------------------------------------
914  * Downmix_foldFrom7Point1()
915  *----------------------------------------------------------------------------
916  * Purpose:
917  * downmix a 7.1 signal to stereo
918  *
919  * Inputs:
920  *  pSrc       7.1 audio samples to downmix
921  *  numFrames  the number of 7.1 frames to downmix
922  *  accumulate whether to mix (when true) the result of the downmix with the contents of pDst,
923  *               or overwrite pDst (when false)
924  *
925  * Outputs:
926  *  pDst       downmixed stereo audio samples
927  *
928  *----------------------------------------------------------------------------
929  */
Downmix_foldFrom7Point1(int16_t * pSrc,int16_t * pDst,size_t numFrames,bool accumulate)930 void Downmix_foldFrom7Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) {
931     int32_t lt, rt, centerPlusLfeContrib; // samples in Q19.12 format
932     // sample at index 0 is FL
933     // sample at index 1 is FR
934     // sample at index 2 is FC
935     // sample at index 3 is LFE
936     // sample at index 4 is RL
937     // sample at index 5 is RR
938     // sample at index 6 is SL
939     // sample at index 7 is SR
940     // code is mostly duplicated between the two values of accumulate to avoid repeating the test
941     // for every sample
942     if (accumulate) {
943         while (numFrames) {
944             // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB)
945             centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12)
946                     + (pSrc[3] * MINUS_3_DB_IN_Q19_12);
947             // FL + centerPlusLfeContrib + SL + RL
948             lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[6] << 12) + (pSrc[4] << 12);
949             // FR + centerPlusLfeContrib + SR + RR
950             rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[7] << 12) + (pSrc[5] << 12);
951             //accumulate in destination
952             pDst[0] = clamp16(pDst[0] + (lt >> 13));
953             pDst[1] = clamp16(pDst[1] + (rt >> 13));
954             pSrc += 8;
955             pDst += 2;
956             numFrames--;
957     }
958     } else { // same code as above but without adding and clamping pDst[i] to itself
959         while (numFrames) {
960             // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB)
961             centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12)
962                     + (pSrc[3] * MINUS_3_DB_IN_Q19_12);
963             // FL + centerPlusLfeContrib + SL + RL
964             lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[6] << 12) + (pSrc[4] << 12);
965             // FR + centerPlusLfeContrib + SR + RR
966             rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[7] << 12) + (pSrc[5] << 12);
967             // store in destination
968             pDst[0] = clamp16(lt >> 13); // differs from when accumulate is true above
969             pDst[1] = clamp16(rt >> 13); // differs from when accumulate is true above
970             pSrc += 8;
971             pDst += 2;
972             numFrames--;
973         }
974     }
975 }
976 
977 
978 /*----------------------------------------------------------------------------
979  * Downmix_foldGeneric()
980  *----------------------------------------------------------------------------
981  * Purpose:
982  * downmix to stereo a multichannel signal whose format is:
983  *  - has FL/FR
984  *  - if using AUDIO_CHANNEL_OUT_SIDE*, it contains both left and right
985  *  - if using AUDIO_CHANNEL_OUT_BACK*, it contains both left and right
986  *  - doesn't use any of the AUDIO_CHANNEL_OUT_TOP* channels
987  *  - doesn't use any of the AUDIO_CHANNEL_OUT_FRONT_*_OF_CENTER channels
988  * Only handles channel masks not enumerated in downmix_input_channel_mask_t
989  *
990  * Inputs:
991  *  mask       the channel mask of pSrc
992  *  pSrc       multichannel audio buffer to downmix
993  *  numFrames  the number of multichannel frames to downmix
994  *  accumulate whether to mix (when true) the result of the downmix with the contents of pDst,
995  *               or overwrite pDst (when false)
996  *
997  * Outputs:
998  *  pDst       downmixed stereo audio samples
999  *
1000  * Returns: false if multichannel format is not supported
1001  *
1002  *----------------------------------------------------------------------------
1003  */
Downmix_foldGeneric(uint32_t mask,int16_t * pSrc,int16_t * pDst,size_t numFrames,bool accumulate)1004 bool Downmix_foldGeneric(
1005         uint32_t mask, int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) {
1006 
1007     if (!Downmix_validChannelMask(mask)) {
1008         return false;
1009     }
1010 
1011     const bool hasSides = (mask & kSides) != 0;
1012     const bool hasBacks = (mask & kBacks) != 0;
1013 
1014     const int numChan = audio_channel_count_from_out_mask(mask);
1015     const bool hasFC = ((mask & AUDIO_CHANNEL_OUT_FRONT_CENTER) == AUDIO_CHANNEL_OUT_FRONT_CENTER);
1016     const bool hasLFE =
1017             ((mask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY);
1018     const bool hasBC = ((mask & AUDIO_CHANNEL_OUT_BACK_CENTER) == AUDIO_CHANNEL_OUT_BACK_CENTER);
1019     // compute at what index each channel is: samples will be in the following order:
1020     //   FL FR FC LFE BL BR BC SL SR
1021     // when a channel is not present, its index is set to the same as the index of the preceding
1022     // channel
1023     const int indexFC  = hasFC    ? 2            : 1;        // front center
1024     const int indexLFE = hasLFE   ? indexFC + 1  : indexFC;  // low frequency
1025     const int indexBL  = hasBacks ? indexLFE + 1 : indexLFE; // back left
1026     const int indexBR  = hasBacks ? indexBL + 1  : indexBL;  // back right
1027     const int indexBC  = hasBC    ? indexBR + 1  : indexBR;  // back center
1028     const int indexSL  = hasSides ? indexBC + 1  : indexBC;  // side left
1029     const int indexSR  = hasSides ? indexSL + 1  : indexSL;  // side right
1030 
1031     int32_t lt, rt, centersLfeContrib; // samples in Q19.12 format
1032     // code is mostly duplicated between the two values of accumulate to avoid repeating the test
1033     // for every sample
1034     if (accumulate) {
1035         while (numFrames) {
1036             // compute contribution of FC, BC and LFE
1037             centersLfeContrib = 0;
1038             if (hasFC)  { centersLfeContrib += pSrc[indexFC]; }
1039             if (hasLFE) { centersLfeContrib += pSrc[indexLFE]; }
1040             if (hasBC)  { centersLfeContrib += pSrc[indexBC]; }
1041             centersLfeContrib *= MINUS_3_DB_IN_Q19_12;
1042             // always has FL/FR
1043             lt = (pSrc[0] << 12);
1044             rt = (pSrc[1] << 12);
1045             // mix in sides and backs
1046             if (hasSides) {
1047                 lt += pSrc[indexSL] << 12;
1048                 rt += pSrc[indexSR] << 12;
1049             }
1050             if (hasBacks) {
1051                 lt += pSrc[indexBL] << 12;
1052                 rt += pSrc[indexBR] << 12;
1053             }
1054             lt += centersLfeContrib;
1055             rt += centersLfeContrib;
1056             // accumulate in destination
1057             pDst[0] = clamp16(pDst[0] + (lt >> 13));
1058             pDst[1] = clamp16(pDst[1] + (rt >> 13));
1059             pSrc += numChan;
1060             pDst += 2;
1061             numFrames--;
1062         }
1063     } else {
1064         while (numFrames) {
1065             // compute contribution of FC, BC and LFE
1066             centersLfeContrib = 0;
1067             if (hasFC)  { centersLfeContrib += pSrc[indexFC]; }
1068             if (hasLFE) { centersLfeContrib += pSrc[indexLFE]; }
1069             if (hasBC)  { centersLfeContrib += pSrc[indexBC]; }
1070             centersLfeContrib *= MINUS_3_DB_IN_Q19_12;
1071             // always has FL/FR
1072             lt = (pSrc[0] << 12);
1073             rt = (pSrc[1] << 12);
1074             // mix in sides and backs
1075             if (hasSides) {
1076                 lt += pSrc[indexSL] << 12;
1077                 rt += pSrc[indexSR] << 12;
1078             }
1079             if (hasBacks) {
1080                 lt += pSrc[indexBL] << 12;
1081                 rt += pSrc[indexBR] << 12;
1082             }
1083             lt += centersLfeContrib;
1084             rt += centersLfeContrib;
1085             // store in destination
1086             pDst[0] = clamp16(lt >> 13); // differs from when accumulate is true above
1087             pDst[1] = clamp16(rt >> 13); // differs from when accumulate is true above
1088             pSrc += numChan;
1089             pDst += 2;
1090             numFrames--;
1091         }
1092     }
1093     return true;
1094 }
1095