summaryrefslogtreecommitdiffstats
path: root/media/libeffects/downmix
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2012-03-02 10:59:56 -0800
committerJean-Michel Trivi <jmtrivi@google.com>2012-03-07 10:17:19 -0800
commit04c1e531b5913c09aa9b2e59e2b8ed9b4d8a4cba (patch)
tree643a1bd10a8e4d0e9a208a3987d41459d6db49d1 /media/libeffects/downmix
parenteb7de4504bdb3a0182021ca6f022cccca01dbf1e (diff)
downloadframeworks_av-04c1e531b5913c09aa9b2e59e2b8ed9b4d8a4cba.zip
frameworks_av-04c1e531b5913c09aa9b2e59e2b8ed9b4d8a4cba.tar.gz
frameworks_av-04c1e531b5913c09aa9b2e59e2b8ed9b4d8a4cba.tar.bz2
Effect for multichannel PCM downmix to stereo
First pass at implementing an audio effect whose role is to downmix multichannel PCM buffers to stereo. The effect is not handling volume changes. The effect code here handles quad, 4.0, 5.1 and 7.1 input configurations, to optimize the most commom configurations, and does not yet handle generic multichanel configurations. Change-Id: I74d04bd961348f3f0e4ae7714b70e620808a0829
Diffstat (limited to 'media/libeffects/downmix')
-rw-r--r--media/libeffects/downmix/Android.mk28
-rw-r--r--media/libeffects/downmix/EffectDownmix.c889
-rw-r--r--media/libeffects/downmix/EffectDownmix.h96
3 files changed, 1013 insertions, 0 deletions
diff --git a/media/libeffects/downmix/Android.mk b/media/libeffects/downmix/Android.mk
new file mode 100644
index 0000000..0348e1e
--- /dev/null
+++ b/media/libeffects/downmix/Android.mk
@@ -0,0 +1,28 @@
+LOCAL_PATH:= $(call my-dir)
+
+# Multichannel downmix effect library
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ EffectDownmix.c
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils
+
+LOCAL_MODULE:= libdownmix
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx
+
+ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true)
+LOCAL_LDLIBS += -ldl
+endif
+
+LOCAL_C_INCLUDES := \
+ system/media/audio_effects/include \
+ system/media/audio_utils/include
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libeffects/downmix/EffectDownmix.c b/media/libeffects/downmix/EffectDownmix.c
new file mode 100644
index 0000000..a325172
--- /dev/null
+++ b/media/libeffects/downmix/EffectDownmix.c
@@ -0,0 +1,889 @@
+/*
+ * 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 <cutils/log.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "EffectDownmix.h"
+
+#define MINUS_3_DB_IN_Q19_12 2896 // -3dB = 0.707 * 2^12 = 2896
+
+// 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 */
+};
+
+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",
+ query_num_effects : DownmixLib_QueryNumberEffects,
+ query_effect : DownmixLib_QueryEffect,
+ 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 *);
+
+
+/*----------------------------------------------------------------------------
+ * Effect API implementation
+ *--------------------------------------------------------------------------*/
+
+/*--- Effect Library Interface Implementation ---*/
+
+int32_t DownmixLib_QueryNumberEffects(uint32_t *pNumEffects) {
+ ALOGV("DownmixLib_QueryNumberEffects()");
+ *pNumEffects = kNbEffects;
+ return 0;
+}
+
+int32_t DownmixLib_QueryEffect(uint32_t index, effect_descriptor_t *pDescriptor) {
+ ALOGV("DownmixLib_QueryEffect() index=%d", index);
+ if (pDescriptor == NULL) {
+ return -EINVAL;
+ }
+ if (index >= (uint32_t)kNbEffects) {
+ return -EINVAL;
+ }
+ memcpy(pDescriptor, gDescriptors[index], sizeof(effect_descriptor_t));
+ return 0;
+}
+
+
+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()");
+
+ 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 %d", 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 = %x",
+ 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);
+
+ 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:
+ // optimize for the common formats
+ switch(pDwmModule->config.inputCfg.channels) {
+ case AUDIO_CHANNEL_OUT_QUAD:
+ Downmix_foldFromQuad(pSrc, pDst, numFrames, accumulate);
+ break;
+ case AUDIO_CHANNEL_OUT_SURROUND:
+ Downmix_foldFromSurround(pSrc, pDst, numFrames, accumulate);
+ break;
+ case AUDIO_CHANNEL_OUT_5POINT1:
+ Downmix_foldFrom5Point1(pSrc, pDst, numFrames, accumulate);
+ break;
+ case AUDIO_CHANNEL_OUT_7POINT1:
+ Downmix_foldFrom7Point1(pSrc, pDst, numFrames, accumulate);
+ break;
+ default:
+ // FIXME implement generic downmix
+ ALOGE("Multichannel configurations other than quad, 4.0, 5.1 and 7.1 are not supported");
+ 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 %d cmdSize %d",cmdCode, cmdSize);
+
+ switch (cmdCode) {
+ case EFFECT_CMD_INIT:
+ if (pReplyData == 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 != 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 %d, pReplyData: %p",
+ pCmdData, *replySize, pReplyData);
+ if (pCmdData == NULL || cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t)) ||
+ pReplyData == 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 %d",
+ *(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 %d, " \
+ "pReplyData %p", cmdSize, pCmdData, *replySize, pReplyData);
+ if (pCmdData == NULL || (cmdSize < (int)(sizeof(effect_param_t) + sizeof(int32_t)))
+ || pReplyData == 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 != 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 != 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%08x", *(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: %d", *(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 %d",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;
+ }
+
+ 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 = popcount(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, 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:
+ *
+ *----------------------------------------------------------------------------
+ */
+int Downmix_setParameter(downmix_object_t *pDownmixer, int32_t param, size_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 %d, should be %d",
+ 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:
+ *
+ *----------------------------------------------------------------------------
+ */
+int Downmix_getParameter(downmix_object_t *pDownmixer, int32_t param, size_t *pSize, void *pValue) {
+ int16_t *pValue16;
+
+ switch (param) {
+
+ case DOWNMIX_PARAM_TYPE:
+ if (*pSize < sizeof(int16_t)) {
+ ALOGE("Downmix_getParameter invalid parameter size %d 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
+ *
+ * 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]);
+ // FR + RR
+ pDst[1] = clamp16(pDst[1] + pSrc[1] + pSrc[3]);
+ 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]);
+ // FR + RR
+ pDst[1] = clamp16(pSrc[1] + pSrc[3]);
+ pSrc += 4;
+ pDst += 2;
+ numFrames--;
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------------
+ * Downmix_foldFromSurround()
+ *----------------------------------------------------------------------------
+ * Purpose:
+ * downmix a "surround sound" (mono rear) signal to stereo
+ *
+ * Inputs:
+ * pSrc surround signal to downmix
+ * numFrames the number of surround frames to downmix
+ *
+ * Outputs:
+ * pDst downmixed stereo audio samples
+ *
+ *----------------------------------------------------------------------------
+ */
+void Downmix_foldFromSurround(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate) {
+ int32_t lt, rt, centerPlusRearContrib; // 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 RC
+ if (accumulate) {
+ while (numFrames) {
+ // centerPlusRearContrib = FC(-3dB) + RC(-3dB)
+ centerPlusRearContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12);
+ // FL + centerPlusRearContrib
+ lt = (pSrc[0] << 12) + centerPlusRearContrib;
+ // FR + centerPlusRearContrib
+ rt = (pSrc[1] << 12) + centerPlusRearContrib;
+ pDst[0] = clamp16(pDst[0] + (lt >> 12));
+ pDst[1] = clamp16(pDst[1] + (rt >> 12));
+ pSrc += 4;
+ pDst += 2;
+ numFrames--;
+ }
+ } else { // same code as above but without adding and clamping pDst[i] to itself
+ while (numFrames) {
+ // centerPlusRearContrib = FC(-3dB) + RC(-3dB)
+ centerPlusRearContrib = (pSrc[2] * MINUS_3_DB_IN_Q19_12) + (pSrc[3] * MINUS_3_DB_IN_Q19_12);
+ // FL + centerPlusRearContrib
+ lt = (pSrc[0] << 12) + centerPlusRearContrib;
+ // FR + centerPlusRearContrib
+ rt = (pSrc[1] << 12) + centerPlusRearContrib;
+ pDst[0] = clamp16(lt >> 12);
+ pDst[1] = clamp16(rt >> 12);
+ 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
+ *
+ * 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
+ 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);
+ pDst[0] = clamp16(pDst[0] + (lt >> 12));
+ pDst[1] = clamp16(pDst[1] + (rt >> 12));
+ 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);
+ pDst[0] = clamp16(lt >> 12);
+ pDst[1] = clamp16(rt >> 12);
+ 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
+ *
+ * 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
+ 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);
+ pDst[0] = clamp16(lt >> 12);
+ pDst[1] = clamp16(rt >> 12);
+ 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);
+ pDst[0] = clamp16(pDst[0] + (lt >> 12));
+ pDst[1] = clamp16(pDst[1] + (rt >> 12));
+ pSrc += 8;
+ pDst += 2;
+ numFrames--;
+ }
+ }
+}
+
diff --git a/media/libeffects/downmix/EffectDownmix.h b/media/libeffects/downmix/EffectDownmix.h
new file mode 100644
index 0000000..4176b5a
--- /dev/null
+++ b/media/libeffects/downmix/EffectDownmix.h
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_EFFECTDOWNMIX_H_
+#define ANDROID_EFFECTDOWNMIX_H_
+
+#include <audio_effects/effect_downmix.h>
+#include <audio_utils/primitives.h>
+#include <system/audio.h>
+
+/*------------------------------------
+ * definitions
+ *------------------------------------
+*/
+
+#define DOWNMIX_OUTPUT_CHANNELS AUDIO_CHANNEL_OUT_STEREO
+
+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;
+
+
+/*------------------------------------
+ * Effect API
+ *------------------------------------
+*/
+int32_t DownmixLib_QueryNumberEffects(uint32_t *pNumEffects);
+int32_t DownmixLib_QueryEffect(uint32_t index,
+ effect_descriptor_t *pDescriptor);
+int32_t DownmixLib_Create(const effect_uuid_t *uuid,
+ int32_t sessionId,
+ int32_t ioId,
+ effect_handle_t *pHandle);
+int32_t DownmixLib_Release(effect_handle_t handle);
+int32_t DownmixLib_GetDescriptor(const effect_uuid_t *uuid,
+ effect_descriptor_t *pDescriptor);
+
+static int Downmix_Process(effect_handle_t self,
+ audio_buffer_t *inBuffer,
+ audio_buffer_t *outBuffer);
+static int Downmix_Command(effect_handle_t self,
+ uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t *replySize,
+ void *pReplyData);
+static int Downmix_GetDescriptor(effect_handle_t self,
+ effect_descriptor_t *pDescriptor);
+
+
+/*------------------------------------
+ * internal functions
+ *------------------------------------
+*/
+int Downmix_Init(downmix_module_t *pDwmModule);
+int Downmix_Configure(downmix_module_t *pDwmModule, effect_config_t *pConfig, bool init);
+int Downmix_Reset(downmix_object_t *pDownmixer, bool init);
+int Downmix_setParameter(downmix_object_t *pDownmixer, int32_t param, size_t size, void *pValue);
+int Downmix_getParameter(downmix_object_t *pDownmixer, int32_t param, size_t *pSize, void *pValue);
+
+void Downmix_foldFromQuad(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate);
+void Downmix_foldFromSurround(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate);
+void Downmix_foldFrom5Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate);
+void Downmix_foldFrom7Point1(int16_t *pSrc, int16_t*pDst, size_t numFrames, bool accumulate);
+
+#endif /*ANDROID_EFFECTDOWNMIX_H_*/