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