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