/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "EffectDownmix" //#define LOG_NDEBUG 0 #include <log/log.h> #include <inttypes.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include "EffectDownmix.h" // Do not submit with DOWNMIX_TEST_CHANNEL_INDEX defined, strictly for testing //#define DOWNMIX_TEST_CHANNEL_INDEX 0 // Do not submit with DOWNMIX_ALWAYS_USE_GENERIC_DOWNMIXER defined, strictly for testing //#define DOWNMIX_ALWAYS_USE_GENERIC_DOWNMIXER 0 #define MINUS_3_DB_IN_Q19_12 2896 // -3dB = 0.707 * 2^12 = 2896 // subset of possible audio_channel_mask_t values, and AUDIO_CHANNEL_OUT_* renamed to CHANNEL_MASK_* typedef enum { CHANNEL_MASK_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD_BACK, CHANNEL_MASK_QUAD_SIDE = AUDIO_CHANNEL_OUT_QUAD_SIDE, CHANNEL_MASK_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1_BACK, CHANNEL_MASK_5POINT1_SIDE = AUDIO_CHANNEL_OUT_5POINT1_SIDE, CHANNEL_MASK_7POINT1 = AUDIO_CHANNEL_OUT_7POINT1, } downmix_input_channel_mask_t; // effect_handle_t interface implementation for downmix effect const struct effect_interface_s gDownmixInterface = { Downmix_Process, Downmix_Command, Downmix_GetDescriptor, NULL /* no process_reverse function, no reference stream needed */ }; // This is the only symbol that needs to be exported __attribute__ ((visibility ("default"))) audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = { .tag = AUDIO_EFFECT_LIBRARY_TAG, .version = EFFECT_LIBRARY_API_VERSION, .name = "Downmix Library", .implementor = "The Android Open Source Project", .create_effect = DownmixLib_Create, .release_effect = DownmixLib_Release, .get_descriptor = DownmixLib_GetDescriptor, }; // AOSP insert downmix UUID: 93f04452-e4fe-41cc-91f9-e475b6d1d69f static const effect_descriptor_t gDownmixDescriptor = { EFFECT_UIID_DOWNMIX__, //type {0x93f04452, 0xe4fe, 0x41cc, 0x91f9, {0xe4, 0x75, 0xb6, 0xd1, 0xd6, 0x9f}}, // uuid EFFECT_CONTROL_API_VERSION, EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST, 0, //FIXME what value should be reported? // cpu load 0, //FIXME what value should be reported? // memory usage "Multichannel Downmix To Stereo", // human readable effect name "The Android Open Source Project" // human readable effect implementor name }; // gDescriptors contains pointers to all defined effect descriptor in this library static const effect_descriptor_t * const gDescriptors[] = { &gDownmixDescriptor }; // number of effects in this library const int kNbEffects = sizeof(gDescriptors) / sizeof(const effect_descriptor_t *); /*---------------------------------------------------------------------------- * Test code *--------------------------------------------------------------------------*/ #ifdef DOWNMIX_TEST_CHANNEL_INDEX // strictly for testing, logs the indices of the channels for a given mask, // uses the same code as Downmix_foldGeneric() void Downmix_testIndexComputation(uint32_t mask) { ALOGI("Testing index computation for 0x%" PRIx32 ":", mask); // check against unsupported channels if (mask & kUnsupported) { ALOGE("Unsupported channels (top or front left/right of center)"); return; } // verify has FL/FR if ((mask & AUDIO_CHANNEL_OUT_STEREO) != AUDIO_CHANNEL_OUT_STEREO) { ALOGE("Front channels must be present"); return; } // verify uses SIDE as a pair (ok if not using SIDE at all) bool hasSides = false; if ((mask & kSides) != 0) { if ((mask & kSides) != kSides) { ALOGE("Side channels must be used as a pair"); return; } hasSides = true; } // verify uses BACK as a pair (ok if not using BACK at all) bool hasBacks = false; if ((mask & kBacks) != 0) { if ((mask & kBacks) != kBacks) { ALOGE("Back channels must be used as a pair"); return; } hasBacks = true; } const int numChan = audio_channel_count_from_out_mask(mask); const bool hasFC = ((mask & AUDIO_CHANNEL_OUT_FRONT_CENTER) == AUDIO_CHANNEL_OUT_FRONT_CENTER); const bool hasLFE = ((mask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY); const bool hasBC = ((mask & AUDIO_CHANNEL_OUT_BACK_CENTER) == AUDIO_CHANNEL_OUT_BACK_CENTER); // compute at what index each channel is: samples will be in the following order: // FL FR FC LFE BL BR BC SL SR // when a channel is not present, its index is set to the same as the index of the preceding // channel const int indexFC = hasFC ? 2 : 1; // front center const int indexLFE = hasLFE ? indexFC + 1 : indexFC; // low frequency const int indexBL = hasBacks ? indexLFE + 1 : indexLFE; // back left const int indexBR = hasBacks ? indexBL + 1 : indexBL; // back right const int indexBC = hasBC ? indexBR + 1 : indexBR; // back center const int indexSL = hasSides ? indexBC + 1 : indexBC; // side left const int indexSR = hasSides ? indexSL + 1 : indexSL; // side right ALOGI(" FL FR FC LFE BL BR BC SL SR"); ALOGI(" %d %d %d %d %d %d %d %d %d", 0, 1, indexFC, indexLFE, indexBL, indexBR, indexBC, indexSL, indexSR); } #endif /*---------------------------------------------------------------------------- * Effect API implementation *--------------------------------------------------------------------------*/ /*--- Effect Library Interface Implementation ---*/ int32_t DownmixLib_Create(const effect_uuid_t *uuid, int32_t sessionId __unused, int32_t ioId __unused, effect_handle_t *pHandle) { int ret; int i; downmix_module_t *module; const effect_descriptor_t *desc; ALOGV("DownmixLib_Create()"); #ifdef DOWNMIX_TEST_CHANNEL_INDEX // should work (won't log an error) ALOGI("DOWNMIX_TEST_CHANNEL_INDEX: should work:"); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_FRONT_RIGHT | AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_BACK_CENTER); Downmix_testIndexComputation(CHANNEL_MASK_QUAD_SIDE | CHANNEL_MASK_QUAD_BACK); Downmix_testIndexComputation(CHANNEL_MASK_5POINT1_SIDE | AUDIO_CHANNEL_OUT_BACK_CENTER); Downmix_testIndexComputation(CHANNEL_MASK_5POINT1_BACK | AUDIO_CHANNEL_OUT_BACK_CENTER); // shouldn't work (will log an error, won't display channel indices) ALOGI("DOWNMIX_TEST_CHANNEL_INDEX: should NOT work:"); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_FRONT_RIGHT | AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_BACK_LEFT); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_FRONT_RIGHT | AUDIO_CHANNEL_OUT_LOW_FREQUENCY | AUDIO_CHANNEL_OUT_SIDE_LEFT); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_BACK_LEFT | AUDIO_CHANNEL_OUT_BACK_RIGHT); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_FRONT_LEFT | AUDIO_CHANNEL_OUT_SIDE_LEFT | AUDIO_CHANNEL_OUT_SIDE_RIGHT); #endif if (pHandle == NULL || uuid == NULL) { return -EINVAL; } for (i = 0 ; i < kNbEffects ; i++) { desc = gDescriptors[i]; if (memcmp(uuid, &desc->uuid, sizeof(effect_uuid_t)) == 0) { break; } } if (i == kNbEffects) { return -ENOENT; } module = malloc(sizeof(downmix_module_t)); module->itfe = &gDownmixInterface; module->context.state = DOWNMIX_STATE_UNINITIALIZED; ret = Downmix_Init(module); if (ret < 0) { ALOGW("DownmixLib_Create() init failed"); free(module); return ret; } *pHandle = (effect_handle_t) module; ALOGV("DownmixLib_Create() %p , size %zu", module, sizeof(downmix_module_t)); return 0; } int32_t DownmixLib_Release(effect_handle_t handle) { downmix_module_t *pDwmModule = (downmix_module_t *)handle; ALOGV("DownmixLib_Release() %p", handle); if (handle == NULL) { return -EINVAL; } pDwmModule->context.state = DOWNMIX_STATE_UNINITIALIZED; free(pDwmModule); return 0; } int32_t DownmixLib_GetDescriptor(const effect_uuid_t *uuid, effect_descriptor_t *pDescriptor) { ALOGV("DownmixLib_GetDescriptor()"); int i; if (pDescriptor == NULL || uuid == NULL){ ALOGE("DownmixLib_Create() called with NULL pointer"); return -EINVAL; } ALOGV("DownmixLib_GetDescriptor() nb effects=%d", kNbEffects); for (i = 0; i < kNbEffects; i++) { ALOGV("DownmixLib_GetDescriptor() i=%d", i); if (memcmp(uuid, &gDescriptors[i]->uuid, sizeof(effect_uuid_t)) == 0) { memcpy(pDescriptor, gDescriptors[i], sizeof(effect_descriptor_t)); ALOGV("EffectGetDescriptor - UUID matched downmix type %d, UUID = %" PRIx32, i, gDescriptors[i]->uuid.timeLow); return 0; } } return -EINVAL; } /*--- Effect Control Interface Implementation ---*/ static int Downmix_Process(effect_handle_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) { downmix_object_t *pDownmixer; int16_t *pSrc, *pDst; downmix_module_t *pDwmModule = (downmix_module_t *)self; if (pDwmModule == NULL) { return -EINVAL; } if (inBuffer == NULL || inBuffer->raw == NULL || outBuffer == NULL || outBuffer->raw == NULL || inBuffer->frameCount != outBuffer->frameCount) { return -EINVAL; } pDownmixer = (downmix_object_t*) &pDwmModule->context; if (pDownmixer->state == DOWNMIX_STATE_UNINITIALIZED) { ALOGE("Downmix_Process error: trying to use an uninitialized downmixer"); return -EINVAL; } else if (pDownmixer->state == DOWNMIX_STATE_INITIALIZED) { ALOGE("Downmix_Process error: trying to use a non-configured downmixer"); return -ENODATA; } pSrc = inBuffer->s16; pDst = outBuffer->s16; size_t numFrames = outBuffer->frameCount; const bool accumulate = (pDwmModule->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE); const uint32_t downmixInputChannelMask = pDwmModule->config.inputCfg.channels; switch(pDownmixer->type) { case DOWNMIX_TYPE_STRIP: if (accumulate) { while (numFrames) { pDst[0] = clamp16(pDst[0] + pSrc[0]); pDst[1] = clamp16(pDst[1] + pSrc[1]); pSrc += pDownmixer->input_channel_count; pDst += 2; numFrames--; } } else { while (numFrames) { pDst[0] = pSrc[0]; pDst[1] = pSrc[1]; pSrc += pDownmixer->input_channel_count; pDst += 2; numFrames--; } } break; case DOWNMIX_TYPE_FOLD: #ifdef DOWNMIX_ALWAYS_USE_GENERIC_DOWNMIXER // bypass the optimized downmix routines for the common formats if (!Downmix_foldGeneric( downmixInputChannelMask, pSrc, pDst, numFrames, accumulate)) { ALOGE("Multichannel configuration 0x%" PRIx32 " is not supported", downmixInputChannelMask); return -EINVAL; } break; #endif // optimize for the common formats switch((downmix_input_channel_mask_t)downmixInputChannelMask) { case CHANNEL_MASK_QUAD_BACK: case CHANNEL_MASK_QUAD_SIDE: Downmix_foldFromQuad(pSrc, pDst, numFrames, accumulate); break; case CHANNEL_MASK_5POINT1_BACK: case CHANNEL_MASK_5POINT1_SIDE: Downmix_foldFrom5Point1(pSrc, pDst, numFrames, accumulate); break; case CHANNEL_MASK_7POINT1: Downmix_foldFrom7Point1(pSrc, pDst, numFrames, accumulate); break; default: if (!Downmix_foldGeneric( downmixInputChannelMask, pSrc, pDst, numFrames, accumulate)) { ALOGE("Multichannel configuration 0x%" PRIx32 " is not supported", downmixInputChannelMask); return -EINVAL; } break; } break; default: return -EINVAL; } return 0; } static int Downmix_Command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData) { downmix_module_t *pDwmModule = (downmix_module_t *) self; downmix_object_t *pDownmixer; int retsize; if (pDwmModule == NULL || pDwmModule->context.state == DOWNMIX_STATE_UNINITIALIZED) { return -EINVAL; } pDownmixer = (downmix_object_t*) &pDwmModule->context; ALOGV("Downmix_Command command %" PRIu32 " cmdSize %" PRIu32, cmdCode, cmdSize); switch (cmdCode) { case EFFECT_CMD_INIT: if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { return -EINVAL; } *(int *) pReplyData = Downmix_Init(pDwmModule); break; case EFFECT_CMD_SET_CONFIG: if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) || pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { return -EINVAL; } *(int *) pReplyData = Downmix_Configure(pDwmModule, (effect_config_t *)pCmdData, false); break; case EFFECT_CMD_RESET: Downmix_Reset(pDownmixer, false); break; case EFFECT_CMD_GET_PARAM: ALOGV("Downmix_Command EFFECT_CMD_GET_PARAM pCmdData %p, *replySize %" PRIu32 ", pReplyData: %p", pCmdData, *replySize, pReplyData); if (pCmdData == NULL || cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t)) || pReplyData == NULL || replySize == NULL || *replySize < (int) sizeof(effect_param_t) + 2 * sizeof(int32_t)) { return -EINVAL; } effect_param_t *rep = (effect_param_t *) pReplyData; memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + sizeof(int32_t)); ALOGV("Downmix_Command EFFECT_CMD_GET_PARAM param %" PRId32 ", replySize %" PRIu32, *(int32_t *)rep->data, rep->vsize); rep->status = Downmix_getParameter(pDownmixer, *(int32_t *)rep->data, &rep->vsize, rep->data + sizeof(int32_t)); *replySize = sizeof(effect_param_t) + sizeof(int32_t) + rep->vsize; break; case EFFECT_CMD_SET_PARAM: ALOGV("Downmix_Command EFFECT_CMD_SET_PARAM cmdSize %d pCmdData %p, *replySize %" PRIu32 ", pReplyData %p", cmdSize, pCmdData, *replySize, pReplyData); if (pCmdData == NULL || (cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t))) || pReplyData == NULL || replySize == NULL || *replySize != (int)sizeof(int32_t)) { return -EINVAL; } effect_param_t *cmd = (effect_param_t *) pCmdData; *(int *)pReplyData = Downmix_setParameter(pDownmixer, *(int32_t *)cmd->data, cmd->vsize, cmd->data + sizeof(int32_t)); break; case EFFECT_CMD_SET_PARAM_DEFERRED: //FIXME implement ALOGW("Downmix_Command command EFFECT_CMD_SET_PARAM_DEFERRED not supported, FIXME"); break; case EFFECT_CMD_SET_PARAM_COMMIT: //FIXME implement ALOGW("Downmix_Command command EFFECT_CMD_SET_PARAM_COMMIT not supported, FIXME"); break; case EFFECT_CMD_ENABLE: if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { return -EINVAL; } if (pDownmixer->state != DOWNMIX_STATE_INITIALIZED) { return -ENOSYS; } pDownmixer->state = DOWNMIX_STATE_ACTIVE; ALOGV("EFFECT_CMD_ENABLE() OK"); *(int *)pReplyData = 0; break; case EFFECT_CMD_DISABLE: if (pReplyData == NULL || replySize == NULL || *replySize != sizeof(int)) { return -EINVAL; } if (pDownmixer->state != DOWNMIX_STATE_ACTIVE) { return -ENOSYS; } pDownmixer->state = DOWNMIX_STATE_INITIALIZED; ALOGV("EFFECT_CMD_DISABLE() OK"); *(int *)pReplyData = 0; break; case EFFECT_CMD_SET_DEVICE: if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t)) { return -EINVAL; } // FIXME change type if playing on headset vs speaker ALOGV("Downmix_Command EFFECT_CMD_SET_DEVICE: 0x%08" PRIx32, *(uint32_t *)pCmdData); break; case EFFECT_CMD_SET_VOLUME: { // audio output is always stereo => 2 channel volumes if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t) * 2) { return -EINVAL; } // FIXME change volume ALOGW("Downmix_Command command EFFECT_CMD_SET_VOLUME not supported, FIXME"); float left = (float)(*(uint32_t *)pCmdData) / (1 << 24); float right = (float)(*((uint32_t *)pCmdData + 1)) / (1 << 24); ALOGV("Downmix_Command EFFECT_CMD_SET_VOLUME: left %f, right %f ", left, right); break; } case EFFECT_CMD_SET_AUDIO_MODE: if (pCmdData == NULL || cmdSize != (int)sizeof(uint32_t)) { return -EINVAL; } ALOGV("Downmix_Command EFFECT_CMD_SET_AUDIO_MODE: %" PRIu32, *(uint32_t *)pCmdData); break; case EFFECT_CMD_SET_CONFIG_REVERSE: case EFFECT_CMD_SET_INPUT_DEVICE: // these commands are ignored by a downmix effect break; default: ALOGW("Downmix_Command invalid command %" PRIu32, cmdCode); return -EINVAL; } return 0; } int Downmix_GetDescriptor(effect_handle_t self, effect_descriptor_t *pDescriptor) { downmix_module_t *pDwnmxModule = (downmix_module_t *) self; if (pDwnmxModule == NULL || pDwnmxModule->context.state == DOWNMIX_STATE_UNINITIALIZED) { return -EINVAL; } memcpy(pDescriptor, &gDownmixDescriptor, sizeof(effect_descriptor_t)); return 0; } /*---------------------------------------------------------------------------- * Downmix internal functions *--------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------- * Downmix_Init() *---------------------------------------------------------------------------- * Purpose: * Initialize downmix context and apply default parameters * * Inputs: * pDwmModule pointer to downmix effect module * * Outputs: * * Returns: * 0 indicates success * * Side Effects: * updates: * pDwmModule->context.type * pDwmModule->context.apply_volume_correction * pDwmModule->config.inputCfg * pDwmModule->config.outputCfg * pDwmModule->config.inputCfg.samplingRate * pDwmModule->config.outputCfg.samplingRate * pDwmModule->context.state * doesn't set: * pDwmModule->itfe * *---------------------------------------------------------------------------- */ int Downmix_Init(downmix_module_t *pDwmModule) { ALOGV("Downmix_Init module %p", pDwmModule); int ret = 0; memset(&pDwmModule->context, 0, sizeof(downmix_object_t)); pDwmModule->config.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; pDwmModule->config.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT; pDwmModule->config.inputCfg.channels = AUDIO_CHANNEL_OUT_7POINT1; pDwmModule->config.inputCfg.bufferProvider.getBuffer = NULL; pDwmModule->config.inputCfg.bufferProvider.releaseBuffer = NULL; pDwmModule->config.inputCfg.bufferProvider.cookie = NULL; pDwmModule->config.inputCfg.mask = EFFECT_CONFIG_ALL; pDwmModule->config.inputCfg.samplingRate = 44100; pDwmModule->config.outputCfg.samplingRate = pDwmModule->config.inputCfg.samplingRate; // set a default value for the access mode, but should be overwritten by caller pDwmModule->config.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE; pDwmModule->config.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT; pDwmModule->config.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; pDwmModule->config.outputCfg.bufferProvider.getBuffer = NULL; pDwmModule->config.outputCfg.bufferProvider.releaseBuffer = NULL; pDwmModule->config.outputCfg.bufferProvider.cookie = NULL; pDwmModule->config.outputCfg.mask = EFFECT_CONFIG_ALL; ret = Downmix_Configure(pDwmModule, &pDwmModule->config, true); if (ret != 0) { ALOGV("Downmix_Init error %d on module %p", ret, pDwmModule); } else { pDwmModule->context.state = DOWNMIX_STATE_INITIALIZED; } return ret; } /*---------------------------------------------------------------------------- * Downmix_Configure() *---------------------------------------------------------------------------- * Purpose: * Set input and output audio configuration. * * Inputs: * pDwmModule pointer to downmix effect module * pConfig pointer to effect_config_t structure containing input * and output audio parameters configuration * init true if called from init function * * Outputs: * * Returns: * 0 indicates success * * Side Effects: * *---------------------------------------------------------------------------- */ int Downmix_Configure(downmix_module_t *pDwmModule, effect_config_t *pConfig, bool init) { downmix_object_t *pDownmixer = &pDwmModule->context; // Check configuration compatibility with build options, and effect capabilities if (pConfig->inputCfg.samplingRate != pConfig->outputCfg.samplingRate || pConfig->outputCfg.channels != DOWNMIX_OUTPUT_CHANNELS || pConfig->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT || pConfig->outputCfg.format != AUDIO_FORMAT_PCM_16_BIT) { ALOGE("Downmix_Configure error: invalid config"); return -EINVAL; } if (&pDwmModule->config != pConfig) { memcpy(&pDwmModule->config, pConfig, sizeof(effect_config_t)); } if (init) { pDownmixer->type = DOWNMIX_TYPE_FOLD; pDownmixer->apply_volume_correction = false; pDownmixer->input_channel_count = 8; // matches default input of AUDIO_CHANNEL_OUT_7POINT1 } else { // when configuring the effect, do not allow a blank channel mask if (pConfig->inputCfg.channels == 0) { ALOGE("Downmix_Configure error: input channel mask can't be 0"); return -EINVAL; } pDownmixer->input_channel_count = audio_channel_count_from_out_mask(pConfig->inputCfg.channels); } Downmix_Reset(pDownmixer, init); return 0; } /*---------------------------------------------------------------------------- * Downmix_Reset() *---------------------------------------------------------------------------- * Purpose: * Reset internal states. * * Inputs: * pDownmixer pointer to downmix context * init true if called from init function * * Outputs: * * Returns: * 0 indicates success * * Side Effects: * *---------------------------------------------------------------------------- */ int Downmix_Reset(downmix_object_t *pDownmixer __unused, bool init __unused) { // nothing to do here return 0; } /*---------------------------------------------------------------------------- * Downmix_setParameter() *---------------------------------------------------------------------------- * Purpose: * Set a Downmix parameter * * Inputs: * pDownmixer handle to instance data * param parameter * pValue pointer to parameter value * size value size * * Outputs: * * Returns: * 0 indicates success * * Side Effects: * *---------------------------------------------------------------------------- */ int Downmix_setParameter(downmix_object_t *pDownmixer, int32_t param, uint32_t size, void *pValue) { int16_t value16; ALOGV("Downmix_setParameter, context %p, param %" PRId32 ", value16 %" PRId16 ", value32 %" PRId32, pDownmixer, param, *(int16_t *)pValue, *(int32_t *)pValue); switch (param) { case DOWNMIX_PARAM_TYPE: if (size != sizeof(downmix_type_t)) { ALOGE("Downmix_setParameter(DOWNMIX_PARAM_TYPE) invalid size %" PRIu32 ", should be %zu", size, sizeof(downmix_type_t)); return -EINVAL; } value16 = *(int16_t *)pValue; ALOGV("set DOWNMIX_PARAM_TYPE, type %" PRId16, value16); if (!((value16 > DOWNMIX_TYPE_INVALID) && (value16 <= DOWNMIX_TYPE_LAST))) { ALOGE("Downmix_setParameter invalid DOWNMIX_PARAM_TYPE value %" PRId16, value16); return -EINVAL; } else { pDownmixer->type = (downmix_type_t) value16; break; default: ALOGE("Downmix_setParameter unknown parameter %" PRId32, param); return -EINVAL; } } return 0; } /* end Downmix_setParameter */ /*---------------------------------------------------------------------------- * Downmix_getParameter() *---------------------------------------------------------------------------- * Purpose: * Get a Downmix parameter * * Inputs: * pDownmixer handle to instance data * param parameter * pValue pointer to variable to hold retrieved value * pSize pointer to value size: maximum size as input * * Outputs: * *pValue updated with parameter value * *pSize updated with actual value size * * Returns: * 0 indicates success * * Side Effects: * *---------------------------------------------------------------------------- */ int Downmix_getParameter(downmix_object_t *pDownmixer, int32_t param, uint32_t *pSize, void *pValue) { int16_t *pValue16; switch (param) { case DOWNMIX_PARAM_TYPE: if (*pSize < sizeof(int16_t)) { ALOGE("Downmix_getParameter invalid parameter size %" PRIu32 " for DOWNMIX_PARAM_TYPE", *pSize); return -EINVAL; } pValue16 = (int16_t *)pValue; *pValue16 = (int16_t) pDownmixer->type; *pSize = sizeof(int16_t); ALOGV("Downmix_getParameter DOWNMIX_PARAM_TYPE is %" PRId16, *pValue16); break; default: ALOGE("Downmix_getParameter unknown parameter %" PRId16, param); return -EINVAL; } return 0; } /* end Downmix_getParameter */ /*---------------------------------------------------------------------------- * Downmix_foldFromQuad() *---------------------------------------------------------------------------- * Purpose: * downmix a quad signal to stereo * * Inputs: * pSrc quad audio samples to downmix * numFrames the number of quad frames to downmix * accumulate whether to mix (when true) the result of the downmix with the contents of pDst, * or overwrite pDst (when false) * * Outputs: * pDst downmixed stereo audio samples * *---------------------------------------------------------------------------- */ void Downmix_foldFromQuad(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { // sample at index 0 is FL // sample at index 1 is FR // sample at index 2 is RL // sample at index 3 is RR if (accumulate) { while (numFrames) { // FL + RL pDst[0] = clamp16(pDst[0] + ((pSrc[0] + pSrc[2]) >> 1)); // FR + RR pDst[1] = clamp16(pDst[1] + ((pSrc[1] + pSrc[3]) >> 1)); pSrc += 4; pDst += 2; numFrames--; } } else { // same code as above but without adding and clamping pDst[i] to itself while (numFrames) { // FL + RL pDst[0] = clamp16((pSrc[0] + pSrc[2]) >> 1); // FR + RR pDst[1] = clamp16((pSrc[1] + pSrc[3]) >> 1); pSrc += 4; pDst += 2; numFrames--; } } } /*---------------------------------------------------------------------------- * Downmix_foldFrom5Point1() *---------------------------------------------------------------------------- * Purpose: * downmix a 5.1 signal to stereo * * Inputs: * pSrc 5.1 audio samples to downmix * numFrames the number of 5.1 frames to downmix * accumulate whether to mix (when true) the result of the downmix with the contents of pDst, * or overwrite pDst (when false) * * Outputs: * pDst downmixed stereo audio samples * *---------------------------------------------------------------------------- */ void Downmix_foldFrom5Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { int32_t lt, rt, centerPlusLfeContrib; // samples in Q19.12 format // sample at index 0 is FL // sample at index 1 is FR // sample at index 2 is FC // sample at index 3 is LFE // sample at index 4 is RL // sample at index 5 is RR // code is mostly duplicated between the two values of accumulate to avoid repeating the test // for every sample if (accumulate) { while (numFrames) { // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12); // FL + centerPlusLfeContrib + RL lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[4] << 12); // FR + centerPlusLfeContrib + RR rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[5] << 12); // accumulate in destination pDst[0] = clamp16(pDst[0] + (lt >> 13)); pDst[1] = clamp16(pDst[1] + (rt >> 13)); pSrc += 6; pDst += 2; numFrames--; } } else { // same code as above but without adding and clamping pDst[i] to itself while (numFrames) { // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12); // FL + centerPlusLfeContrib + RL lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[4] << 12); // FR + centerPlusLfeContrib + RR rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[5] << 12); // store in destination pDst[0] = clamp16(lt >> 13); // differs from when accumulate is true above pDst[1] = clamp16(rt >> 13); // differs from when accumulate is true above pSrc += 6; pDst += 2; numFrames--; } } } /*---------------------------------------------------------------------------- * Downmix_foldFrom7Point1() *---------------------------------------------------------------------------- * Purpose: * downmix a 7.1 signal to stereo * * Inputs: * pSrc 7.1 audio samples to downmix * numFrames the number of 7.1 frames to downmix * accumulate whether to mix (when true) the result of the downmix with the contents of pDst, * or overwrite pDst (when false) * * Outputs: * pDst downmixed stereo audio samples * *---------------------------------------------------------------------------- */ void Downmix_foldFrom7Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { int32_t lt, rt, centerPlusLfeContrib; // samples in Q19.12 format // sample at index 0 is FL // sample at index 1 is FR // sample at index 2 is FC // sample at index 3 is LFE // sample at index 4 is RL // sample at index 5 is RR // sample at index 6 is SL // sample at index 7 is SR // code is mostly duplicated between the two values of accumulate to avoid repeating the test // for every sample if (accumulate) { while (numFrames) { // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12); // FL + centerPlusLfeContrib + SL + RL lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[6] << 12) + (pSrc[4] << 12); // FR + centerPlusLfeContrib + SR + RR rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[7] << 12) + (pSrc[5] << 12); //accumulate in destination pDst[0] = clamp16(pDst[0] + (lt >> 13)); pDst[1] = clamp16(pDst[1] + (rt >> 13)); pSrc += 8; pDst += 2; numFrames--; } } else { // same code as above but without adding and clamping pDst[i] to itself while (numFrames) { // centerPlusLfeContrib = FC(-3dB) + LFE(-3dB) centerPlusLfeContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12); // FL + centerPlusLfeContrib + SL + RL lt = (pSrc[0] << 12) + centerPlusLfeContrib + (pSrc[6] << 12) + (pSrc[4] << 12); // FR + centerPlusLfeContrib + SR + RR rt = (pSrc[1] << 12) + centerPlusLfeContrib + (pSrc[7] << 12) + (pSrc[5] << 12); // store in destination pDst[0] = clamp16(lt >> 13); // differs from when accumulate is true above pDst[1] = clamp16(rt >> 13); // differs from when accumulate is true above pSrc += 8; pDst += 2; numFrames--; } } } /*---------------------------------------------------------------------------- * Downmix_foldGeneric() *---------------------------------------------------------------------------- * Purpose: * downmix to stereo a multichannel signal whose format is: * - has FL/FR * - if using AUDIO_CHANNEL_OUT_SIDE*, it contains both left and right * - if using AUDIO_CHANNEL_OUT_BACK*, it contains both left and right * - doesn't use any of the AUDIO_CHANNEL_OUT_TOP* channels * - doesn't use any of the AUDIO_CHANNEL_OUT_FRONT_*_OF_CENTER channels * Only handles channel masks not enumerated in downmix_input_channel_mask_t * * Inputs: * mask the channel mask of pSrc * pSrc multichannel audio buffer to downmix * numFrames the number of multichannel frames to downmix * accumulate whether to mix (when true) the result of the downmix with the contents of pDst, * or overwrite pDst (when false) * * Outputs: * pDst downmixed stereo audio samples * * Returns: false if multichannel format is not supported * *---------------------------------------------------------------------------- */ bool Downmix_foldGeneric( uint32_t mask, int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) { // check against unsupported channels if (mask & kUnsupported) { ALOGE("Unsupported channels (top or front left/right of center)"); return false; } // verify has FL/FR if ((mask & AUDIO_CHANNEL_OUT_STEREO) != AUDIO_CHANNEL_OUT_STEREO) { ALOGE("Front channels must be present"); return false; } // verify uses SIDE as a pair (ok if not using SIDE at all) bool hasSides = false; if ((mask & kSides) != 0) { if ((mask & kSides) != kSides) { ALOGE("Side channels must be used as a pair"); return false; } hasSides = true; } // verify uses BACK as a pair (ok if not using BACK at all) bool hasBacks = false; if ((mask & kBacks) != 0) { if ((mask & kBacks) != kBacks) { ALOGE("Back channels must be used as a pair"); return false; } hasBacks = true; } const int numChan = audio_channel_count_from_out_mask(mask); const bool hasFC = ((mask & AUDIO_CHANNEL_OUT_FRONT_CENTER) == AUDIO_CHANNEL_OUT_FRONT_CENTER); const bool hasLFE = ((mask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY) == AUDIO_CHANNEL_OUT_LOW_FREQUENCY); const bool hasBC = ((mask & AUDIO_CHANNEL_OUT_BACK_CENTER) == AUDIO_CHANNEL_OUT_BACK_CENTER); // compute at what index each channel is: samples will be in the following order: // FL FR FC LFE BL BR BC SL SR // when a channel is not present, its index is set to the same as the index of the preceding // channel const int indexFC = hasFC ? 2 : 1; // front center const int indexLFE = hasLFE ? indexFC + 1 : indexFC; // low frequency const int indexBL = hasBacks ? indexLFE + 1 : indexLFE; // back left const int indexBR = hasBacks ? indexBL + 1 : indexBL; // back right const int indexBC = hasBC ? indexBR + 1 : indexBR; // back center const int indexSL = hasSides ? indexBC + 1 : indexBC; // side left const int indexSR = hasSides ? indexSL + 1 : indexSL; // side right int32_t lt, rt, centersLfeContrib; // samples in Q19.12 format // code is mostly duplicated between the two values of accumulate to avoid repeating the test // for every sample if (accumulate) { while (numFrames) { // compute contribution of FC, BC and LFE centersLfeContrib = 0; if (hasFC) { centersLfeContrib += pSrc[indexFC]; } if (hasLFE) { centersLfeContrib += pSrc[indexLFE]; } if (hasBC) { centersLfeContrib += pSrc[indexBC]; } centersLfeContrib *= MINUS_3_DB_IN_Q19_12; // always has FL/FR lt = (pSrc[0] << 12); rt = (pSrc[1] << 12); // mix in sides and backs if (hasSides) { lt += pSrc[indexSL] << 12; rt += pSrc[indexSR] << 12; } if (hasBacks) { lt += pSrc[indexBL] << 12; rt += pSrc[indexBR] << 12; } lt += centersLfeContrib; rt += centersLfeContrib; // accumulate in destination pDst[0] = clamp16(pDst[0] + (lt >> 13)); pDst[1] = clamp16(pDst[1] + (rt >> 13)); pSrc += numChan; pDst += 2; numFrames--; } } else { while (numFrames) { // compute contribution of FC, BC and LFE centersLfeContrib = 0; if (hasFC) { centersLfeContrib += pSrc[indexFC]; } if (hasLFE) { centersLfeContrib += pSrc[indexLFE]; } if (hasBC) { centersLfeContrib += pSrc[indexBC]; } centersLfeContrib *= MINUS_3_DB_IN_Q19_12; // always has FL/FR lt = (pSrc[0] << 12); rt = (pSrc[1] << 12); // mix in sides and backs if (hasSides) { lt += pSrc[indexSL] << 12; rt += pSrc[indexSR] << 12; } if (hasBacks) { lt += pSrc[indexBL] << 12; rt += pSrc[indexBR] << 12; } lt += centersLfeContrib; rt += centersLfeContrib; // store in destination pDst[0] = clamp16(lt >> 13); // differs from when accumulate is true above pDst[1] = clamp16(rt >> 13); // differs from when accumulate is true above pSrc += numChan; pDst += 2; numFrames--; } } return true; }