/* * 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 #include "EffectDownmix.h" #include // 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_FLOAT M_SQRT1_2 // -3dB = 0.70710678 typedef enum { DOWNMIX_STATE_UNINITIALIZED, DOWNMIX_STATE_INITIALIZED, DOWNMIX_STATE_ACTIVE, } downmix_state_t; /* parameters for each downmixer */ typedef struct { downmix_state_t state; downmix_type_t type; bool apply_volume_correction; uint8_t input_channel_count; } downmix_object_t; typedef struct downmix_module_s { const struct effect_interface_s *itfe; effect_config_t config; downmix_object_t context; } downmix_module_t; // Audio Effect API static int32_t DownmixLib_Create(const effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, effect_handle_t *pHandle); static int32_t DownmixLib_Release(effect_handle_t handle); static int32_t DownmixLib_GetDescriptor(const effect_uuid_t *uuid, effect_descriptor_t *pDescriptor); static int32_t Downmix_Process(effect_handle_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer); static int32_t Downmix_Command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, void *pCmdData, uint32_t *replySize, void *pReplyData); static int32_t Downmix_GetDescriptor(effect_handle_t self, effect_descriptor_t *pDescriptor); // Internal methods static int Downmix_Init(downmix_module_t *pDwmModule); static int Downmix_Configure(downmix_module_t *pDwmModule, effect_config_t *pConfig, bool init); static int Downmix_Reset(downmix_object_t *pDownmixer, bool init); static int Downmix_setParameter( downmix_object_t *pDownmixer, int32_t param, uint32_t size, void *pValue); static int Downmix_getParameter( downmix_object_t *pDownmixer, int32_t param, uint32_t *pSize, void *pValue); static void Downmix_foldFromQuad(float *pSrc, float *pDst, size_t numFrames, bool accumulate); static void Downmix_foldFrom5Point1(float *pSrc, float *pDst, size_t numFrames, bool accumulate); static void Downmix_foldFrom7Point1(float *pSrc, float *pDst, size_t numFrames, bool accumulate); static bool Downmix_foldGeneric( uint32_t mask, float *pSrc, float *pDst, size_t numFrames, bool accumulate); // 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 *); static inline float clamp_float(float value) { return fmin(fmax(value, -1.f), 1.f); } /*---------------------------------------------------------------------------- * 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 %#x:", 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 static bool Downmix_validChannelMask(uint32_t mask) { if (!mask) { return false; } // check against unsupported channels if (mask & ~AUDIO_CHANNEL_OUT_22POINT2) { ALOGE("Unsupported channels in %u", mask & ~AUDIO_CHANNEL_OUT_22POINT2); return false; } return true; } /*---------------------------------------------------------------------------- * Effect API implementation *--------------------------------------------------------------------------*/ /*--- Effect Library Interface Implementation ---*/ static int32_t DownmixLib_Create(const effect_uuid_t *uuid, int32_t /* sessionId */, int32_t /* ioId */, 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(AUDIO_CHANNEL_OUT_QUAD_SIDE | AUDIO_CHANNEL_OUT_QUAD_BACK); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_5POINT1_SIDE | AUDIO_CHANNEL_OUT_BACK_CENTER); Downmix_testIndexComputation(AUDIO_CHANNEL_OUT_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 = new 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; } static 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; delete pDwmModule; return 0; } static 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 = %#x", i, gDescriptors[i]->uuid.timeLow); return 0; } } return -EINVAL; } /*--- Effect Control Interface Implementation ---*/ static int32_t Downmix_Process(effect_handle_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) { downmix_object_t *pDownmixer; float *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->f32; pDst = outBuffer->f32; 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] = clamp_float(pDst[0] + pSrc[0]); pDst[1] = clamp_float(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 %#x is not supported", downmixInputChannelMask); return -EINVAL; } break; #endif // optimize for the common formats switch (downmixInputChannelMask) { case AUDIO_CHANNEL_OUT_QUAD_BACK: case AUDIO_CHANNEL_OUT_QUAD_SIDE: Downmix_foldFromQuad(pSrc, pDst, numFrames, accumulate); break; case AUDIO_CHANNEL_OUT_5POINT1_BACK: case AUDIO_CHANNEL_OUT_5POINT1_SIDE: Downmix_foldFrom5Point1(pSrc, pDst, numFrames, accumulate); break; case AUDIO_CHANNEL_OUT_7POINT1: Downmix_foldFrom7Point1(pSrc, pDst, numFrames, accumulate); break; default: if (!Downmix_foldGeneric( downmixInputChannelMask, pSrc, pDst, numFrames, accumulate)) { ALOGE("Multichannel configuration %#x is not supported", downmixInputChannelMask); return -EINVAL; } break; } break; default: return -EINVAL; } return 0; } static int32_t 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; if (pDwmModule == NULL || pDwmModule->context.state == DOWNMIX_STATE_UNINITIALIZED) { return -EINVAL; } pDownmixer = (downmix_object_t*) &pDwmModule->context; ALOGV("Downmix_Command command %u cmdSize %u", 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 %u, 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 %d, replySize %u", *(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 %u" ", 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; if (cmd->psize != sizeof(int32_t)) { android_errorWriteLog(0x534e4554, "63662938"); return -EINVAL; } *(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: %#x", *(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: %u", *(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 %u", cmdCode); return -EINVAL; } return 0; } static int32_t 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 * *---------------------------------------------------------------------------- */ static 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_FLOAT; 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_FLOAT; 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: * *---------------------------------------------------------------------------- */ static 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 != AUDIO_CHANNEL_OUT_STEREO || pConfig->inputCfg.format != AUDIO_FORMAT_PCM_FLOAT || pConfig->outputCfg.format != AUDIO_FORMAT_PCM_FLOAT) { 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 or unsupported channel mask if (!Downmix_validChannelMask(pConfig->inputCfg.channels)) { ALOGE("Downmix_Configure error: input channel mask(0x%x) not supported", pConfig->inputCfg.channels); 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: * *---------------------------------------------------------------------------- */ static int Downmix_Reset(downmix_object_t* /* pDownmixer */, bool /* init */) { // 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: * *---------------------------------------------------------------------------- */ static int Downmix_setParameter( downmix_object_t *pDownmixer, int32_t param, uint32_t size, void *pValue) { int16_t value16; ALOGV("Downmix_setParameter, context %p, param %d, value16 %d, value32 %d", 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 %u, should be %zu", size, sizeof(downmix_type_t)); return -EINVAL; } value16 = *(int16_t *)pValue; ALOGV("set DOWNMIX_PARAM_TYPE, type %d", value16); if (!((value16 > DOWNMIX_TYPE_INVALID) && (value16 <= DOWNMIX_TYPE_LAST))) { ALOGE("Downmix_setParameter invalid DOWNMIX_PARAM_TYPE value %d", value16); return -EINVAL; } else { pDownmixer->type = (downmix_type_t) value16; break; default: ALOGE("Downmix_setParameter unknown parameter %d", 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: * *---------------------------------------------------------------------------- */ static 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 %u 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 %d", *pValue16); break; default: ALOGE("Downmix_getParameter unknown parameter %d", 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(float *pSrc, float *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] = clamp_float(pDst[0] + ((pSrc[0] + pSrc[2]) / 2.0f)); // FR + RR pDst[1] = clamp_float(pDst[1] + ((pSrc[1] + pSrc[3]) / 2.0f)); 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] = clamp_float((pSrc[0] + pSrc[2]) / 2.0f); // FR + RR pDst[1] = clamp_float((pSrc[1] + pSrc[3]) / 2.0f); 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(float *pSrc, float *pDst, size_t numFrames, bool accumulate) { float 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_FLOAT) + (pSrc[3] * MINUS_3_DB_IN_FLOAT); // FL + centerPlusLfeContrib + RL lt = pSrc[0] + centerPlusLfeContrib + pSrc[4]; // FR + centerPlusLfeContrib + RR rt = pSrc[1] + centerPlusLfeContrib + pSrc[5]; // accumulate in destination pDst[0] = clamp_float(pDst[0] + (lt / 2.0f)); pDst[1] = clamp_float(pDst[1] + (rt / 2.0f)); 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_FLOAT) + (pSrc[3] * MINUS_3_DB_IN_FLOAT); // FL + centerPlusLfeContrib + RL lt = pSrc[0] + centerPlusLfeContrib + pSrc[4]; // FR + centerPlusLfeContrib + RR rt = pSrc[1] + centerPlusLfeContrib + pSrc[5]; // store in destination pDst[0] = clamp_float(lt / 2.0f); // differs from when accumulate is true above pDst[1] = clamp_float(rt / 2.0f); // 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(float *pSrc, float *pDst, size_t numFrames, bool accumulate) { float 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_FLOAT) + (pSrc[3] * MINUS_3_DB_IN_FLOAT); // FL + centerPlusLfeContrib + SL + RL lt = pSrc[0] + centerPlusLfeContrib + pSrc[6] + pSrc[4]; // FR + centerPlusLfeContrib + SR + RR rt = pSrc[1] + centerPlusLfeContrib + pSrc[7] + pSrc[5]; //accumulate in destination pDst[0] = clamp_float(pDst[0] + (lt / 2.0f)); pDst[1] = clamp_float(pDst[1] + (rt / 2.0f)); 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_FLOAT) + (pSrc[3] * MINUS_3_DB_IN_FLOAT); // FL + centerPlusLfeContrib + SL + RL lt = pSrc[0] + centerPlusLfeContrib + pSrc[6] + pSrc[4]; // FR + centerPlusLfeContrib + SR + RR rt = pSrc[1] + centerPlusLfeContrib + pSrc[7] + pSrc[5]; // store in destination pDst[0] = clamp_float(lt / 2.0f); // differs from when accumulate is true above pDst[1] = clamp_float(rt / 2.0f); // differs from when accumulate is true above pSrc += 8; pDst += 2; numFrames--; } } } /*---------------------------------------------------------------------------- * Downmix_foldGeneric() *---------------------------------------------------------------------------- * Purpose: * downmix to stereo a multichannel signal of arbitrary channel position mask. * * 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, float *pSrc, float *pDst, size_t numFrames, bool accumulate) { if (!Downmix_validChannelMask(mask)) { return false; } const int numChan = audio_channel_count_from_out_mask(mask); // compute at what index each channel is: samples will be in the following order: // FL FR FC LFE BL BR BC SL SR // // (transfer matrix) // FL FR FC LFE BL BR BC SL SR // 0.5 0.353 0.353 0.5 0.353 0.5 // 0.5 0.353 0.353 0.5 0.353 0.5 // derive the indices for the transfer matrix columns that have non-zero values. int indexFL = -1; int indexFR = -1; int indexFC = -1; int indexLFE = -1; int indexBL = -1; int indexBR = -1; int indexBC = -1; int indexSL = -1; int indexSR = -1; int index = 0; for (unsigned tmp = mask; (tmp & (AUDIO_CHANNEL_OUT_7POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER)) != 0; ++index) { const unsigned lowestBit = tmp & -(signed)tmp; switch (lowestBit) { case AUDIO_CHANNEL_OUT_FRONT_LEFT: indexFL = index; break; case AUDIO_CHANNEL_OUT_FRONT_RIGHT: indexFR = index; break; case AUDIO_CHANNEL_OUT_FRONT_CENTER: indexFC = index; break; case AUDIO_CHANNEL_OUT_LOW_FREQUENCY: indexLFE = index; break; case AUDIO_CHANNEL_OUT_BACK_LEFT: indexBL = index; break; case AUDIO_CHANNEL_OUT_BACK_RIGHT: indexBR = index; break; case AUDIO_CHANNEL_OUT_BACK_CENTER: indexBC = index; break; case AUDIO_CHANNEL_OUT_SIDE_LEFT: indexSL = index; break; case AUDIO_CHANNEL_OUT_SIDE_RIGHT: indexSR = index; break; } tmp ^= lowestBit; } // With good branch prediction, this should run reasonably fast. // Also consider using a transfer matrix form. while (numFrames) { // compute contribution of FC, BC and LFE float centersLfeContrib = 0; if (indexFC >= 0) centersLfeContrib = pSrc[indexFC]; if (indexLFE >= 0) centersLfeContrib += pSrc[indexLFE]; if (indexBC >= 0) centersLfeContrib += pSrc[indexBC]; centersLfeContrib *= MINUS_3_DB_IN_FLOAT; float ch[2]; ch[0] = centersLfeContrib; ch[1] = centersLfeContrib; // mix in left / right channels if (indexFL >= 0) ch[0] += pSrc[indexFL]; if (indexFR >= 0) ch[1] += pSrc[indexFR]; if (indexSL >= 0) ch[0] += pSrc[indexSL]; if (indexSR >= 0) ch[1] += pSrc[indexSR]; // note pair checks enforce this if indexSL != 0 if (indexBL >= 0) ch[0] += pSrc[indexBL]; if (indexBR >= 0) ch[1] += pSrc[indexBR]; // note pair checks enforce this if indexBL != 0 // scale to prevent overflow. ch[0] *= 0.5f; ch[1] *= 0.5f; if (accumulate) { ch[0] += pDst[0]; ch[1] += pDst[1]; } pDst[0] = clamp_float(ch[0]); pDst[1] = clamp_float(ch[1]); pSrc += numChan; pDst += 2; numFrames--; } return true; }