diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2013-11-22 10:35:20 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2013-11-22 10:35:20 -0800 |
commit | 5bf2560ce9b70bee077e0c264ac06648f0f63acc (patch) | |
tree | f17ecec5321e8d583d045135f29f14f3c7418e71 /media/libeffects | |
parent | eb76f318e9daf91dbf195bcb74852b3bd736a32a (diff) | |
parent | b2059ff384eee8ffb70a7ec8fc5570405201c734 (diff) | |
download | frameworks_av-5bf2560ce9b70bee077e0c264ac06648f0f63acc.zip frameworks_av-5bf2560ce9b70bee077e0c264ac06648f0f63acc.tar.gz frameworks_av-5bf2560ce9b70bee077e0c264ac06648f0f63acc.tar.bz2 |
Merge commit 'b2059ff384eee8ffb70a7ec8fc5570405201c734' into HEAD
Diffstat (limited to 'media/libeffects')
27 files changed, 2768 insertions, 23 deletions
diff --git a/media/libeffects/data/audio_effects.conf b/media/libeffects/data/audio_effects.conf index 93f27cb..c3c4b67 100644 --- a/media/libeffects/data/audio_effects.conf +++ b/media/libeffects/data/audio_effects.conf @@ -6,6 +6,23 @@ # } # } libraries { +# This is a proxy library that will be an abstraction for +# the HW and SW effects + + #proxy { + #path /system/lib/soundfx/libeffectproxy.so + #} + +# This is the SW implementation library of the effect + #libSW { + #path /system/lib/soundfx/libswwrapper.so + #} + +# This is the HW implementation library for the effect + #libHW { + #path /system/lib/soundfx/libhwwrapper.so + #} + bundle { path /system/lib/soundfx/libbundlewrapper.so } @@ -18,6 +35,9 @@ libraries { downmix { path /system/lib/soundfx/libdownmix.so } + loudness_enhancer { + path /system/lib/soundfx/libldnhncr.so + } } # Default pre-processing library. Add to audio_effect.conf "libraries" section if @@ -43,6 +63,28 @@ libraries { # } effects { + +# additions for the proxy implementation +# Proxy implementation + #effectname { + #library proxy + #uuid xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + + # SW implemetation of the effect. Added as a node under the proxy to + # indicate this as a sub effect. + #libsw { + #library libSW + #uuid yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy + #} End of SW effect + + # HW implementation of the effect. Added as a node under the proxy to + # indicate this as a sub effect. + #libhw { + #library libHW + #uuid zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz + #}End of HW effect + #} End of effect proxy + bassboost { library bundle uuid 8631f300-72e2-11df-b57e-0002a5d5c51b @@ -83,6 +125,10 @@ effects { library downmix uuid 93f04452-e4fe-41cc-91f9-e475b6d1d69f } + loudness_enhancer { + library loudness_enhancer + uuid fa415329-2034-4bea-b5dc-5b381c8d1e2c + } } # Default pre-processing effects. Add to audio_effect.conf "effects" section if diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c index f158929..f8d6041 100644 --- a/media/libeffects/factory/EffectsFactory.c +++ b/media/libeffects/factory/EffectsFactory.c @@ -28,6 +28,9 @@ static list_elem_t *gEffectList; // list of effect_entry_t: all currently created effects static list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries +// list of effect_descriptor and list of sub effects : all currently loaded +// It does not contain effects without sub effects. +static list_sub_elem_t *gSubEffectList; static pthread_mutex_t gLibLock = PTHREAD_MUTEX_INITIALIZER; // controls access to gLibraryList static uint32_t gNumEffects; // total number number of effects static list_elem_t *gCurLib; // current library in enumeration process @@ -50,6 +53,8 @@ static int loadLibraries(cnode *root); static int loadLibrary(cnode *root, const char *name); static int loadEffects(cnode *root); static int loadEffect(cnode *node); +// To get and add the effect pointed by the passed node to the gSubEffectList +static int addSubEffect(cnode *root); static lib_entry_t *getLibrary(const char *path); static void resetEffectEnumeration(); static uint32_t updateNumEffects(); @@ -57,6 +62,10 @@ static int findEffect(const effect_uuid_t *type, const effect_uuid_t *uuid, lib_entry_t **lib, effect_descriptor_t **desc); +// To search a subeffect in the gSubEffectList +int findSubEffect(const effect_uuid_t *uuid, + lib_entry_t **lib, + effect_descriptor_t **desc); static void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len); static int stringToUuid(const char *str, effect_uuid_t *uuid); static int uuidToString(const effect_uuid_t *uuid, char *str, size_t maxLen); @@ -287,7 +296,12 @@ int EffectCreate(const effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, eff ret = findEffect(NULL, uuid, &l, &d); if (ret < 0){ - goto exit; + // Sub effects are not associated with the library->effects, + // so, findEffect will fail. Search for the effect in gSubEffectList. + ret = findSubEffect(uuid, &l, &d); + if (ret < 0 ) { + goto exit; + } } // create effect in library @@ -354,21 +368,27 @@ int EffectRelease(effect_handle_t handle) } if (e1 == NULL) { ret = -ENOENT; + pthread_mutex_unlock(&gLibLock); goto exit; } // release effect in library if (fx->lib == NULL) { ALOGW("EffectRelease() fx %p library already unloaded", handle); + pthread_mutex_unlock(&gLibLock); } else { pthread_mutex_lock(&fx->lib->lock); + // Releasing the gLibLock here as the list access is over as the + // effect is removed from the list. + // If the gLibLock is not released, we will have a deadlock situation + // since we call the sub effect release inside the EffectRelease of Proxy + pthread_mutex_unlock(&gLibLock); fx->lib->desc->release_effect(fx->subItfe); pthread_mutex_unlock(&fx->lib->lock); } free(fx); exit: - pthread_mutex_unlock(&gLibLock); return ret; } @@ -380,6 +400,49 @@ int EffectIsNullUuid(const effect_uuid_t *uuid) return 1; } +// Function to get the sub effect descriptors of the effect whose uuid +// is pointed by the first argument. It searches the gSubEffectList for the +// matching uuid and then copies the corresponding sub effect descriptors +// to the inout param +int EffectGetSubEffects(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptors, size_t size) +{ + ALOGV("EffectGetSubEffects() UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X" + "%02X\n",uuid->timeLow, uuid->timeMid, uuid->timeHiAndVersion, + uuid->clockSeq, uuid->node[0], uuid->node[1],uuid->node[2], + uuid->node[3],uuid->node[4],uuid->node[5]); + + // Check if the size of the desc buffer is large enough for 2 subeffects + if ((uuid == NULL) || (pDescriptors == NULL) || + (size < 2*sizeof(effect_descriptor_t))) { + ALOGW("NULL pointer or insufficient memory. Cannot query subeffects"); + return -EINVAL; + } + int ret = init(); + if (ret < 0) + return ret; + list_sub_elem_t *e = gSubEffectList; + sub_effect_entry_t *subeffect; + effect_descriptor_t *d; + int count = 0; + while (e != NULL) { + d = (effect_descriptor_t*)e->object; + if (memcmp(uuid, &d->uuid, sizeof(effect_uuid_t)) == 0) { + ALOGV("EffectGetSubEffects: effect found in the list"); + list_elem_t *subefx = e->sub_elem; + while (subefx != NULL) { + subeffect = (sub_effect_entry_t*)subefx->object; + d = (effect_descriptor_t*)(subeffect->object); + pDescriptors[count++] = *d; + subefx = subefx->next; + } + ALOGV("EffectGetSubEffects end - copied the sub effect descriptors"); + return count; + } + e = e->next; + } + return -ENOENT; +} ///////////////////////////////////////////////// // Local functions ///////////////////////////////////////////////// @@ -503,6 +566,65 @@ error: return -EINVAL; } +// This will find the library and UUID tags of the sub effect pointed by the +// node, gets the effect descriptor and lib_entry_t and adds the subeffect - +// sub_entry_t to the gSubEffectList +int addSubEffect(cnode *root) +{ + ALOGV("addSubEffect"); + cnode *node; + effect_uuid_t uuid; + effect_descriptor_t *d; + lib_entry_t *l; + list_elem_t *e; + node = config_find(root, LIBRARY_TAG); + if (node == NULL) { + return -EINVAL; + } + l = getLibrary(node->value); + if (l == NULL) { + ALOGW("addSubEffect() could not get library %s", node->value); + return -EINVAL; + } + node = config_find(root, UUID_TAG); + if (node == NULL) { + return -EINVAL; + } + if (stringToUuid(node->value, &uuid) != 0) { + ALOGW("addSubEffect() invalid uuid %s", node->value); + return -EINVAL; + } + d = malloc(sizeof(effect_descriptor_t)); + if (l->desc->get_descriptor(&uuid, d) != 0) { + char s[40]; + uuidToString(&uuid, s, 40); + ALOGW("Error querying effect %s on lib %s", s, l->name); + free(d); + return -EINVAL; + } +#if (LOG_NDEBUG==0) + char s[256]; + dumpEffectDescriptor(d, s, 256); + ALOGV("addSubEffect() read descriptor %p:%s",d, s); +#endif + if (EFFECT_API_VERSION_MAJOR(d->apiVersion) != + EFFECT_API_VERSION_MAJOR(EFFECT_CONTROL_API_VERSION)) { + ALOGW("Bad API version %08x on lib %s", d->apiVersion, l->name); + free(d); + return -EINVAL; + } + sub_effect_entry_t *sub_effect = malloc(sizeof(sub_effect_entry_t)); + sub_effect->object = d; + // lib_entry_t is stored since the sub effects are not linked to the library + sub_effect->lib = l; + e = malloc(sizeof(list_elem_t)); + e->object = sub_effect; + e->next = gSubEffectList->sub_elem; + gSubEffectList->sub_elem = e; + ALOGV("addSubEffect end"); + return 0; +} + int loadEffects(cnode *root) { cnode *node; @@ -571,9 +693,101 @@ int loadEffect(cnode *root) e->next = l->effects; l->effects = e; + // After the UUID node in the config_tree, if node->next is valid, + // that would be sub effect node. + // Find the sub effects and add them to the gSubEffectList + node = node->next; + int count = 2; + bool hwSubefx = false, swSubefx = false; + list_sub_elem_t *sube = NULL; + if (node != NULL) { + ALOGV("Adding the effect to gEffectSubList as there are sub effects"); + sube = malloc(sizeof(list_sub_elem_t)); + sube->object = d; + sube->sub_elem = NULL; + sube->next = gSubEffectList; + gSubEffectList = sube; + } + while (node != NULL && count) { + if (addSubEffect(node)) { + ALOGW("loadEffect() could not add subEffect %s", node->value); + // Change the gSubEffectList to point to older list; + gSubEffectList = sube->next; + free(sube->sub_elem);// Free an already added sub effect + sube->sub_elem = NULL; + free(sube); + return -ENOENT; + } + sub_effect_entry_t *subEntry = (sub_effect_entry_t*)gSubEffectList->sub_elem->object; + effect_descriptor_t *subEffectDesc = (effect_descriptor_t*)(subEntry->object); + // Since we return a dummy descriptor for the proxy during + // get_descriptor call,we replace it with the correspoding + // sw effect descriptor, but with Proxy UUID + // check for Sw desc + if (!((subEffectDesc->flags & EFFECT_FLAG_HW_ACC_MASK) == + EFFECT_FLAG_HW_ACC_TUNNEL)) { + swSubefx = true; + *d = *subEffectDesc; + d->uuid = uuid; + ALOGV("loadEffect() Changed the Proxy desc"); + } else + hwSubefx = true; + count--; + node = node->next; + } + // 1 HW and 1 SW sub effect found. Set the offload flag in the Proxy desc + if (hwSubefx && swSubefx) { + d->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED; + } return 0; } +// Searches the sub effect matching to the specified uuid +// in the gSubEffectList. It gets the lib_entry_t for +// the matched sub_effect . Used in EffectCreate of sub effects +int findSubEffect(const effect_uuid_t *uuid, + lib_entry_t **lib, + effect_descriptor_t **desc) +{ + list_sub_elem_t *e = gSubEffectList; + list_elem_t *subefx; + sub_effect_entry_t *effect; + lib_entry_t *l = NULL; + effect_descriptor_t *d = NULL; + int found = 0; + int ret = 0; + + if (uuid == NULL) + return -EINVAL; + + while (e != NULL && !found) { + subefx = (list_elem_t*)(e->sub_elem); + while (subefx != NULL) { + effect = (sub_effect_entry_t*)subefx->object; + l = (lib_entry_t *)effect->lib; + d = (effect_descriptor_t *)effect->object; + if (memcmp(&d->uuid, uuid, sizeof(effect_uuid_t)) == 0) { + ALOGV("uuid matched"); + found = 1; + break; + } + subefx = subefx->next; + } + e = e->next; + } + if (!found) { + ALOGV("findSubEffect() effect not found"); + ret = -ENOENT; + } else { + ALOGV("findSubEffect() found effect: %s in lib %s", d->name, l->name); + *lib = l; + if (desc != NULL) { + *desc = d; + } + } + return ret; +} + lib_entry_t *getLibrary(const char *name) { list_elem_t *e; diff --git a/media/libeffects/factory/EffectsFactory.h b/media/libeffects/factory/EffectsFactory.h index c1d4319..147ff18 100644 --- a/media/libeffects/factory/EffectsFactory.h +++ b/media/libeffects/factory/EffectsFactory.h @@ -32,6 +32,15 @@ typedef struct list_elem_s { struct list_elem_s *next; } list_elem_t; +// Structure used for storing effects with their sub effects. +// Used in creating gSubEffectList. Here, +// object holds the effect desc and the list sub_elem holds the sub effects +typedef struct list_sub_elem_s { + void *object; + list_elem_t *sub_elem; + struct list_sub_elem_s *next; +} list_sub_elem_t; + typedef struct lib_entry_s { audio_effect_library_t *desc; char *name; @@ -47,6 +56,16 @@ typedef struct effect_entry_s { lib_entry_t *lib; } effect_entry_t; +// Structure used to store the lib entry +// and the descriptor of the sub effects. +// The library entry is to be stored in case of +// sub effects as the sub effects are not linked +// to the library list - gLibraryList. +typedef struct sub_effect_entry_s { + lib_entry_t *lib; + void *object; +} sub_effect_entry_t; + #if __cplusplus } // extern "C" #endif diff --git a/media/libeffects/loudness/Android.mk b/media/libeffects/loudness/Android.mk new file mode 100644 index 0000000..dcb7b27 --- /dev/null +++ b/media/libeffects/loudness/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH:= $(call my-dir) + +# LoudnessEnhancer library +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + EffectLoudnessEnhancer.cpp \ + dsp/core/dynamic_range_compression.cpp + +LOCAL_CFLAGS+= -O2 -fvisibility=hidden + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + liblog \ + libstlport + +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx +LOCAL_MODULE:= libldnhncr + +LOCAL_C_INCLUDES := \ + $(call include-path-for, audio-effects) \ + bionic \ + bionic/libstdc++/include \ + external/stlport/stlport + + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libeffects/loudness/EffectLoudnessEnhancer.cpp b/media/libeffects/loudness/EffectLoudnessEnhancer.cpp new file mode 100644 index 0000000..91ed677 --- /dev/null +++ b/media/libeffects/loudness/EffectLoudnessEnhancer.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2013 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 "EffectLE" +//#define LOG_NDEBUG 0 +#include <cutils/log.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <new> +#include <time.h> +#include <math.h> +#include <audio_effects/effect_loudnessenhancer.h> +#include "dsp/core/dynamic_range_compression.h" + +extern "C" { + +// effect_handle_t interface implementation for LE effect +extern const struct effect_interface_s gLEInterface; + +// AOSP Loudness Enhancer UUID: fa415329-2034-4bea-b5dc-5b381c8d1e2c +const effect_descriptor_t gLEDescriptor = { + {0xfe3199be, 0xaed0, 0x413f, 0x87bb, {0x11, 0x26, 0x0e, 0xb6, 0x3c, 0xf1}}, // type + {0xfa415329, 0x2034, 0x4bea, 0xb5dc, {0x5b, 0x38, 0x1c, 0x8d, 0x1e, 0x2c}}, // uuid + EFFECT_CONTROL_API_VERSION, + (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_FIRST), + 0, // TODO + 1, + "Loudness Enhancer", + "The Android Open Source Project", +}; + +enum le_state_e { + LOUDNESS_ENHANCER_STATE_UNINITIALIZED, + LOUDNESS_ENHANCER_STATE_INITIALIZED, + LOUDNESS_ENHANCER_STATE_ACTIVE, +}; + +struct LoudnessEnhancerContext { + const struct effect_interface_s *mItfe; + effect_config_t mConfig; + uint8_t mState; + int32_t mTargetGainmB;// target gain in mB + // in this implementation, there is no coupling between the compression on the left and right + // channels + le_fx::AdaptiveDynamicRangeCompression* mCompressor; +}; + +// +//--- Local functions (not directly used by effect interface) +// + +void LE_reset(LoudnessEnhancerContext *pContext) +{ + ALOGV(" > LE_reset(%p)", pContext); + + if (pContext->mCompressor != NULL) { + float targetAmp = pow(10, pContext->mTargetGainmB/2000.0f); // mB to linear amplification + ALOGV("LE_reset(): Target gain=%dmB <=> factor=%.2fX", pContext->mTargetGainmB, targetAmp); + pContext->mCompressor->Initialize(targetAmp, pContext->mConfig.inputCfg.samplingRate); + } else { + ALOGE("LE_reset(%p): null compressors, can't apply target gain", pContext); + } +} + +static inline int16_t clamp16(int32_t sample) +{ + if ((sample>>15) ^ (sample>>31)) + sample = 0x7FFF ^ (sample>>31); + return sample; +} + +//---------------------------------------------------------------------------- +// LE_setConfig() +//---------------------------------------------------------------------------- +// Purpose: Set input and output audio configuration. +// +// Inputs: +// pContext: effect engine context +// pConfig: pointer to effect_config_t structure holding input and output +// configuration parameters +// +// Outputs: +// +//---------------------------------------------------------------------------- + +int LE_setConfig(LoudnessEnhancerContext *pContext, effect_config_t *pConfig) +{ + ALOGV("LE_setConfig(%p)", pContext); + + if (pConfig->inputCfg.samplingRate != pConfig->outputCfg.samplingRate) return -EINVAL; + if (pConfig->inputCfg.channels != pConfig->outputCfg.channels) return -EINVAL; + if (pConfig->inputCfg.format != pConfig->outputCfg.format) return -EINVAL; + if (pConfig->inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO) return -EINVAL; + if (pConfig->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_WRITE && + pConfig->outputCfg.accessMode != EFFECT_BUFFER_ACCESS_ACCUMULATE) return -EINVAL; + if (pConfig->inputCfg.format != AUDIO_FORMAT_PCM_16_BIT) return -EINVAL; + + pContext->mConfig = *pConfig; + + LE_reset(pContext); + + return 0; +} + + +//---------------------------------------------------------------------------- +// LE_getConfig() +//---------------------------------------------------------------------------- +// Purpose: Get input and output audio configuration. +// +// Inputs: +// pContext: effect engine context +// pConfig: pointer to effect_config_t structure holding input and output +// configuration parameters +// +// Outputs: +// +//---------------------------------------------------------------------------- + +void LE_getConfig(LoudnessEnhancerContext *pContext, effect_config_t *pConfig) +{ + *pConfig = pContext->mConfig; +} + + +//---------------------------------------------------------------------------- +// LE_init() +//---------------------------------------------------------------------------- +// Purpose: Initialize engine with default configuration. +// +// Inputs: +// pContext: effect engine context +// +// Outputs: +// +//---------------------------------------------------------------------------- + +int LE_init(LoudnessEnhancerContext *pContext) +{ + ALOGV("LE_init(%p)", pContext); + + pContext->mConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; + pContext->mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; + pContext->mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT; + pContext->mConfig.inputCfg.samplingRate = 44100; + pContext->mConfig.inputCfg.bufferProvider.getBuffer = NULL; + pContext->mConfig.inputCfg.bufferProvider.releaseBuffer = NULL; + pContext->mConfig.inputCfg.bufferProvider.cookie = NULL; + pContext->mConfig.inputCfg.mask = EFFECT_CONFIG_ALL; + pContext->mConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE; + pContext->mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO; + pContext->mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT; + pContext->mConfig.outputCfg.samplingRate = 44100; + pContext->mConfig.outputCfg.bufferProvider.getBuffer = NULL; + pContext->mConfig.outputCfg.bufferProvider.releaseBuffer = NULL; + pContext->mConfig.outputCfg.bufferProvider.cookie = NULL; + pContext->mConfig.outputCfg.mask = EFFECT_CONFIG_ALL; + + pContext->mTargetGainmB = LOUDNESS_ENHANCER_DEFAULT_TARGET_GAIN_MB; + float targetAmp = pow(10, pContext->mTargetGainmB/2000.0f); // mB to linear amplification + ALOGV("LE_init(): Target gain=%dmB <=> factor=%.2fX", pContext->mTargetGainmB, targetAmp); + + if (pContext->mCompressor == NULL) { + pContext->mCompressor = new le_fx::AdaptiveDynamicRangeCompression(); + pContext->mCompressor->Initialize(targetAmp, pContext->mConfig.inputCfg.samplingRate); + } + + LE_setConfig(pContext, &pContext->mConfig); + + return 0; +} + +// +//--- Effect Library Interface Implementation +// + +int LELib_Create(const effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_handle_t *pHandle) { + ALOGV("LELib_Create()"); + int ret; + int i; + + if (pHandle == NULL || uuid == NULL) { + return -EINVAL; + } + + if (memcmp(uuid, &gLEDescriptor.uuid, sizeof(effect_uuid_t)) != 0) { + return -EINVAL; + } + + LoudnessEnhancerContext *pContext = new LoudnessEnhancerContext; + + pContext->mItfe = &gLEInterface; + pContext->mState = LOUDNESS_ENHANCER_STATE_UNINITIALIZED; + + pContext->mCompressor = NULL; + ret = LE_init(pContext); + if (ret < 0) { + ALOGW("LELib_Create() init failed"); + delete pContext; + return ret; + } + + *pHandle = (effect_handle_t)pContext; + + pContext->mState = LOUDNESS_ENHANCER_STATE_INITIALIZED; + + ALOGV(" LELib_Create context is %p", pContext); + + return 0; + +} + +int LELib_Release(effect_handle_t handle) { + LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *)handle; + + ALOGV("LELib_Release %p", handle); + if (pContext == NULL) { + return -EINVAL; + } + pContext->mState = LOUDNESS_ENHANCER_STATE_UNINITIALIZED; + if (pContext->mCompressor != NULL) { + delete pContext->mCompressor; + pContext->mCompressor = NULL; + } + delete pContext; + + return 0; +} + +int LELib_GetDescriptor(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptor) { + + if (pDescriptor == NULL || uuid == NULL){ + ALOGV("LELib_GetDescriptor() called with NULL pointer"); + return -EINVAL; + } + + if (memcmp(uuid, &gLEDescriptor.uuid, sizeof(effect_uuid_t)) == 0) { + *pDescriptor = gLEDescriptor; + return 0; + } + + return -EINVAL; +} /* end LELib_GetDescriptor */ + +// +//--- Effect Control Interface Implementation +// +int LE_process( + effect_handle_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer) +{ + LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *)self; + + if (pContext == NULL) { + return -EINVAL; + } + + if (inBuffer == NULL || inBuffer->raw == NULL || + outBuffer == NULL || outBuffer->raw == NULL || + inBuffer->frameCount != outBuffer->frameCount || + inBuffer->frameCount == 0) { + return -EINVAL; + } + + //ALOGV("LE about to process %d samples", inBuffer->frameCount); + uint16_t inIdx; + float inputAmp = pow(10, pContext->mTargetGainmB/2000.0f); + float leftSample, rightSample; + for (inIdx = 0 ; inIdx < inBuffer->frameCount ; inIdx++) { + // makeup gain is applied on the input of the compressor + leftSample = inputAmp * (float)inBuffer->s16[2*inIdx]; + rightSample = inputAmp * (float)inBuffer->s16[2*inIdx +1]; + pContext->mCompressor->Compress(&leftSample, &rightSample); + inBuffer->s16[2*inIdx] = (int16_t) leftSample; + inBuffer->s16[2*inIdx +1] = (int16_t) rightSample; + } + + if (inBuffer->raw != outBuffer->raw) { + if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) { + for (size_t i = 0; i < outBuffer->frameCount*2; i++) { + outBuffer->s16[i] = clamp16(outBuffer->s16[i] + inBuffer->s16[i]); + } + } else { + memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount * 2 * sizeof(int16_t)); + } + } + if (pContext->mState != LOUDNESS_ENHANCER_STATE_ACTIVE) { + return -ENODATA; + } + return 0; +} + +int LE_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, + void *pCmdData, uint32_t *replySize, void *pReplyData) { + + LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *)self; + int retsize; + + if (pContext == NULL || pContext->mState == LOUDNESS_ENHANCER_STATE_UNINITIALIZED) { + return -EINVAL; + } + +// ALOGV("LE_command command %d cmdSize %d",cmdCode, cmdSize); + switch (cmdCode) { + case EFFECT_CMD_INIT: + if (pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + *(int *) pReplyData = LE_init(pContext); + break; + case EFFECT_CMD_SET_CONFIG: + if (pCmdData == NULL || cmdSize != sizeof(effect_config_t) + || pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + *(int *) pReplyData = LE_setConfig(pContext, + (effect_config_t *) pCmdData); + break; + case EFFECT_CMD_GET_CONFIG: + if (pReplyData == NULL || + *replySize != sizeof(effect_config_t)) { + return -EINVAL; + } + LE_getConfig(pContext, (effect_config_t *)pReplyData); + break; + case EFFECT_CMD_RESET: + LE_reset(pContext); + break; + case EFFECT_CMD_ENABLE: + if (pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + if (pContext->mState != LOUDNESS_ENHANCER_STATE_INITIALIZED) { + return -ENOSYS; + } + pContext->mState = LOUDNESS_ENHANCER_STATE_ACTIVE; + ALOGV("EFFECT_CMD_ENABLE() OK"); + *(int *)pReplyData = 0; + break; + case EFFECT_CMD_DISABLE: + if (pReplyData == NULL || *replySize != sizeof(int)) { + return -EINVAL; + } + if (pContext->mState != LOUDNESS_ENHANCER_STATE_ACTIVE) { + return -ENOSYS; + } + pContext->mState = LOUDNESS_ENHANCER_STATE_INITIALIZED; + ALOGV("EFFECT_CMD_DISABLE() OK"); + *(int *)pReplyData = 0; + break; + case EFFECT_CMD_GET_PARAM: { + if (pCmdData == NULL || + cmdSize != (int)(sizeof(effect_param_t) + sizeof(uint32_t)) || + pReplyData == NULL || + *replySize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint32_t))) { + return -EINVAL; + } + memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + sizeof(uint32_t)); + effect_param_t *p = (effect_param_t *)pReplyData; + p->status = 0; + *replySize = sizeof(effect_param_t) + sizeof(uint32_t); + if (p->psize != sizeof(uint32_t)) { + p->status = -EINVAL; + break; + } + switch (*(uint32_t *)p->data) { + case LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB: + ALOGV("get target gain(mB) = %d", pContext->mTargetGainmB); + *((int32_t *)p->data + 1) = pContext->mTargetGainmB; + p->vsize = sizeof(int32_t); + *replySize += sizeof(int32_t); + break; + default: + p->status = -EINVAL; + } + } break; + case EFFECT_CMD_SET_PARAM: { + if (pCmdData == NULL || + cmdSize != (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint32_t)) || + pReplyData == NULL || *replySize != sizeof(int32_t)) { + return -EINVAL; + } + *(int32_t *)pReplyData = 0; + effect_param_t *p = (effect_param_t *)pCmdData; + if (p->psize != sizeof(uint32_t) || p->vsize != sizeof(uint32_t)) { + *(int32_t *)pReplyData = -EINVAL; + break; + } + switch (*(uint32_t *)p->data) { + case LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB: + pContext->mTargetGainmB = *((int32_t *)p->data + 1); + ALOGV("set target gain(mB) = %d", pContext->mTargetGainmB); + LE_reset(pContext); // apply parameter update + break; + default: + *(int32_t *)pReplyData = -EINVAL; + } + } break; + case EFFECT_CMD_SET_DEVICE: + case EFFECT_CMD_SET_VOLUME: + case EFFECT_CMD_SET_AUDIO_MODE: + break; + + default: + ALOGW("LE_command invalid command %d",cmdCode); + return -EINVAL; + } + + return 0; +} + +/* Effect Control Interface Implementation: get_descriptor */ +int LE_getDescriptor(effect_handle_t self, + effect_descriptor_t *pDescriptor) +{ + LoudnessEnhancerContext * pContext = (LoudnessEnhancerContext *) self; + + if (pContext == NULL || pDescriptor == NULL) { + ALOGV("LE_getDescriptor() invalid param"); + return -EINVAL; + } + + *pDescriptor = gLEDescriptor; + + return 0; +} /* end LE_getDescriptor */ + +// effect_handle_t interface implementation for DRC effect +const struct effect_interface_s gLEInterface = { + LE_process, + LE_command, + LE_getDescriptor, + NULL, +}; + +// 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 : "Loudness Enhancer Library", + implementor : "The Android Open Source Project", + create_effect : LELib_Create, + release_effect : LELib_Release, + get_descriptor : LELib_GetDescriptor, +}; + +}; // extern "C" + diff --git a/media/libeffects/loudness/MODULE_LICENSE_APACHE2 b/media/libeffects/loudness/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/media/libeffects/loudness/MODULE_LICENSE_APACHE2 diff --git a/media/libeffects/loudness/NOTICE b/media/libeffects/loudness/NOTICE new file mode 100644 index 0000000..ad6ed94 --- /dev/null +++ b/media/libeffects/loudness/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2013, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/media/libeffects/loudness/common/core/basic_types.h b/media/libeffects/loudness/common/core/basic_types.h new file mode 100644 index 0000000..593e914 --- /dev/null +++ b/media/libeffects/loudness/common/core/basic_types.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_COMMON_CORE_BASIC_TYPES_H_ +#define LE_FX_ENGINE_COMMON_CORE_BASIC_TYPES_H_ + +#include <stddef.h> +#include <stdlib.h> +#include <string> +using ::std::string; +using ::std::basic_string; +#include <vector> +using ::std::vector; + +#include "common/core/os.h" + +// ----------------------------------------------------------------------------- +// Definitions of common basic types: +// ----------------------------------------------------------------------------- + +#if !defined(G_COMPILE) && !defined(BASE_INTEGRAL_TYPES_H_) + +namespace le_fx { + +typedef signed char schar; +typedef signed char int8; +typedef short int16; +typedef int int32; +typedef long long int64; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +} // namespace le_fx + +#endif + +namespace le_fx { + +struct FloatArray { + int length; + float *data; + + FloatArray(void) { + data = NULL; + length = 0; + } +}; + +struct Int16Array { + int length; + int16 *data; + + Int16Array(void) { + data = NULL; + length = 0; + } +}; + +struct Int32Array { + int length; + int32 *data; + + Int32Array(void) { + data = NULL; + length = 0; + } +}; + +struct Int8Array { + int length; + uint8 *data; + + Int8Array(void) { + data = NULL; + length = 0; + } +}; + +// +// Simple wrapper for waveform data: +// +class WaveData : public vector<int16> { + public: + WaveData(); + ~WaveData(); + + void Set(int number_samples, int sampling_rate, int16 *data); + int sample_rate(void) const; + void set_sample_rate(int sample_rate); + bool Equals(const WaveData &wave_data, int threshold = 0) const; + + private: + int sample_rate_; +}; + +} // namespace le_fx + +#endif // LE_FX_ENGINE_COMMON_CORE_BASIC_TYPES_H_ diff --git a/media/libeffects/loudness/common/core/byte_swapper.h b/media/libeffects/loudness/common/core/byte_swapper.h new file mode 100644 index 0000000..8f0caf3 --- /dev/null +++ b/media/libeffects/loudness/common/core/byte_swapper.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_COMMON_CORE_BYTE_SWAPPER_H_ +#define LE_FX_ENGINE_COMMON_CORE_BYTE_SWAPPER_H_ + +#include <stdio.h> +#include <string.h> + +#include "common/core/basic_types.h" +#include "common/core/os.h" + +namespace le_fx { + +namespace arch { + +inline bool IsLittleEndian(void) { + int16 word = 1; + char *cp = reinterpret_cast<char *>(&word); + return cp[0] != 0; +} + +inline bool IsBigEndian(void) { + return !IsLittleEndian(); +} + +template <typename T, unsigned int kValSize> +struct ByteSwapper { + static T Swap(const T &val) { + T new_val = val; + char *first = &new_val, *last = first + kValSize - 1, x; + for (; first < last; ++first, --last) { + x = *last; + *last = *first; + *first = x; + } + return new_val; + } +}; + +template <typename T> +struct ByteSwapper<T, 1> { + static T Swap(const T &val) { + return val; + } +}; + +template <typename T> +struct ByteSwapper<T, 2> { + static T Swap(const T &val) { + T new_val; + const char *o = (const char *)&val; + char *p = reinterpret_cast<char *>(&new_val); + p[0] = o[1]; + p[1] = o[0]; + return new_val; + } +}; + +template <typename T> +struct ByteSwapper<T, 4> { + static T Swap(const T &val) { + T new_val; + const char *o = (const char *)&val; + char *p = reinterpret_cast<char *>(&new_val); + p[0] = o[3]; + p[1] = o[2]; + p[2] = o[1]; + p[3] = o[0]; + return new_val; + } +}; + +template <typename T> +struct ByteSwapper<T, 8> { + static T Swap(const T &val) { + T new_val = val; + const char *o = (const char *)&val; + char *p = reinterpret_cast<char *>(&new_val); + p[0] = o[7]; + p[1] = o[6]; + p[2] = o[5]; + p[3] = o[4]; + p[4] = o[3]; + p[5] = o[2]; + p[6] = o[1]; + p[7] = o[0]; + return new_val; + } +}; + +template <typename T> +T SwapBytes(const T &val, bool force_swap) { + if (force_swap) { +#if !defined(LE_FX__NEED_BYTESWAP) + return ByteSwapper<T, sizeof(T)>::Swap(val); +#else + return val; +#endif // !LE_FX_NEED_BYTESWAP + } else { +#if !defined(LE_FX_NEED_BYTESWAP) + return val; +#else + return ByteSwapper<T, sizeof(T)>::Swap(val); +#endif // !LE_FX_NEED_BYTESWAP + } +} + +template <typename T> +const T *SwapBytes(const T *vals, unsigned int num_items, bool force_swap) { + if (force_swap) { +#if !defined(LE_FX_NEED_BYTESWAP) + T *writeable_vals = const_cast<T *>(vals); + for (unsigned int i = 0; i < num_items; i++) { + writeable_vals[i] = ByteSwapper<T, sizeof(T)>::Swap(vals[i]); + } + return writeable_vals; +#else + return vals; +#endif // !LE_FX_NEED_BYTESWAP + } else { +#if !defined(LE_FX_NEED_BYTESWAP) + return vals; +#else + T *writeable_vals = const_cast<T *>(vals); + for (unsigned int i = 0; i < num_items; i++) { + writeable_vals[i] = ByteSwapper<T, sizeof(T)>::Swap(vals[i]); + } + return writeable_vals; +#endif // !LE_FX_NEED_BYTESWAP + } +} + +} // namespace arch + +} // namespace le_fx + +#endif // LE_FX_ENGINE_COMMON_CORE_BYTE_SWAPPER_H_ diff --git a/media/libeffects/loudness/common/core/math.h b/media/libeffects/loudness/common/core/math.h new file mode 100644 index 0000000..3f302cc --- /dev/null +++ b/media/libeffects/loudness/common/core/math.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_COMMON_CORE_MATH_H_ +#define LE_FX_ENGINE_COMMON_CORE_MATH_H_ + +#include <math.h> +#include <algorithm> +using ::std::min; +using ::std::max; +using ::std::fill; +using ::std::fill_n;using ::std::lower_bound; +#include <cmath> +#include <math.h> +//using ::std::fpclassify; + +#include "common/core/os.h" +#include "common/core/types.h" + +namespace le_fx { +namespace math { + +// A fast approximation to log2(.) +inline float fast_log2(float val) { + int* const exp_ptr = reinterpret_cast <int *> (&val); + int x = *exp_ptr; + const int log_2 = ((x >> 23) & 255) - 128; + x &= ~(255 << 23); + x += 127 << 23; + *exp_ptr = x; + val = ((-1.0f / 3) * val + 2) * val - 2.0f / 3; + return static_cast<float>(val + log_2); +} + +// A fast approximation to log(.) +inline float fast_log(float val) { + return fast_log2(val) * + 0.693147180559945286226763982995180413126945495605468750f; +} + +// An approximation of the exp(.) function using a 5-th order Taylor expansion. +// It's pretty accurate between +-0.1 and accurate to 10e-3 between +-1 +template <typename T> +inline T ExpApproximationViaTaylorExpansionOrder5(T x) { + const T x2 = x * x; + const T x3 = x2 * x; + const T x4 = x2 * x2; + const T x5 = x3 * x2; + return 1.0f + x + 0.5f * x2 + + 0.16666666666666665741480812812369549646973609924316406250f * x3 + + 0.0416666666666666643537020320309238741174340248107910156250f * x4 + + 0.008333333333333333217685101601546193705871701240539550781250f * x5; +} + +} // namespace math +} // namespace le_fx + +// Math functions missing in Android NDK: +#if defined(LE_FX_OS_ANDROID) + +namespace std { + +// +// Round to the nearest integer: We need this implementation +// since std::round is missing on android. +// +template <typename T> +inline T round(const T &x) { + return static_cast<T>(std::floor(static_cast<double>(x) + 0.5)); +} + +} // namespace std + +#endif // LE_FX_OS_ANDROID + +#endif // LE_FX_ENGINE_COMMON_CORE_MATH_H_ diff --git a/media/libeffects/loudness/common/core/os.h b/media/libeffects/loudness/common/core/os.h new file mode 100644 index 0000000..4a8ce82 --- /dev/null +++ b/media/libeffects/loudness/common/core/os.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_COMMON_CORE_OS_H_ +#define LE_FX_ENGINE_COMMON_CORE_OS_H_ + +// ----------------------------------------------------------------------------- +// OS Identification: +// ----------------------------------------------------------------------------- + +#define LE_FX_OS_UNIX +#if defined(__ANDROID__) +# define LE_FX_OS_ANDROID +#endif // Android + +#endif // LE_FX_ENGINE_COMMON_CORE_OS_H_ diff --git a/media/libeffects/loudness/common/core/types.h b/media/libeffects/loudness/common/core/types.h new file mode 100644 index 0000000..d1b6c6a --- /dev/null +++ b/media/libeffects/loudness/common/core/types.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_COMMON_CORE_TYPES_H_ +#define LE_FX_ENGINE_COMMON_CORE_TYPES_H_ + +#include "common/core/os.h" + +#include "common/core/basic_types.h" + +#ifndef LE_FX_DISALLOW_COPY_AND_ASSIGN +#define LE_FX_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) +#endif // LE_FX_DISALLOW_COPY_AND_ASSIGN + + +#endif // LE_FX_ENGINE_COMMON_CORE_TYPES_H_ diff --git a/media/libeffects/loudness/dsp/core/basic-inl.h b/media/libeffects/loudness/dsp/core/basic-inl.h new file mode 100644 index 0000000..3f77147 --- /dev/null +++ b/media/libeffects/loudness/dsp/core/basic-inl.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_BASIC_INL_H_ +#define LE_FX_ENGINE_DSP_CORE_BASIC_INL_H_ + +#include <math.h> + +namespace le_fx { + +namespace sigmod { + +template <typename T> +int SearchIndex(const T x_data[], + T x, + int start_index, + int end_index) { + int start = start_index; + int end = end_index; + while (end > start + 1) { + int i = (end + start) / 2; + if (x_data[i] > x) { + end = i; + } else { + start = i; + } + } + return start; +} + +} // namespace sigmod + +} // namespace le_fx + +#endif // LE_FX_ENGINE_DSP_CORE_BASIC_INL_H_ diff --git a/media/libeffects/loudness/dsp/core/basic.h b/media/libeffects/loudness/dsp/core/basic.h new file mode 100644 index 0000000..27e0a8d --- /dev/null +++ b/media/libeffects/loudness/dsp/core/basic.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_BASIC_H_ +#define LE_FX_ENGINE_DSP_CORE_BASIC_H_ + +#include <limits.h> +#include "common/core/math.h" +#include "common/core/types.h" + +namespace le_fx { + +namespace sigmod { + +// Searchs for the interval that contains <x> using a divide-and-conquer +// algorithm. +// X[]: a vector of sorted values (X[i+1] > X[i]) +// x: a value +// StartIndex: the minimum searched index +// EndIndex: the maximum searched index +// returns: the index <i> that satisfies: X[i] <= x <= X[i+1] && +// StartIndex <= i <= (EndIndex-1) +template <typename T> +int SearchIndex(const T x_data[], + T x, + int start_index, + int end_index); + +} // namespace sigmod + +} // namespace le_fx + +#include "dsp/core/basic-inl.h" + +#endif // LE_FX_ENGINE_DSP_CORE_BASIC_H_ diff --git a/media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h b/media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h new file mode 100644 index 0000000..da75ceb --- /dev/null +++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression-inl.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_INL_H_ +#define LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_INL_H_ + +//#define LOG_NDEBUG 0 +#include <cutils/log.h> + + +namespace le_fx { + + +inline void AdaptiveDynamicRangeCompression::set_knee_threshold(float decibel) { + // Converts to 1og-base + knee_threshold_in_decibel_ = decibel; + knee_threshold_ = 0.1151292546497023061569109358970308676362037658691406250f * + decibel + 10.39717719035538401328722102334722876548767089843750f; +} + + +inline void AdaptiveDynamicRangeCompression::set_knee_threshold_via_target_gain( + float target_gain) { + const float decibel = target_gain_to_knee_threshold_.Interpolate( + target_gain); + ALOGV("set_knee_threshold_via_target_gain: decibel =%.3fdB", decibel); + set_knee_threshold(decibel); +} + +} // namespace le_fx + + +#endif // LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_INL_H_ diff --git a/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp b/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp new file mode 100644 index 0000000..7bd068e --- /dev/null +++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2013 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. + */ + +#include <cmath> + +#include "common/core/math.h" +#include "common/core/types.h" +#include "dsp/core/basic.h" +#include "dsp/core/interpolation.h" +#include "dsp/core/dynamic_range_compression.h" + +//#define LOG_NDEBUG 0 +#include <cutils/log.h> + + +namespace le_fx { + +// Definitions for static const class members declared in +// dynamic_range_compression.h. +const float AdaptiveDynamicRangeCompression::kMinAbsValue = 0.000001f; +const float AdaptiveDynamicRangeCompression::kMinLogAbsValue = + 0.032766999999999997517097227728299912996590137481689453125f; +const float AdaptiveDynamicRangeCompression::kFixedPointLimit = 32767.0f; +const float AdaptiveDynamicRangeCompression::kInverseFixedPointLimit = + 1.0f / AdaptiveDynamicRangeCompression::kFixedPointLimit; +const float AdaptiveDynamicRangeCompression::kDefaultKneeThresholdInDecibel = + -8.0f; +const float AdaptiveDynamicRangeCompression::kCompressionRatio = 7.0f; +const float AdaptiveDynamicRangeCompression::kTauAttack = 0.001f; +const float AdaptiveDynamicRangeCompression::kTauRelease = 0.015f; + +AdaptiveDynamicRangeCompression::AdaptiveDynamicRangeCompression() { + static const float kTargetGain[] = { + 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + static const float kKneeThreshold[] = { + -8.0f, -8.0f, -8.5f, -9.0f, -10.0f }; + target_gain_to_knee_threshold_.Initialize( + &kTargetGain[0], &kKneeThreshold[0], + sizeof(kTargetGain) / sizeof(kTargetGain[0])); +} + +bool AdaptiveDynamicRangeCompression::Initialize( + float target_gain, float sampling_rate) { + set_knee_threshold_via_target_gain(target_gain); + sampling_rate_ = sampling_rate; + state_ = 0.0f; + compressor_gain_ = 1.0f; + if (kTauAttack > 0.0f) { + const float taufs = kTauAttack * sampling_rate_; + alpha_attack_ = std::exp(-1.0f / taufs); + } else { + alpha_attack_ = 0.0f; + } + if (kTauRelease > 0.0f) { + const float taufs = kTauRelease * sampling_rate_; + alpha_release_ = std::exp(-1.0f / taufs); + } else { + alpha_release_ = 0.0f; + } + // Feed-forward topology + slope_ = 1.0f / kCompressionRatio - 1.0f; + return true; +} + +float AdaptiveDynamicRangeCompression::Compress(float x) { + const float max_abs_x = std::max(std::fabs(x), kMinLogAbsValue); + const float max_abs_x_dB = math::fast_log(max_abs_x); + // Subtract Threshold from log-encoded input to get the amount of overshoot + const float overshoot = max_abs_x_dB - knee_threshold_; + // Hard half-wave rectifier + const float rect = std::max(overshoot, 0.0f); + // Multiply rectified overshoot with slope + const float cv = rect * slope_; + const float prev_state = state_; + if (cv <= state_) { + state_ = alpha_attack_ * state_ + (1.0f - alpha_attack_) * cv; + } else { + state_ = alpha_release_ * state_ + (1.0f - alpha_release_) * cv; + } + compressor_gain_ *= + math::ExpApproximationViaTaylorExpansionOrder5(state_ - prev_state); + x *= compressor_gain_; + if (x > kFixedPointLimit) { + return kFixedPointLimit; + } + if (x < -kFixedPointLimit) { + return -kFixedPointLimit; + } + return x; +} + +void AdaptiveDynamicRangeCompression::Compress(float *x1, float *x2) { + // Taking the maximum amplitude of both channels + const float max_abs_x = std::max(std::fabs(*x1), + std::max(std::fabs(*x2), kMinLogAbsValue)); + const float max_abs_x_dB = math::fast_log(max_abs_x); + // Subtract Threshold from log-encoded input to get the amount of overshoot + const float overshoot = max_abs_x_dB - knee_threshold_; + // Hard half-wave rectifier + const float rect = std::max(overshoot, 0.0f); + // Multiply rectified overshoot with slope + const float cv = rect * slope_; + const float prev_state = state_; + if (cv <= state_) { + state_ = alpha_attack_ * state_ + (1.0f - alpha_attack_) * cv; + } else { + state_ = alpha_release_ * state_ + (1.0f - alpha_release_) * cv; + } + compressor_gain_ *= + math::ExpApproximationViaTaylorExpansionOrder5(state_ - prev_state); + *x1 *= compressor_gain_; + if (*x1 > kFixedPointLimit) { + *x1 = kFixedPointLimit; + } + if (*x1 < -kFixedPointLimit) { + *x1 = -kFixedPointLimit; + } + *x2 *= compressor_gain_; + if (*x2 > kFixedPointLimit) { + *x2 = kFixedPointLimit; + } + if (*x2 < -kFixedPointLimit) { + *x2 = -kFixedPointLimit; + } +} + +} // namespace le_fx + diff --git a/media/libeffects/loudness/dsp/core/dynamic_range_compression.h b/media/libeffects/loudness/dsp/core/dynamic_range_compression.h new file mode 100644 index 0000000..2821a78 --- /dev/null +++ b/media/libeffects/loudness/dsp/core/dynamic_range_compression.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_H_ +#define LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_H_ + +#include "common/core/types.h" +#include "common/core/math.h" +#include "dsp/core/basic.h" +#include "dsp/core/interpolation.h" + +//#define LOG_NDEBUG 0 +#include <cutils/log.h> + + +namespace le_fx { + +// An adaptive dynamic range compression algorithm. The gain adaptation is made +// at the logarithmic domain and it is based on a Branching-Smooth compensated +// digital peak detector with different time constants for attack and release. +class AdaptiveDynamicRangeCompression { + public: + AdaptiveDynamicRangeCompression(); + + // Initializes the compressor using prior information. It assumes that the + // input signal is speech from high-quality recordings that is scaled and then + // fed to the compressor. The compressor is tuned according to the target gain + // that is expected to be applied. + // + // Target gain receives values between 0.0 and 10.0. The knee threshold is + // reduced as the target gain increases in order to fit the increased range of + // values. + // + // Values between 1.0 and 2.0 will only mildly affect your signal. Higher + // values will reduce the dynamic range of the signal to the benefit of + // increased loudness. + // + // If nothing is known regarding the input, a `target_gain` of 1.0f is a + // relatively safe choice for many signals. + bool Initialize(float target_gain, float sampling_rate); + + // A fast version of the algorithm that uses approximate computations for the + // log(.) and exp(.). + float Compress(float x); + + // Stereo channel version of the compressor + void Compress(float *x1, float *x2); + + // This version is slower than Compress(.) but faster than CompressSlow(.) + float CompressNormalSpeed(float x); + + // A slow version of the algorithm that is easier for further developement, + // tuning and debugging + float CompressSlow(float x); + + // Sets knee threshold (in decibel). + void set_knee_threshold(float decibel); + + // Sets knee threshold via the target gain using an experimentally derived + // relationship. + void set_knee_threshold_via_target_gain(float target_gain); + + private: + // The minimum accepted absolute input value and it's natural logarithm. This + // is to prevent numerical issues when the input is close to zero + static const float kMinAbsValue; + static const float kMinLogAbsValue; + // Fixed-point arithmetic limits + static const float kFixedPointLimit; + static const float kInverseFixedPointLimit; + // The default knee threshold in decibel. The knee threshold defines when the + // compressor is actually starting to compress the value of the input samples + static const float kDefaultKneeThresholdInDecibel; + // The compression ratio is the reciprocal of the slope of the line segment + // above the threshold (in the log-domain). The ratio controls the + // effectiveness of the compression. + static const float kCompressionRatio; + // The attack time of the envelope detector + static const float kTauAttack; + // The release time of the envelope detector + static const float kTauRelease; + + float sampling_rate_; + // the internal state of the envelope detector + float state_; + // the latest gain factor that was applied to the input signal + float compressor_gain_; + // attack constant for exponential dumping + float alpha_attack_; + // release constant for exponential dumping + float alpha_release_; + float slope_; + // The knee threshold + float knee_threshold_; + float knee_threshold_in_decibel_; + // This interpolator provides the function that relates target gain to knee + // threshold. + sigmod::InterpolatorLinear<float> target_gain_to_knee_threshold_; + + LE_FX_DISALLOW_COPY_AND_ASSIGN(AdaptiveDynamicRangeCompression); +}; + +} // namespace le_fx + +#include "dsp/core/dynamic_range_compression-inl.h" + +#endif // LE_FX_ENGINE_DSP_CORE_DYNAMIC_RANGE_COMPRESSION_H_ diff --git a/media/libeffects/loudness/dsp/core/interpolation.h b/media/libeffects/loudness/dsp/core/interpolation.h new file mode 100644 index 0000000..23c287c --- /dev/null +++ b/media/libeffects/loudness/dsp/core/interpolation.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATION_H_ +#define LE_FX_ENGINE_DSP_CORE_INTERPOLATION_H_ + +#include "common/core/math.h" +#include "dsp/core/interpolator_base.h" +#include "dsp/core/interpolator_linear.h" + +#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATION_H_ + diff --git a/media/libeffects/loudness/dsp/core/interpolator_base-inl.h b/media/libeffects/loudness/dsp/core/interpolator_base-inl.h new file mode 100644 index 0000000..bd08b65 --- /dev/null +++ b/media/libeffects/loudness/dsp/core/interpolator_base-inl.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_INL_H_ +#define LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_INL_H_ + +#include "dsp/core/basic.h" + +//#define LOG_NDEBUG 0 +#include <cutils/log.h> + + +namespace le_fx { + +namespace sigmod { + +template <typename T, class Algorithm> +InterpolatorBase<T, Algorithm>::InterpolatorBase() { + status_ = false; + cached_index_ = 0; + x_data_ = NULL; + y_data_ = NULL; + data_length_ = 0; + own_x_data_ = false; + x_start_offset_ = 0.0; + last_element_index_ = -1; + x_inverse_sampling_interval_ = 0.0; + state_ = NULL; +} + +template <typename T, class Algorithm> +InterpolatorBase<T, Algorithm>::~InterpolatorBase() { + delete [] state_; + if (own_x_data_) { + delete [] x_data_; + } +} + +template <typename T, class Algorithm> +bool InterpolatorBase<T, Algorithm>::Initialize(const vector<T> &x_data, + const vector<T> &y_data) { +#ifndef NDEBUG + if (x_data.size() != y_data.size()) { + LoggerError("InterpolatorBase::Initialize: xData size (%d) != yData size" + " (%d)", x_data.size(), y_data.size()); + } +#endif + return Initialize(&x_data[0], &y_data[0], x_data.size()); +} + +template <typename T, class Algorithm> +bool InterpolatorBase<T, Algorithm>::Initialize(double x_start_offset, + double x_sampling_interval, + const vector<T> &y_data) { + return Initialize(x_start_offset, + x_sampling_interval, + &y_data[0], + y_data.size()); +} + +template <typename T, class Algorithm> +bool InterpolatorBase<T, Algorithm>::Initialize(double x_start_offset, + double x_sampling_interval, + const T *y_data, + int data_length) { + // Constructs and populate x-axis data: `x_data_` + T *x_data_tmp = new T[data_length]; + float time_offset = x_start_offset; + for (int n = 0; n < data_length; n++) { + x_data_tmp[n] = time_offset; + time_offset += x_sampling_interval; + } + Initialize(x_data_tmp, y_data, data_length); + // Sets-up the regularly sampled interpolation mode + x_start_offset_ = x_start_offset; + x_inverse_sampling_interval_ = 1.0 / x_sampling_interval; + own_x_data_ = true; + return status_; +} + + +template <typename T, class Algorithm> +bool InterpolatorBase<T, Algorithm>::Initialize( + const T *x_data, const T *y_data, int data_length) { + // Default settings + cached_index_ = 0; + data_length_ = 0; + x_start_offset_ = 0; + x_inverse_sampling_interval_ = 0; + state_ = NULL; + // Input data is externally owned + own_x_data_ = false; + x_data_ = x_data; + y_data_ = y_data; + data_length_ = data_length; + last_element_index_ = data_length - 1; + // Check input data sanity + for (int n = 0; n < last_element_index_; ++n) { + if (x_data_[n + 1] <= x_data_[n]) { + ALOGE("InterpolatorBase::Initialize: xData are not ordered or " + "contain equal values (X[%d] <= X[%d]) (%.5e <= %.5e)", + n + 1, n, x_data_[n + 1], x_data_[n]); + status_ = false; + return false; + } + } + // Pre-compute internal state by calling the corresponding function of the + // derived class. + status_ = static_cast<Algorithm*>(this)->SetInternalState(); + return status_; +} + +template <typename T, class Algorithm> +T InterpolatorBase<T, Algorithm>::Interpolate(T x) { +#ifndef NDEBUG + if (cached_index_ < 0 || cached_index_ > data_length_ - 2) { + LoggerError("InterpolatorBase:Interpolate: CachedIndex_ out of bounds " + "[0, %d, %d]", cached_index_, data_length_ - 2); + } +#endif + // Search for the containing interval + if (x <= x_data_[cached_index_]) { + if (cached_index_ <= 0) { + cached_index_ = 0; + return y_data_[0]; + } + if (x >= x_data_[cached_index_ - 1]) { + cached_index_--; // Fast descending + } else { + if (x <= x_data_[0]) { + cached_index_ = 0; + return y_data_[0]; + } + cached_index_ = SearchIndex(x_data_, x, 0, cached_index_); + } + } else { + if (cached_index_ >= last_element_index_) { + cached_index_ = last_element_index_; + return y_data_[last_element_index_]; + } + if (x > x_data_[cached_index_ + 1]) { + if (cached_index_ + 2 > last_element_index_) { + cached_index_ = last_element_index_ - 1; + return y_data_[last_element_index_]; + } + if (x <= x_data_[cached_index_ + 2]) { + cached_index_++; // Fast ascending + } else { + if (x >= x_data_[last_element_index_]) { + cached_index_ = last_element_index_ - 1; + return y_data_[last_element_index_]; + } + cached_index_ = SearchIndex( + x_data_, x, cached_index_, last_element_index_); + } + } + } + // Compute interpolated value by calling the corresponding function of the + // derived class. + return static_cast<Algorithm*>(this)->MethodSpecificInterpolation(x); +} + +} // namespace sigmod + +} // namespace le_fx + +#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_INL_H_ diff --git a/media/libeffects/loudness/dsp/core/interpolator_base.h b/media/libeffects/loudness/dsp/core/interpolator_base.h new file mode 100644 index 0000000..0cd1a35 --- /dev/null +++ b/media/libeffects/loudness/dsp/core/interpolator_base.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_H_ +#define LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_H_ + +#include "common/core/types.h" + +namespace le_fx { + +namespace sigmod { + +// Interpolation base-class that provides the interface, while it is the derived +// class that provides the specific interpolation algorithm. The following list +// of interpolation algorithms are currently present: +// +// InterpolationSine<T>: weighted interpolation between y_data[n] and +// y_data[n+1] using a sin(.) weighting factor from +// 0 to pi/4. +// InterpolationLinear<T>: linear interpolation +// InterpolationSplines<T>: spline-based interpolation +// +// Example (using derived spline-based interpolation class): +// InterpolatorSplines<float> interp(x_data, y_data, data_length); +// for (int n = 0; n < data_length; n++) Y[n] = interp.Interpolate(X[n]); +// +template <typename T, class Algorithm> +class InterpolatorBase { + public: + InterpolatorBase(); + ~InterpolatorBase(); + + // Generic random-access interpolation with arbitrary spaced x-axis samples. + // Below X[0], the interpolator returns Y[0]. Above X[data_length-1], it + // returns Y[data_length-1]. + T Interpolate(T x); + + bool get_status() const { + return status_; + } + + // Initializes internal buffers. + // x_data: [(data_length)x1] x-axis coordinates (searching axis) + // y_data: [(data_length)x1] y-axis coordinates (interpolation axis) + // data_length: number of points + // returns `true` if everything is ok, `false`, otherwise + bool Initialize(const T *x_data, const T *y_data, int data_length); + + // Initializes internal buffers. + // x_data: x-axis coordinates (searching axis) + // y_data: y-axis coordinates (interpolating axis) + // returns `true` if everything is ok, `false`, otherwise + bool Initialize(const vector<T> &x_data, const vector<T> &y_data); + + // Initialization for regularly sampled sequences, where: + // x_data[i] = x_start_offset + i * x_sampling_interval + bool Initialize(double x_start_offset, + double x_sampling_interval, + const vector<T> &y_data); + + // Initialization for regularly sampled sequences, where: + // x_data[i] = x_start_offset + i * x_sampling_interval + bool Initialize(double x_start_offset, + double x_sampling_interval, + const T *y_data, + int data_length); + + protected: + // Is set to false if something goes wrong, and to true if everything is ok. + bool status_; + + // The start-index of the previously searched interval + int cached_index_; + + // Data points + const T *x_data_; // Externally or internally owned, depending on own_x_data_ + const T *y_data_; // Externally owned (always) + int data_length_; + // Index of the last element `data_length_ - 1` kept here for optimization + int last_element_index_; + bool own_x_data_; + // For regularly-samples sequences, keep only the boundaries and the intervals + T x_start_offset_; + float x_inverse_sampling_interval_; + + // Algorithm state (internally owned) + double *state_; + + private: + LE_FX_DISALLOW_COPY_AND_ASSIGN(InterpolatorBase); +}; + +} // namespace sigmod + +} // namespace le_fx + +#include "dsp/core/interpolator_base-inl.h" + +#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_BASE_H_ diff --git a/media/libeffects/loudness/dsp/core/interpolator_linear.h b/media/libeffects/loudness/dsp/core/interpolator_linear.h new file mode 100644 index 0000000..434698a --- /dev/null +++ b/media/libeffects/loudness/dsp/core/interpolator_linear.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 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 LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_LINEAR_H_ +#define LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_LINEAR_H_ + +#include <math.h> +#include "dsp/core/interpolator_base.h" + +namespace le_fx { + +namespace sigmod { + +// Linear interpolation class. +// +// The main functionality of this class is provided by it's base-class, so +// please refer to: InterpolatorBase +// +// Example: +// InterpolatorLinear<float> interp(x_data, y_data, data_length); +// for (int n = 0; n < data_length; n++) Y[n] = interp.Interpolate(X[n]); +// +template <typename T> +class InterpolatorLinear: public InterpolatorBase<T, InterpolatorLinear<T> > { + public: + InterpolatorLinear() { } + ~InterpolatorLinear() { } + + protected: + // Provides the main implementation of the linear interpolation algorithm. + // Assumes that: X[cached_index_] < x < X[cached_index_ + 1] + T MethodSpecificInterpolation(T x); + + // Pre-compute internal state_ parameters. + bool SetInternalState(); + + private: + friend class InterpolatorBase<T, InterpolatorLinear<T> >; + typedef InterpolatorBase<T, InterpolatorLinear<T> > BaseClass; + using BaseClass::status_; + using BaseClass::cached_index_; + using BaseClass::x_data_; + using BaseClass::y_data_; + using BaseClass::data_length_; + using BaseClass::state_; + + LE_FX_DISALLOW_COPY_AND_ASSIGN(InterpolatorLinear<T>); +}; + +template <typename T> +inline T InterpolatorLinear<T>::MethodSpecificInterpolation(T x) { + T dX = x_data_[cached_index_ + 1] - x_data_[cached_index_]; + T dY = y_data_[cached_index_ + 1] - y_data_[cached_index_]; + T dx = x - x_data_[cached_index_]; + return y_data_[cached_index_] + (dY * dx) / dX; +} + +template <typename T> +bool InterpolatorLinear<T>::SetInternalState() { + state_ = NULL; + return true; +} + +} // namespace sigmod + +} // namespace le_fx + +#endif // LE_FX_ENGINE_DSP_CORE_INTERPOLATOR_LINEAR_H_ diff --git a/media/libeffects/proxy/Android.mk b/media/libeffects/proxy/Android.mk new file mode 100644 index 0000000..01b3be1 --- /dev/null +++ b/media/libeffects/proxy/Android.mk @@ -0,0 +1,34 @@ +# Copyright 2013 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE:= libeffectproxy +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/soundfx +LOCAL_MODULE_TAGS := optional + + +LOCAL_SRC_FILES := \ + EffectProxy.cpp + +LOCAL_CFLAGS+= -fvisibility=hidden + +LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libdl libeffects + +LOCAL_C_INCLUDES := \ + system/media/audio_effects/include \ + bionic/libc/include + +include $(BUILD_SHARED_LIBRARY) + diff --git a/media/libeffects/proxy/EffectProxy.cpp b/media/libeffects/proxy/EffectProxy.cpp new file mode 100644 index 0000000..dd4ad08 --- /dev/null +++ b/media/libeffects/proxy/EffectProxy.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2013 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 "EffectProxy" +//#define LOG_NDEBUG 0 + +#include <cutils/log.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <new> +#include <EffectProxy.h> +#include <utils/threads.h> +#include <media/EffectsFactoryApi.h> + +namespace android { +// This is a dummy proxy descriptor just to return to Factory during the initial +// GetDescriptor call. Later in the factory, it is replaced with the +// SW sub effect descriptor +// proxy UUID af8da7e0-2ca1-11e3-b71d-0002a5d5c51b +const effect_descriptor_t gProxyDescriptor = { + EFFECT_UUID_INITIALIZER, // type + {0xaf8da7e0, 0x2ca1, 0x11e3, 0xb71d, { 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b }}, // uuid + EFFECT_CONTROL_API_VERSION, //version of effect control API + (EFFECT_FLAG_TYPE_INSERT | EFFECT_FLAG_INSERT_LAST | + EFFECT_FLAG_VOLUME_CTRL), // effect capability flags + 0, // CPU load + 1, // Data memory + "Proxy", //effect name + "AOSP", //implementor name +}; + + +static const effect_descriptor_t *const gDescriptors[] = +{ + &gProxyDescriptor, +}; + + +int EffectProxyCreate(const effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_handle_t *pHandle) { + + effect_descriptor_t* desc; + EffectContext* pContext; + if (pHandle == NULL || uuid == NULL) { + ALOGE("EffectProxyCreate() called with NULL pointer"); + return -EINVAL; + } + ALOGV("EffectProxyCreate start.."); + pContext = new EffectContext; + pContext->sessionId = sessionId; + pContext->ioId = ioId; + pContext->uuid = *uuid; + pContext->common_itfe = &gEffectInterface; + + // The sub effects will be created in effect_command when the first command + // for the effect is received + pContext->eHandle[SUB_FX_HOST] = pContext->eHandle[SUB_FX_OFFLOAD] = NULL; + + // Get the HW and SW sub effect descriptors from the effects factory + desc = new effect_descriptor_t[SUB_FX_COUNT]; + pContext->desc = new effect_descriptor_t[SUB_FX_COUNT]; + int retValue = EffectGetSubEffects(uuid, desc, + sizeof(effect_descriptor_t) * SUB_FX_COUNT); + // EffectGetSubEffects returns the number of sub-effects copied. + if (retValue != SUB_FX_COUNT) { + ALOGE("EffectCreate() could not get the sub effects"); + delete desc; + delete pContext->desc; + return -EINVAL; + } + // Check which is the HW descriptor and copy the descriptors + // to the Context desc array + // Also check if there is only one HW and one SW descriptor. + // HW descriptor alone has the HW_TUNNEL flag. + if ((desc[0].flags & EFFECT_FLAG_HW_ACC_TUNNEL) && + !(desc[1].flags & EFFECT_FLAG_HW_ACC_TUNNEL)) { + pContext->desc[SUB_FX_OFFLOAD] = desc[0]; + pContext->desc[SUB_FX_HOST] = desc[1]; + } + else if ((desc[1].flags & EFFECT_FLAG_HW_ACC_TUNNEL) && + !(desc[0].flags & EFFECT_FLAG_HW_ACC_TUNNEL)) { + pContext->desc[SUB_FX_HOST] = desc[0]; + pContext->desc[SUB_FX_OFFLOAD] = desc[1]; + } + delete desc; +#if (LOG_NDEBUG == 0) + effect_uuid_t uuid_print = pContext->desc[SUB_FX_HOST].uuid; + ALOGV("EffectCreate() UUID of HOST: %08X-%04X-%04X-%04X-%02X%02X%02X%02X" + "%02X%02X\n",uuid_print.timeLow, uuid_print.timeMid, + uuid_print.timeHiAndVersion, uuid_print.clockSeq, uuid_print.node[0], + uuid_print.node[1], uuid_print.node[2], uuid_print.node[3], + uuid_print.node[4], uuid_print.node[5]); + ALOGV("EffectCreate() UUID of OFFLOAD: %08X-%04X-%04X-%04X-%02X%02X%02X%02X" + "%02X%02X\n", uuid_print.timeLow, uuid_print.timeMid, + uuid_print.timeHiAndVersion, uuid_print.clockSeq, uuid_print.node[0], + uuid_print.node[1], uuid_print.node[2], uuid_print.node[3], + uuid_print.node[4], uuid_print.node[5]); +#endif + + pContext->replySize = PROXY_REPLY_SIZE_DEFAULT; + pContext->replyData = (char *)malloc(PROXY_REPLY_SIZE_DEFAULT); + + *pHandle = (effect_handle_t)pContext; + ALOGV("EffectCreate end"); + return 0; +} //end EffectProxyCreate + +int EffectProxyRelease(effect_handle_t handle) { + EffectContext * pContext = (EffectContext *)handle; + if (pContext == NULL) { + ALOGV("ERROR : EffectRelease called with NULL pointer"); + return -EINVAL; + } + ALOGV("EffectRelease"); + delete pContext->desc; + free(pContext->replyData); + + if (pContext->eHandle[SUB_FX_HOST]) + EffectRelease(pContext->eHandle[SUB_FX_HOST]); + if (pContext->eHandle[SUB_FX_OFFLOAD]) + EffectRelease(pContext->eHandle[SUB_FX_OFFLOAD]); + delete pContext; + pContext = NULL; + return 0; +} /*end EffectProxyRelease */ + +int EffectProxyGetDescriptor(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptor) { + const effect_descriptor_t *desc = NULL; + + if (pDescriptor == NULL || uuid == NULL) { + ALOGV("EffectGetDescriptor() called with NULL pointer"); + return -EINVAL; + } + desc = &gProxyDescriptor; + *pDescriptor = *desc; + return 0; +} /* end EffectProxyGetDescriptor */ + +/* Effect Control Interface Implementation: Process */ +int Effect_process(effect_handle_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer) { + + EffectContext *pContext = (EffectContext *) self; + int ret = 0; + if (pContext != NULL) { + int index = pContext->index; + // if the index refers to HW , do not do anything. Just return. + if (index == SUB_FX_HOST) { + ret = (*pContext->eHandle[index])->process(pContext->eHandle[index], + inBuffer, outBuffer); + } + } + return ret; +} /* end Effect_process */ + +/* Effect Control Interface Implementation: Command */ +int Effect_command(effect_handle_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData) { + + EffectContext *pContext = (EffectContext *) self; + int status = 0; + if (pContext == NULL) { + ALOGV("Effect_command() Proxy context is NULL"); + return -EINVAL; + } + if (pContext->eHandle[SUB_FX_HOST] == NULL) { + ALOGV("Effect_command() Calling HOST EffectCreate"); + status = EffectCreate(&pContext->desc[SUB_FX_HOST].uuid, + pContext->sessionId, pContext->ioId, + &(pContext->eHandle[SUB_FX_HOST])); + if (status != NO_ERROR || (pContext->eHandle[SUB_FX_HOST] == NULL)) { + ALOGV("Effect_command() Error creating SW sub effect"); + return status; + } + } + if (pContext->eHandle[SUB_FX_OFFLOAD] == NULL) { + ALOGV("Effect_command() Calling OFFLOAD EffectCreate"); + status = EffectCreate(&pContext->desc[SUB_FX_OFFLOAD].uuid, + pContext->sessionId, pContext->ioId, + &(pContext->eHandle[SUB_FX_OFFLOAD])); + if (status != NO_ERROR || (pContext->eHandle[SUB_FX_OFFLOAD] == NULL)) { + ALOGV("Effect_command() Error creating HW effect"); + // Do not return error here as SW effect is created + // Return error if the CMD_OFFLOAD sends the index as OFFLOAD + } + pContext->index = SUB_FX_HOST; + } + // EFFECT_CMD_OFFLOAD used to (1) send whether the thread is offload or not + // (2) Send the ioHandle of the effectThread when the effect + // is moved from one type of thread to another. + // pCmdData points to a memory holding effect_offload_param_t structure + if (cmdCode == EFFECT_CMD_OFFLOAD) { + ALOGV("Effect_command() cmdCode = EFFECT_CMD_OFFLOAD"); + if (cmdSize == 0 || pCmdData == NULL) { + ALOGV("effectsOffload: Effect_command: CMD_OFFLOAD has no data"); + *(int*)pReplyData = FAILED_TRANSACTION; + return FAILED_TRANSACTION; + } + effect_offload_param_t* offloadParam = (effect_offload_param_t*)pCmdData; + // Assign the effect context index based on isOffload field of the structure + pContext->index = offloadParam->isOffload ? SUB_FX_OFFLOAD : SUB_FX_HOST; + // if the index is HW and the HW effect is unavailable, return error + // and reset the index to SW + if (pContext->eHandle[pContext->index] == NULL) { + ALOGV("Effect_command()CMD_OFFLOAD sub effect unavailable"); + *(int*)pReplyData = FAILED_TRANSACTION; + return FAILED_TRANSACTION; + } + pContext->ioId = offloadParam->ioHandle; + ALOGV("Effect_command()CMD_OFFLOAD index:%d io %d", pContext->index, pContext->ioId); + // Update the DSP wrapper with the new ioHandle. + // Pass the OFFLOAD command to the wrapper. + // The DSP wrapper needs to handle this CMD + if (pContext->eHandle[SUB_FX_OFFLOAD]) + status = (*pContext->eHandle[SUB_FX_OFFLOAD])->command( + pContext->eHandle[SUB_FX_OFFLOAD], cmdCode, cmdSize, + pCmdData, replySize, pReplyData); + return status; + } + + int index = pContext->index; + if (index != SUB_FX_HOST && index != SUB_FX_OFFLOAD) { + ALOGV("Effect_command: effect index is neither offload nor host"); + return -EINVAL; + } + + // Getter commands are only sent to the active sub effect. + int *subStatus[SUB_FX_COUNT]; + uint32_t *subReplySize[SUB_FX_COUNT]; + void *subReplyData[SUB_FX_COUNT]; + uint32_t tmpSize; + int tmpStatus; + + // grow temp reply buffer if needed + if (replySize != NULL) { + tmpSize = pContext->replySize; + while (tmpSize < *replySize && tmpSize < PROXY_REPLY_SIZE_MAX) { + tmpSize *= 2; + } + if (tmpSize > pContext->replySize) { + ALOGV("Effect_command grow reply buf to %d", tmpSize); + pContext->replyData = (char *)realloc(pContext->replyData, tmpSize); + pContext->replySize = tmpSize; + } + if (tmpSize > *replySize) { + tmpSize = *replySize; + } + } else { + tmpSize = 0; + } + // tmpSize is now the actual reply size for the non active sub effect + + // Send command to sub effects. The command is sent to all sub effects so that their internal + // state is kept in sync. + // Only the reply from the active sub effect is returned to the caller. The reply from the + // other sub effect is lost in pContext->replyData + for (int i = 0; i < SUB_FX_COUNT; i++) { + if (pContext->eHandle[i] == NULL) { + continue; + } + if (i == index) { + subStatus[i] = &status; + subReplySize[i] = replySize; + subReplyData[i] = pReplyData; + } else { + subStatus[i] = &tmpStatus; + subReplySize[i] = replySize == NULL ? NULL : &tmpSize; + subReplyData[i] = pReplyData == NULL ? NULL : pContext->replyData; + } + *subStatus[i] = (*pContext->eHandle[i])->command( + pContext->eHandle[i], cmdCode, cmdSize, + pCmdData, subReplySize[i], subReplyData[i]); + } + + return status; +} /* end Effect_command */ + + +/* Effect Control Interface Implementation: get_descriptor */ +int Effect_getDescriptor(effect_handle_t self, + effect_descriptor_t *pDescriptor) { + + EffectContext * pContext = (EffectContext *) self; + const effect_descriptor_t *desc; + + ALOGV("Effect_getDescriptor"); + if (pContext == NULL || pDescriptor == NULL) { + ALOGV("Effect_getDescriptor() invalid param"); + return -EINVAL; + } + if (pContext->desc == NULL) { + ALOGV("Effect_getDescriptor() could not get descriptor"); + return -EINVAL; + } + desc = &pContext->desc[SUB_FX_HOST]; + *pDescriptor = *desc; + pDescriptor->uuid = pContext->uuid; // Replace the uuid with the Proxy UUID + // Also set/clear the EFFECT_FLAG_OFFLOAD_SUPPORTED flag based on the sub effects availability + if (pContext->eHandle[SUB_FX_OFFLOAD] != NULL) + pDescriptor->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED; + else + pDescriptor->flags &= ~EFFECT_FLAG_OFFLOAD_SUPPORTED; + return 0; +} /* end Effect_getDescriptor */ + +} // namespace android + +__attribute__ ((visibility ("default"))) +audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = { + tag : AUDIO_EFFECT_LIBRARY_TAG, + version : EFFECT_LIBRARY_API_VERSION, + name : "Effect Proxy", + implementor : "AOSP", + create_effect : android::EffectProxyCreate, + release_effect : android::EffectProxyRelease, + get_descriptor : android::EffectProxyGetDescriptor, +}; diff --git a/media/libeffects/proxy/EffectProxy.h b/media/libeffects/proxy/EffectProxy.h new file mode 100644 index 0000000..acbe17e --- /dev/null +++ b/media/libeffects/proxy/EffectProxy.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2013 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. + */ + +#include <hardware/audio.h> +#include <hardware/audio_effect.h> +namespace android { +enum { + SUB_FX_HOST, // Index of HOST in the descriptor and handle arrays + // of the Proxy context + SUB_FX_OFFLOAD, // Index of OFFLOAD in the descriptor and handle arrays + // of the Proxy context + SUB_FX_COUNT // The number of sub effects for a Proxy(1 HW, 1 SW) +}; +#if __cplusplus +extern "C" { +#endif + +int EffectProxyCreate(const effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_handle_t *pHandle); +int EffectProxyRelease(effect_handle_t handle); +int EffectProxyGetDescriptor(const effect_uuid_t *uuid, + effect_descriptor_t *pDescriptor); +/* Effect Control Interface Implementation: Process */ +int Effect_process(effect_handle_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer); + +/* Effect Control Interface Implementation: Command */ +int Effect_command(effect_handle_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); +int Effect_getDescriptor(effect_handle_t self, + effect_descriptor_t *pDescriptor); + +const struct effect_interface_s gEffectInterface = { + Effect_process, + Effect_command, + Effect_getDescriptor, + NULL, +}; + +#define PROXY_REPLY_SIZE_MAX (64 * 1024) // must be power of two +#define PROXY_REPLY_SIZE_DEFAULT 32 // must be power of two + +struct EffectContext { + const struct effect_interface_s *common_itfe; // Holds the itfe of the Proxy + effect_descriptor_t* desc; // Points to the sub effect descriptors + effect_handle_t eHandle[SUB_FX_COUNT]; // The effect handles of the sub effects + int index; // The index that is currently active - HOST or OFFLOAD + int32_t sessionId; // The sessiond in which the effect is created. + // Stored in context to pass on to sub effect creation + int32_t ioId; // The ioId in which the effect is created. + // Stored in context to pass on to sub effect creation + effect_uuid_t uuid; // UUID of the Proxy + char* replyData; // temporary buffer for non active sub effect command reply + uint32_t replySize; // current size of temporary reply buffer +}; + +#if __cplusplus +} // extern "C" +#endif +} //namespace android diff --git a/media/libeffects/testlibs/AudioFormatAdapter.h b/media/libeffects/testlibs/AudioFormatAdapter.h index 41f1810..dea2734 100644 --- a/media/libeffects/testlibs/AudioFormatAdapter.h +++ b/media/libeffects/testlibs/AudioFormatAdapter.h @@ -75,6 +75,7 @@ public: while (numSamples > 0) { uint32_t numSamplesIter = min(numSamples, mMaxSamplesPerCall); uint32_t nSamplesChannels = numSamplesIter * mNumChannels; + // This branch of "if" is untested if (mPcmFormat == AUDIO_FORMAT_PCM_8_24_BIT) { if (mBehavior == EFFECT_BUFFER_ACCESS_WRITE) { mpProcessor->process( diff --git a/media/libeffects/testlibs/EffectEqualizer.cpp b/media/libeffects/testlibs/EffectEqualizer.cpp index c35453b..8d00206 100644 --- a/media/libeffects/testlibs/EffectEqualizer.cpp +++ b/media/libeffects/testlibs/EffectEqualizer.cpp @@ -234,8 +234,7 @@ int Equalizer_setConfig(EqualizerContext *pContext, effect_config_t *pConfig) (pConfig->inputCfg.channels == AUDIO_CHANNEL_OUT_STEREO)); CHECK_ARG(pConfig->outputCfg.accessMode == EFFECT_BUFFER_ACCESS_WRITE || pConfig->outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE); - CHECK_ARG(pConfig->inputCfg.format == AUDIO_FORMAT_PCM_8_24_BIT - || pConfig->inputCfg.format == AUDIO_FORMAT_PCM_16_BIT); + CHECK_ARG(pConfig->inputCfg.format == AUDIO_FORMAT_PCM_16_BIT); int channelCount; if (pConfig->inputCfg.channels == AUDIO_CHANNEL_OUT_MONO) { diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp index 9f8139c..2d66eef 100644 --- a/media/libeffects/visualizer/EffectVisualizer.cpp +++ b/media/libeffects/visualizer/EffectVisualizer.cpp @@ -22,6 +22,7 @@ #include <string.h> #include <new> #include <time.h> +#include <math.h> #include <audio_effects/effect_visualizer.h> @@ -54,6 +55,18 @@ enum visualizer_state_e { #define CAPTURE_BUF_SIZE 65536 // "64k should be enough for everyone" +#define DISCARD_MEASUREMENTS_TIME_MS 2000 // discard measurements older than this number of ms + +// maximum number of buffers for which we keep track of the measurements +#define MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS 25 // note: buffer index is stored in uint8_t + + +struct BufferStats { + bool mIsValid; + uint16_t mPeakU16; // the positive peak of the absolute value of the samples in a buffer + float mRmsSquared; // the average square of the samples in a buffer +}; + struct VisualizerContext { const struct effect_interface_s *mItfe; effect_config_t mConfig; @@ -61,15 +74,38 @@ struct VisualizerContext { uint32_t mCaptureSize; uint32_t mScalingMode; uint8_t mState; - uint8_t mLastCaptureIdx; + uint32_t mLastCaptureIdx; uint32_t mLatency; struct timespec mBufferUpdateTime; uint8_t mCaptureBuf[CAPTURE_BUF_SIZE]; + // for measurements + uint8_t mChannelCount; // to avoid recomputing it every time a buffer is processed + uint32_t mMeasurementMode; + uint8_t mMeasurementWindowSizeInBuffers; + uint8_t mMeasurementBufferIdx; + BufferStats mPastMeasurements[MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS]; }; // //--- Local functions // +uint32_t Visualizer_getDeltaTimeMsFromUpdatedTime(VisualizerContext* pContext) { + uint32_t deltaMs = 0; + if (pContext->mBufferUpdateTime.tv_sec != 0) { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + time_t secs = ts.tv_sec - pContext->mBufferUpdateTime.tv_sec; + long nsec = ts.tv_nsec - pContext->mBufferUpdateTime.tv_nsec; + if (nsec < 0) { + --secs; + nsec += 1000000000; + } + deltaMs = secs * 1000 + nsec / 1000000; + } + } + return deltaMs; +} + void Visualizer_reset(VisualizerContext *pContext) { @@ -165,9 +201,21 @@ int Visualizer_init(VisualizerContext *pContext) pContext->mConfig.outputCfg.bufferProvider.cookie = NULL; pContext->mConfig.outputCfg.mask = EFFECT_CONFIG_ALL; + // visualization initialization pContext->mCaptureSize = VISUALIZER_CAPTURE_SIZE_MAX; pContext->mScalingMode = VISUALIZER_SCALING_MODE_NORMALIZED; + // measurement initialization + pContext->mChannelCount = popcount(pContext->mConfig.inputCfg.channels); + pContext->mMeasurementMode = MEASUREMENT_MODE_NONE; + pContext->mMeasurementWindowSizeInBuffers = MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS; + pContext->mMeasurementBufferIdx = 0; + for (uint32_t i=0 ; i<pContext->mMeasurementWindowSizeInBuffers ; i++) { + pContext->mPastMeasurements[i].mIsValid = false; + pContext->mPastMeasurements[i].mPeakU16 = 0; + pContext->mPastMeasurements[i].mRmsSquared = 0; + } + Visualizer_setConfig(pContext, &pContext->mConfig); return 0; @@ -270,6 +318,30 @@ int Visualizer_process( return -EINVAL; } + // perform measurements if needed + if (pContext->mMeasurementMode & MEASUREMENT_MODE_PEAK_RMS) { + // find the peak and RMS squared for the new buffer + uint32_t inIdx; + int16_t maxSample = 0; + float rmsSqAcc = 0; + for (inIdx = 0 ; inIdx < inBuffer->frameCount * pContext->mChannelCount ; inIdx++) { + if (inBuffer->s16[inIdx] > maxSample) { + maxSample = inBuffer->s16[inIdx]; + } else if (-inBuffer->s16[inIdx] > maxSample) { + maxSample = -inBuffer->s16[inIdx]; + } + rmsSqAcc += (inBuffer->s16[inIdx] * inBuffer->s16[inIdx]); + } + // store the measurement + pContext->mPastMeasurements[pContext->mMeasurementBufferIdx].mPeakU16 = (uint16_t)maxSample; + pContext->mPastMeasurements[pContext->mMeasurementBufferIdx].mRmsSquared = + rmsSqAcc / (inBuffer->frameCount * pContext->mChannelCount); + pContext->mPastMeasurements[pContext->mMeasurementBufferIdx].mIsValid = true; + if (++pContext->mMeasurementBufferIdx >= pContext->mMeasurementWindowSizeInBuffers) { + pContext->mMeasurementBufferIdx = 0; + } + } + // all code below assumes stereo 16 bit PCM output and input int32_t shift; @@ -423,6 +495,12 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, p->vsize = sizeof(uint32_t); *replySize += sizeof(uint32_t); break; + case VISUALIZER_PARAM_MEASUREMENT_MODE: + ALOGV("get mMeasurementMode = %d", pContext->mMeasurementMode); + *((uint32_t *)p->data + 1) = pContext->mMeasurementMode; + p->vsize = sizeof(uint32_t); + *replySize += sizeof(uint32_t); + break; default: p->status = -EINVAL; } @@ -452,6 +530,10 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, pContext->mLatency = *((uint32_t *)p->data + 1); ALOGV("set mLatency = %d", pContext->mLatency); break; + case VISUALIZER_PARAM_MEASUREMENT_MODE: + pContext->mMeasurementMode = *((uint32_t *)p->data + 1); + ALOGV("set mMeasurementMode = %d", pContext->mMeasurementMode); + break; default: *(int32_t *)pReplyData = -EINVAL; } @@ -470,24 +552,12 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, } if (pContext->mState == VISUALIZER_STATE_ACTIVE) { int32_t latencyMs = pContext->mLatency; - uint32_t deltaMs = 0; - if (pContext->mBufferUpdateTime.tv_sec != 0) { - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { - time_t secs = ts.tv_sec - pContext->mBufferUpdateTime.tv_sec; - long nsec = ts.tv_nsec - pContext->mBufferUpdateTime.tv_nsec; - if (nsec < 0) { - --secs; - nsec += 1000000000; - } - deltaMs = secs * 1000 + nsec / 1000000; - latencyMs -= deltaMs; - if (latencyMs < 0) { - latencyMs = 0; - } - } + const uint32_t deltaMs = Visualizer_getDeltaTimeMsFromUpdatedTime(pContext); + latencyMs -= deltaMs; + if (latencyMs < 0) { + latencyMs = 0; } - uint32_t deltaSmpl = pContext->mConfig.inputCfg.samplingRate * latencyMs / 1000; + const uint32_t deltaSmpl = pContext->mConfig.inputCfg.samplingRate * latencyMs / 1000; int32_t capturePoint = pContext->mCaptureIdx - pContext->mCaptureSize - deltaSmpl; int32_t captureSize = pContext->mCaptureSize; @@ -499,7 +569,7 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, memcpy(pReplyData, pContext->mCaptureBuf + CAPTURE_BUF_SIZE + capturePoint, size); - pReplyData += size; + pReplyData = (char *)pReplyData + size; captureSize -= size; capturePoint = 0; } @@ -525,6 +595,54 @@ int Visualizer_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize, break; + case VISUALIZER_CMD_MEASURE: { + uint16_t peakU16 = 0; + float sumRmsSquared = 0.0f; + uint8_t nbValidMeasurements = 0; + // reset measurements if last measurement was too long ago (which implies stored + // measurements aren't relevant anymore and shouldn't bias the new one) + const int32_t delayMs = Visualizer_getDeltaTimeMsFromUpdatedTime(pContext); + if (delayMs > DISCARD_MEASUREMENTS_TIME_MS) { + ALOGV("Discarding measurements, last measurement is %dms old", delayMs); + for (uint32_t i=0 ; i<pContext->mMeasurementWindowSizeInBuffers ; i++) { + pContext->mPastMeasurements[i].mIsValid = false; + pContext->mPastMeasurements[i].mPeakU16 = 0; + pContext->mPastMeasurements[i].mRmsSquared = 0; + } + pContext->mMeasurementBufferIdx = 0; + } else { + // only use actual measurements, otherwise the first RMS measure happening before + // MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS have been played will always be artificially + // low + for (uint32_t i=0 ; i < pContext->mMeasurementWindowSizeInBuffers ; i++) { + if (pContext->mPastMeasurements[i].mIsValid) { + if (pContext->mPastMeasurements[i].mPeakU16 > peakU16) { + peakU16 = pContext->mPastMeasurements[i].mPeakU16; + } + sumRmsSquared += pContext->mPastMeasurements[i].mRmsSquared; + nbValidMeasurements++; + } + } + } + float rms = nbValidMeasurements == 0 ? 0.0f : sqrtf(sumRmsSquared / nbValidMeasurements); + int32_t* pIntReplyData = (int32_t*)pReplyData; + // convert from I16 sample values to mB and write results + if (rms < 0.000016f) { + pIntReplyData[MEASUREMENT_IDX_RMS] = -9600; //-96dB + } else { + pIntReplyData[MEASUREMENT_IDX_RMS] = (int32_t) (2000 * log10(rms / 32767.0f)); + } + if (peakU16 == 0) { + pIntReplyData[MEASUREMENT_IDX_PEAK] = -9600; //-96dB + } else { + pIntReplyData[MEASUREMENT_IDX_PEAK] = (int32_t) (2000 * log10(peakU16 / 32767.0f)); + } + ALOGV("VISUALIZER_CMD_MEASURE peak=%d (%dmB), rms=%.1f (%dmB)", + peakU16, pIntReplyData[MEASUREMENT_IDX_PEAK], + rms, pIntReplyData[MEASUREMENT_IDX_RMS]); + } + break; + default: ALOGW("Visualizer_command invalid command %d",cmdCode); return -EINVAL; |