Frequency Domain implementation of Dynamics Processing Effect
Ported the frequency domain implementation of the effect from the
development sandbox.
Bug: 64161702
Bug: 38266419
Test: manual with Triton app. Cts tests. Listening tests.
Change-Id: I9417beba2f98f2a677f0857c7976bf76a0e0d8e0
diff --git a/media/libeffects/dynamicsproc/Android.mk b/media/libeffects/dynamicsproc/Android.mk
index a27a2e7..7be0c49 100644
--- a/media/libeffects/dynamicsproc/Android.mk
+++ b/media/libeffects/dynamicsproc/Android.mk
@@ -18,9 +18,14 @@
include $(CLEAR_VARS)
LOCAL_VENDOR_MODULE := true
+
+EIGEN_PATH := external/eigen
+LOCAL_C_INCLUDES += $(EIGEN_PATH)
+
LOCAL_SRC_FILES:= \
EffectDynamicsProcessing.cpp \
- dsp/DPBase.cpp
+ dsp/DPBase.cpp \
+ dsp/DPFrequency.cpp
LOCAL_CFLAGS+= -O2 -fvisibility=hidden
LOCAL_CFLAGS += -Wall -Werror
diff --git a/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp b/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp
index 56cd247..55383eb 100644
--- a/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp
+++ b/media/libeffects/dynamicsproc/EffectDynamicsProcessing.cpp
@@ -28,6 +28,7 @@
#include <audio_effects/effect_dynamicsprocessing.h>
#include <dsp/DPBase.h>
+#include <dsp/DPFrequency.h>
//#define VERY_VERY_VERBOSE_LOGGING
#ifdef VERY_VERY_VERBOSE_LOGGING
@@ -186,7 +187,7 @@
pContext->mConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
pContext->mConfig.inputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
pContext->mConfig.inputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
- pContext->mConfig.inputCfg.samplingRate = 44100;
+ pContext->mConfig.inputCfg.samplingRate = 48000;
pContext->mConfig.inputCfg.bufferProvider.getBuffer = NULL;
pContext->mConfig.inputCfg.bufferProvider.releaseBuffer = NULL;
pContext->mConfig.inputCfg.bufferProvider.cookie = NULL;
@@ -194,7 +195,7 @@
pContext->mConfig.outputCfg.accessMode = EFFECT_BUFFER_ACCESS_ACCUMULATE;
pContext->mConfig.outputCfg.channels = AUDIO_CHANNEL_OUT_STEREO;
pContext->mConfig.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
- pContext->mConfig.outputCfg.samplingRate = 44100;
+ pContext->mConfig.outputCfg.samplingRate = 48000;
pContext->mConfig.outputCfg.bufferProvider.getBuffer = NULL;
pContext->mConfig.outputCfg.bufferProvider.releaseBuffer = NULL;
pContext->mConfig.outputCfg.bufferProvider.cookie = NULL;
@@ -209,17 +210,52 @@
}
void DP_changeVariant(DynamicsProcessingContext *pContext, int newVariant) {
- if (pContext->mPDynamics != NULL) {
- delete pContext->mPDynamics;
- pContext->mPDynamics = NULL;
- }
+ ALOGV("DP_changeVariant from %d to %d", pContext->mCurrentVariant, newVariant);
switch(newVariant) {
- //TODO: actually instantiate one of the variants. For now all instantiate the base;
- default:
- pContext->mCurrentVariant = newVariant;
- pContext->mPDynamics = new dp_fx::DPBase();
+ case VARIANT_FAVOR_FREQUENCY_RESOLUTION: {
+ pContext->mCurrentVariant = VARIANT_FAVOR_FREQUENCY_RESOLUTION;
+ delete pContext->mPDynamics;
+ pContext->mPDynamics = new dp_fx::DPFrequency();
break;
}
+ default: {
+ ALOGW("DynamicsProcessing variant %d not available for creation", newVariant);
+ break;
+ }
+ } //switch
+}
+
+static inline bool isPowerOf2(unsigned long n) {
+ return (n & (n - 1)) == 0;
+}
+
+void DP_configureVariant(DynamicsProcessingContext *pContext, int newVariant) {
+ ALOGV("DP_configureVariant %d", newVariant);
+ switch(newVariant) {
+ case VARIANT_FAVOR_FREQUENCY_RESOLUTION: {
+ int32_t minBlockSize = (int32_t)dp_fx::DPFrequency::getMinBockSize();
+ int32_t desiredBlock = pContext->mPreferredFrameDuration *
+ pContext->mConfig.inputCfg.samplingRate / 1000.0f;
+ int32_t currentBlock = desiredBlock;
+ ALOGV(" sampling rate: %d, desiredBlock size %0.2f (%d) samples",
+ pContext->mConfig.inputCfg.samplingRate, pContext->mPreferredFrameDuration,
+ desiredBlock);
+ if (desiredBlock < minBlockSize) {
+ currentBlock = minBlockSize;
+ } else if (!isPowerOf2(desiredBlock)) {
+ //find next highest power of 2.
+ currentBlock = 1 << (32 - __builtin_clz(desiredBlock));
+ }
+ ((dp_fx::DPFrequency*)pContext->mPDynamics)->configure(currentBlock,
+ currentBlock/2,
+ pContext->mConfig.inputCfg.samplingRate);
+ break;
+ }
+ default: {
+ ALOGE("DynamicsProcessing variant %d not available to configure", newVariant);
+ break;
+ }
+ }
}
//
@@ -312,6 +348,7 @@
pContext->mConfig.inputCfg.channels);
pContext->mPDynamics->processSamples(inBuffer->f32, inBuffer->f32,
inBuffer->frameCount * channelCount);
+
if (inBuffer->raw != outBuffer->raw) {
if (pContext->mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
for (size_t i = 0; i < outBuffer->frameCount * channelCount; i++) {
@@ -518,7 +555,8 @@
if (pChannel == NULL) {
return NULL;
}
- dp_fx::DPEq *pEq = eqType == DP_PARAM_PRE_EQ ? pChannel->getPreEq() : pChannel->getPostEq();
+ dp_fx::DPEq *pEq = (eqType == DP_PARAM_PRE_EQ ? pChannel->getPreEq() :
+ (eqType == DP_PARAM_POST_EQ ? pChannel->getPostEq() : NULL));
ALOGE_IF(pEq == NULL,"DPEq NULL invalid eq");
return pEq;
}
@@ -699,8 +737,10 @@
// eqBand.getGain()};
const int32_t channel = params[1];
const int32_t band = params[2];
+ int eqCommand = (command == DP_PARAM_PRE_EQ_BAND ? DP_PARAM_PRE_EQ :
+ (command == DP_PARAM_POST_EQ_BAND ? DP_PARAM_POST_EQ : -1));
- dp_fx::DPEqBand *pEqBand = DP_getEqBand(pContext, channel, command, band);
+ dp_fx::DPEqBand *pEqBand = DP_getEqBand(pContext, channel, eqCommand, band);
if (pEqBand == NULL) {
ALOGE("%s get PARAM_*_EQ_BAND invalid channel %d or band %d", __func__, channel, band);
status = -EINVAL;
@@ -923,6 +963,8 @@
mbcInUse != 0, (uint32_t)mbcBandCount,
postEqInUse != 0, (uint32_t)postEqBandCount,
limiterInUse != 0);
+
+ DP_configureVariant(pContext, variant);
break;
}
case DP_PARAM_INPUT_GAIN: {
@@ -1015,7 +1057,9 @@
(command == DP_PARAM_PRE_EQ_BAND ? "preEqBand" : "postEqBand"), channel, band,
enabled, cutoffFrequency, gain);
- dp_fx::DPEq *pEq = DP_getEq(pContext, channel, command);
+ int eqCommand = (command == DP_PARAM_PRE_EQ_BAND ? DP_PARAM_PRE_EQ :
+ (command == DP_PARAM_POST_EQ_BAND ? DP_PARAM_POST_EQ : -1));
+ dp_fx::DPEq *pEq = DP_getEq(pContext, channel, eqCommand);
if (pEq == NULL) {
ALOGE("%s set PARAM_*_EQ_BAND invalid channel %d or command %d", __func__, channel,
command);
diff --git a/media/libeffects/dynamicsproc/dsp/DPBase.cpp b/media/libeffects/dynamicsproc/dsp/DPBase.cpp
index 30c2c36..8b79991 100644
--- a/media/libeffects/dynamicsproc/dsp/DPBase.cpp
+++ b/media/libeffects/dynamicsproc/dsp/DPBase.cpp
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-#include <android/log.h>
+#define LOG_TAG "DPBase"
+//#define LOG_NDEBUG 0
+
+#include <log/log.h>
#include "DPBase.h"
+#include "DPFrequency.h"
namespace dp_fx {
@@ -234,6 +238,7 @@
void DPBase::init(uint32_t channelCount, bool preEqInUse, uint32_t preEqBandCount,
bool mbcInUse, uint32_t mbcBandCount, bool postEqInUse, uint32_t postEqBandCount,
bool limiterInUse) {
+ ALOGV("DPBase::init");
mChannelCount = channelCount;
mPreEqInUse = preEqInUse;
mPreEqBandCount = preEqBandCount;
@@ -250,31 +255,6 @@
mInitialized = true;
}
-void DPBase::reset() {
- //perform reset operations with current architecture.
-}
-
-size_t DPBase::processSamples(float *in, float *out, size_t samples) {
- //actually do something with samples, for now, just apply level.
- uint32_t channelCount = getChannelCount();
- std::vector<float> level(channelCount);
- for (uint32_t ch = 0; ch < channelCount; ch++) {
- DPChannel *pChannel = getChannel(ch);
- if (pChannel != NULL) {
- level[ch] = pow(10, pChannel->getInputGain() / 20.0);
- }
- }
- size_t processedSamples = 0;
- float *pInput = in;
- float *pOutput = out;
- for (size_t k = 0; k < samples; k++) {
- float value = *pInput++;
- *pOutput++ = value * level[k % channelCount];
- processedSamples++;
- }
- return processedSamples;
-}
-
DPChannel* DPBase::getChannel(uint32_t channelIndex) {
if (!mInitialized || channelIndex < 0 || channelIndex >= mChannel.size()) {
return NULL;
diff --git a/media/libeffects/dynamicsproc/dsp/DPBase.h b/media/libeffects/dynamicsproc/dsp/DPBase.h
index 52593ef..355f64b 100644
--- a/media/libeffects/dynamicsproc/dsp/DPBase.h
+++ b/media/libeffects/dynamicsproc/dsp/DPBase.h
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#ifndef DPBASE_H_
+#define DPBASE_H_
+
+
#include <stdint.h>
#include <cmath>
#include <vector>
@@ -297,8 +301,8 @@
void init(uint32_t channelCount, bool preEqInUse, uint32_t preEqBandCount,
bool mbcInUse, uint32_t mbcBandCount, bool postEqInUse, uint32_t postEqBandCount,
bool limiterInUse);
- virtual size_t processSamples(float *in, float *out, size_t samples);
- virtual void reset();
+ virtual size_t processSamples(const float *in, float *out, size_t samples) = 0;
+ virtual void reset() = 0;
DPChannel* getChannel(uint32_t channelIndex);
uint32_t getChannelCount() const {
@@ -342,3 +346,6 @@
};
} //namespace dp_fx
+
+
+#endif // DPBASE_H_
diff --git a/media/libeffects/dynamicsproc/dsp/DPFrequency.cpp b/media/libeffects/dynamicsproc/dsp/DPFrequency.cpp
new file mode 100644
index 0000000..59195fc
--- /dev/null
+++ b/media/libeffects/dynamicsproc/dsp/DPFrequency.cpp
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2018 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 "DPFrequency"
+//#define LOG_NDEBUG 0
+
+#include <log/log.h>
+#include "DPFrequency.h"
+#include <algorithm>
+
+namespace dp_fx {
+
+using Eigen::MatrixXd;
+#define MAX_BLOCKSIZE 16384 //For this implementation
+#define MIN_BLOCKSIZE 8
+
+#define CIRCULAR_BUFFER_UPSAMPLE 4 //4 times buffer size
+
+static constexpr float MIN_ENVELOPE = 0.000001f;
+//helper functionS
+static inline bool isPowerOf2(unsigned long n) {
+ return (n & (n - 1)) == 0;
+}
+static constexpr float EPSILON = 0.0000001f;
+
+static inline bool isZero(float f) {
+ return fabs(f) <= EPSILON;
+}
+
+template <class T>
+bool compareEquality(T a, T b) {
+ return (a == b);
+}
+
+template <> bool compareEquality<float>(float a, float b) {
+ return isZero(a - b);
+}
+
+//TODO: avoid using macro for estimating change and assignment.
+#define IS_CHANGED(c, a, b) { c |= !compareEquality(a,b); \
+ (a) = (b); }
+
+float dBtoLinear(float valueDb) {
+ return pow (10, valueDb / 20.0);
+}
+
+float linearToDb(float value) {
+ return 20 * log10(value);
+}
+
+//ChannelBuffers helper
+void ChannelBuffer::initBuffers(unsigned int blockSize, unsigned int overlapSize,
+ unsigned int halfFftSize, unsigned int samplingRate, DPBase &dpBase) {
+ ALOGV("ChannelBuffer::initBuffers blockSize %d, overlap %d, halfFft %d",
+ blockSize, overlapSize, halfFftSize);
+
+ mSamplingRate = samplingRate;
+ mBlockSize = blockSize;
+
+ cBInput.resize(mBlockSize * CIRCULAR_BUFFER_UPSAMPLE);
+ cBOutput.resize(mBlockSize * CIRCULAR_BUFFER_UPSAMPLE);
+
+ //fill input with half block size...
+ for (unsigned int k = 0; k < mBlockSize/2; k++) {
+ cBInput.write(0);
+ }
+
+ //temp vectors
+ input.resize(mBlockSize);
+ output.resize(mBlockSize);
+ outTail.resize(overlapSize);
+
+ //module vectors
+ mPreEqFactorVector.resize(halfFftSize, 1.0);
+ mPostEqFactorVector.resize(halfFftSize, 1.0);
+
+ mPreEqBands.resize(dpBase.getPreEqBandCount());
+ mMbcBands.resize(dpBase.getMbcBandCount());
+ mPostEqBands.resize(dpBase.getPostEqBandCount());
+ ALOGV("mPreEqBands %zu, mMbcBands %zu, mPostEqBands %zu",mPreEqBands.size(),
+ mMbcBands.size(), mPostEqBands.size());
+
+ DPChannel *pChannel = dpBase.getChannel(0);
+ if (pChannel != NULL) {
+ mPreEqInUse = pChannel->getPreEq()->isInUse();
+ mMbcInUse = pChannel->getMbc()->isInUse();
+ mPostEqInUse = pChannel->getPostEq()->isInUse();
+ mLimiterInUse = pChannel->getLimiter()->isInUse();
+ }
+}
+
+void ChannelBuffer::computeBinStartStop(BandParams &bp, size_t binStart) {
+
+ bp.binStart = binStart;
+ bp.binStop = (int)(0.5 + bp.freqCutoffHz * mBlockSize / mSamplingRate);
+}
+
+//== DPFrequency
+
+void DPFrequency::reset() {
+}
+
+size_t DPFrequency::getMinBockSize() {
+ return MIN_BLOCKSIZE;
+}
+
+size_t DPFrequency::getMaxBockSize() {
+ return MAX_BLOCKSIZE;
+}
+
+void DPFrequency::configure(size_t blockSize, size_t overlapSize,
+ size_t samplingRate) {
+ ALOGV("configure");
+ mBlockSize = blockSize;
+ if (mBlockSize > MAX_BLOCKSIZE) {
+ mBlockSize = MAX_BLOCKSIZE;
+ } else if (mBlockSize < MIN_BLOCKSIZE) {
+ mBlockSize = MIN_BLOCKSIZE;
+ } else {
+ if (!isPowerOf2(blockSize)) {
+ //find next highest power of 2.
+ mBlockSize = 1 << (32 - __builtin_clz(blockSize));
+ }
+ }
+
+ mHalfFFTSize = 1 + mBlockSize / 2; //including Nyquist bin
+ mOverlapSize = std::min(overlapSize, mBlockSize/2);
+
+ int channelcount = getChannelCount();
+ mSamplingRate = samplingRate;
+ mChannelBuffers.resize(channelcount);
+ for (int ch = 0; ch < channelcount; ch++) {
+ mChannelBuffers[ch].initBuffers(mBlockSize, mOverlapSize, mHalfFFTSize,
+ mSamplingRate, *this);
+ }
+
+ //dsp
+ fill_window(mVWindow, RDSP_WINDOW_HANNING_FLAT_TOP, mBlockSize, mOverlapSize);
+}
+
+void DPFrequency::updateParameters(ChannelBuffer &cb, int channelIndex) {
+ DPChannel *pChannel = getChannel(channelIndex);
+
+ if (pChannel == NULL) {
+ ALOGE("Error: updateParameters null DPChannel %d", channelIndex);
+ return;
+ }
+
+ //===Input Gain and preEq
+ {
+ bool changed = false;
+ IS_CHANGED(changed, cb.inputGainDb, pChannel->getInputGain());
+ //===EqPre
+ if (cb.mPreEqInUse) {
+ DPEq *pPreEq = pChannel->getPreEq();
+ if (pPreEq == NULL) {
+ ALOGE("Error: updateParameters null PreEq for channel: %d", channelIndex);
+ return;
+ }
+ IS_CHANGED(changed, cb.mPreEqEnabled, pPreEq->isEnabled());
+ if (cb.mPreEqEnabled) {
+ for (unsigned int b = 0; b < getPreEqBandCount(); b++) {
+ DPEqBand *pEqBand = pPreEq->getBand(b);
+ if (pEqBand == NULL) {
+ ALOGE("Error: updateParameters null PreEqBand for band %d", b);
+ return; //failed.
+ }
+ ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPreEqBands[b];
+ IS_CHANGED(changed, pEqBandParams->enabled, pEqBand->isEnabled());
+ IS_CHANGED(changed, pEqBandParams->freqCutoffHz,
+ pEqBand->getCutoffFrequency());
+ IS_CHANGED(changed, pEqBandParams->gainDb, pEqBand->getGain());
+ }
+ }
+ }
+
+ if (changed) {
+ float inputGainFactor = dBtoLinear(cb.inputGainDb);
+ if (cb.mPreEqInUse && cb.mPreEqEnabled) {
+ ALOGV("preEq changed, recomputing! channel %d", channelIndex);
+ size_t binNext = 0;
+ for (unsigned int b = 0; b < getPreEqBandCount(); b++) {
+ ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPreEqBands[b];
+
+ //frequency translation
+ cb.computeBinStartStop(*pEqBandParams, binNext);
+ binNext = pEqBandParams->binStop + 1;
+ float factor = dBtoLinear(pEqBandParams->gainDb);
+ if (!pEqBandParams->enabled) {
+ factor = inputGainFactor;
+ }
+ for (size_t k = pEqBandParams->binStart;
+ k <= pEqBandParams->binStop && k < mHalfFFTSize; k++) {
+ cb.mPreEqFactorVector[k] = factor * inputGainFactor;
+ }
+ }
+ } else {
+ ALOGV("only input gain changed, recomputing!");
+ //populate PreEq factor with input gain factor.
+ for (size_t k = 0; k < mHalfFFTSize; k++) {
+ cb.mPreEqFactorVector[k] = inputGainFactor;
+ }
+ }
+ }
+ } //inputGain and preEq
+
+ //===EqPost
+ if (cb.mPostEqInUse) {
+ bool changed = false;
+
+ DPEq *pPostEq = pChannel->getPostEq();
+ if (pPostEq == NULL) {
+ ALOGE("Error: updateParameters null postEq for channel: %d", channelIndex);
+ return; //failed.
+ }
+ IS_CHANGED(changed, cb.mPostEqEnabled, pPostEq->isEnabled());
+ if (cb.mPostEqEnabled) {
+ for (unsigned int b = 0; b < getPostEqBandCount(); b++) {
+ DPEqBand *pEqBand = pPostEq->getBand(b);
+ if (pEqBand == NULL) {
+ ALOGE("Error: updateParameters PostEqBand NULL for band %d", b);
+ return; //failed.
+ }
+ ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPostEqBands[b];
+ IS_CHANGED(changed, pEqBandParams->enabled, pEqBand->isEnabled());
+ IS_CHANGED(changed, pEqBandParams->freqCutoffHz,
+ pEqBand->getCutoffFrequency());
+ IS_CHANGED(changed, pEqBandParams->gainDb, pEqBand->getGain());
+ }
+ if (changed) {
+ ALOGV("postEq changed, recomputing! channel %d", channelIndex);
+ size_t binNext = 0;
+ for (unsigned int b = 0; b < getPostEqBandCount(); b++) {
+ ChannelBuffer::EqBandParams *pEqBandParams = &cb.mPostEqBands[b];
+
+ //frequency translation
+ cb.computeBinStartStop(*pEqBandParams, binNext);
+ binNext = pEqBandParams->binStop + 1;
+ float factor = dBtoLinear(pEqBandParams->gainDb);
+ if (!pEqBandParams->enabled) {
+ factor = 1.0;
+ }
+ for (size_t k = pEqBandParams->binStart;
+ k <= pEqBandParams->binStop && k < mHalfFFTSize; k++) {
+ cb.mPostEqFactorVector[k] = factor;
+ }
+ }
+ }
+ } //enabled
+ }
+
+ //===MBC
+ if (cb.mMbcInUse) {
+ DPMbc *pMbc = pChannel->getMbc();
+ if (pMbc == NULL) {
+ ALOGE("Error: updateParameters Mbc NULL for channel: %d", channelIndex);
+ return;
+ }
+ cb.mMbcEnabled = pMbc->isEnabled();
+ if (cb.mMbcEnabled) {
+ bool changed = false;
+ for (unsigned int b = 0; b < getMbcBandCount(); b++) {
+ DPMbcBand *pMbcBand = pMbc->getBand(b);
+ if (pMbcBand == NULL) {
+ ALOGE("Error: updateParameters MbcBand NULL for band %d", b);
+ return; //failed.
+ }
+ ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[b];
+ pMbcBandParams->enabled = pMbcBand->isEnabled();
+ IS_CHANGED(changed, pMbcBandParams->freqCutoffHz,
+ pMbcBand->getCutoffFrequency());
+
+ pMbcBandParams->gainPreDb = pMbcBand->getPreGain();
+ pMbcBandParams->gainPostDb = pMbcBand->getPostGain();
+ pMbcBandParams->attackTimeMs = pMbcBand->getAttackTime();
+ pMbcBandParams->releaseTimeMs = pMbcBand->getReleaseTime();
+ pMbcBandParams->ratio = pMbcBand->getRatio();
+ pMbcBandParams->thresholdDb = pMbcBand->getThreshold();
+ pMbcBandParams->kneeWidthDb = pMbcBand->getKneeWidth();
+ pMbcBandParams->noiseGateThresholdDb = pMbcBand->getNoiseGateThreshold();
+ pMbcBandParams->expanderRatio = pMbcBand->getExpanderRatio();
+
+ }
+
+ if (changed) {
+ ALOGV("mbc changed, recomputing! channel %d", channelIndex);
+ size_t binNext= 0;
+ for (unsigned int b = 0; b < getMbcBandCount(); b++) {
+ ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[b];
+
+ pMbcBandParams->previousEnvelope = 0;
+
+ //frequency translation
+ cb.computeBinStartStop(*pMbcBandParams, binNext);
+ binNext = pMbcBandParams->binStop + 1;
+ }
+
+ }
+
+ }
+ }
+}
+
+size_t DPFrequency::processSamples(const float *in, float *out, size_t samples) {
+ const float *pIn = in;
+ float *pOut = out;
+
+ int channelCount = mChannelBuffers.size();
+ if (channelCount < 1) {
+ ALOGW("warning: no Channels ready for processing");
+ return 0;
+ }
+
+ //**Check if parameters have changed and update
+ for (int ch = 0; ch < channelCount; ch++) {
+ updateParameters(mChannelBuffers[ch], ch);
+ }
+
+ //**separate into channels
+ for (size_t k = 0; k < samples; k += channelCount) {
+ for (int ch = 0; ch < channelCount; ch++) {
+ mChannelBuffers[ch].cBInput.write(*pIn++);
+ }
+ }
+
+ //TODO: lookahead limiters
+ //TODO: apply linked limiters to all channels.
+ //**Process each Channel
+ for (int ch = 0; ch < channelCount; ch++) {
+ processMono(mChannelBuffers[ch]);
+ }
+
+ //** estimate how much data is available in ALL channels
+ size_t available = mChannelBuffers[0].cBOutput.availableToRead();
+ for (int ch = 1; ch < channelCount; ch++) {
+ available = std::min(available, mChannelBuffers[ch].cBOutput.availableToRead());
+ }
+
+ //** make sure to output just what the buffer can handle
+ if (available > samples/channelCount) {
+ available = samples/channelCount;
+ }
+
+ //**Prepend zeroes if necessary
+ size_t fill = samples - (channelCount * available);
+ for (size_t k = 0; k < fill; k++) {
+ *pOut++ = 0;
+ }
+
+ //**interleave channels
+ for (size_t k = 0; k < available; k++) {
+ for (int ch = 0; ch < channelCount; ch++) {
+ *pOut++ = mChannelBuffers[ch].cBOutput.read();
+ }
+ }
+
+ return samples;
+}
+
+size_t DPFrequency::processMono(ChannelBuffer &cb) {
+
+ size_t processedSamples = 0;
+
+ size_t available = cb.cBInput.availableToRead();
+ while (available >= mBlockSize - mOverlapSize) {
+
+ //move tail of previous
+ for (unsigned int k = 0; k < mOverlapSize; ++k) {
+ cb.input[k] = cb.input[mBlockSize - mOverlapSize + k];
+ }
+
+ //read new available data
+ for (unsigned int k = 0; k < mBlockSize - mOverlapSize; k++) {
+ cb.input[mOverlapSize + k] = cb.cBInput.read();
+ }
+
+ //## Actual process
+ processOneVector(cb.output, cb.input, cb);
+ //##End of Process
+
+ //mix tail (and capture new tail
+ for (unsigned int k = 0; k < mOverlapSize; k++) {
+ cb.output[k] += cb.outTail[k];
+ cb.outTail[k] = cb.output[mBlockSize - mOverlapSize + k]; //new tail
+ }
+
+ //output data
+ for (unsigned int k = 0; k < mBlockSize - mOverlapSize; k++) {
+ cb.cBOutput.write(cb.output[k]);
+ }
+
+ available = cb.cBInput.availableToRead();
+ }
+
+ return processedSamples;
+}
+
+size_t DPFrequency::processOneVector(FloatVec & output, FloatVec & input,
+ ChannelBuffer &cb) {
+
+ //##apply window
+ Eigen::Map<Eigen::VectorXf> eWindow(&mVWindow[0], mVWindow.size());
+ Eigen::Map<Eigen::VectorXf> eInput(&input[0], input.size());
+
+ Eigen::VectorXf eWin = eInput.cwiseProduct(eWindow); //apply window
+
+ //##fft //TODO: refactor frequency transformations away from other stages.
+ mFftServer.fwd(mComplexTemp, eWin);
+
+ size_t cSize = mComplexTemp.size();
+ size_t maxBin = std::min(cSize/2, mHalfFFTSize);
+
+ //== EqPre (always runs)
+ for (size_t k = 0; k < maxBin; k++) {
+ mComplexTemp[k] *= cb.mPreEqFactorVector[k];
+ }
+
+ //== MBC
+ if (cb.mMbcInUse && cb.mMbcEnabled) {
+ for (size_t band = 0; band < cb.mMbcBands.size(); band++) {
+ ChannelBuffer::MbcBandParams *pMbcBandParams = &cb.mMbcBands[band];
+ float fEnergySum = 0;
+
+ //apply pre gain.
+ float preGainFactor = dBtoLinear(pMbcBandParams->gainPreDb);
+ float preGainSquared = preGainFactor * preGainFactor;
+
+ for (size_t k = pMbcBandParams->binStart; k <= pMbcBandParams->binStop; k++) {
+ float fReal = mComplexTemp[k].real();
+ float fImag = mComplexTemp[k].imag();
+ float fSquare = (fReal * fReal + fImag * fImag) * preGainSquared;
+
+ fEnergySum += fSquare;
+ }
+
+ fEnergySum = sqrt(fEnergySum /2.0);
+ float fTheta = 0.0;
+ float fFAtt = pMbcBandParams->attackTimeMs;
+ float fFRel = pMbcBandParams->releaseTimeMs;
+
+ float fUpdatesPerSecond = 10; //TODO: compute from framerate
+
+
+ if (fEnergySum > pMbcBandParams->previousEnvelope) {
+ fTheta = exp(-1.0 / (fFAtt * fUpdatesPerSecond));
+ } else {
+ fTheta = exp(-1.0 / (fFRel * fUpdatesPerSecond));
+ }
+
+ float fEnv = (1.0 - fTheta) * fEnergySum + fTheta * pMbcBandParams->previousEnvelope;
+
+ //preserve for next iteration
+ pMbcBandParams->previousEnvelope = fEnv;
+
+ float fThreshold = dBtoLinear(pMbcBandParams->thresholdDb);
+ float fNoiseGateThreshold = dBtoLinear(pMbcBandParams->noiseGateThresholdDb);
+
+ float fNewFactor = 1.0;
+
+ if (fEnv > fThreshold) {
+ float fDbAbove = linearToDb(fThreshold / fEnv);
+ float fDbTarget = fDbAbove / pMbcBandParams->ratio;
+ float fDbChange = fDbAbove - fDbTarget;
+ fNewFactor = dBtoLinear(fDbChange);
+ } else if (fEnv < fNoiseGateThreshold) {
+ if (fEnv < MIN_ENVELOPE) {
+ fEnv = MIN_ENVELOPE;
+ }
+ float fDbBelow = linearToDb(fNoiseGateThreshold / fEnv);
+ float fDbTarget = fDbBelow / pMbcBandParams->expanderRatio;
+ float fDbChange = fDbBelow - fDbTarget;
+ fNewFactor = dBtoLinear(fDbChange);
+ }
+
+ //apply post gain.
+ fNewFactor *= dBtoLinear(pMbcBandParams->gainPostDb);
+
+ if (fNewFactor < 0) {
+ fNewFactor = 0;
+ }
+
+ //apply to this band
+ for (size_t k = pMbcBandParams->binStart; k <= pMbcBandParams->binStop; k++) {
+ mComplexTemp[k] *= fNewFactor;
+ }
+
+ } //end per band process
+
+ } //end MBC
+
+ //== EqPost
+ if (cb.mPostEqInUse && cb.mPostEqEnabled) {
+ for (size_t k = 0; k < maxBin; k++) {
+ mComplexTemp[k] *= cb.mPostEqFactorVector[k];
+ }
+ }
+
+ //##ifft directly to output.
+ Eigen::Map<Eigen::VectorXf> eOutput(&output[0], output.size());
+ mFftServer.inv(eOutput, mComplexTemp);
+
+ return mBlockSize;
+}
+
+} //namespace dp_fx
diff --git a/media/libeffects/dynamicsproc/dsp/DPFrequency.h b/media/libeffects/dynamicsproc/dsp/DPFrequency.h
new file mode 100644
index 0000000..9919142
--- /dev/null
+++ b/media/libeffects/dynamicsproc/dsp/DPFrequency.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 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 DPFREQUENCY_H_
+#define DPFREQUENCY_H_
+
+#include <Eigen/Dense>
+#include <unsupported/Eigen/FFT>
+
+#include "RDsp.h"
+#include "SHCircularBuffer.h"
+
+#include "DPBase.h"
+
+
+namespace dp_fx {
+
+using FXBuffer = SHCircularBuffer<float>;
+
+class ChannelBuffer {
+public:
+ FXBuffer cBInput; // Circular Buffer input
+ FXBuffer cBOutput; // Circular Buffer output
+ FloatVec input; // time domain temp vector for input
+ FloatVec output; // time domain temp vector for output
+ FloatVec outTail; // time domain temp vector for output tail (for overlap-add method)
+
+ //Current parameters
+ float inputGainDb;
+ struct BandParams {
+ bool enabled;
+ float freqCutoffHz;
+ size_t binStart;
+ size_t binStop;
+ };
+ struct EqBandParams : public BandParams {
+ float gainDb;
+ };
+ struct MbcBandParams : public BandParams {
+ float gainPreDb;
+ float gainPostDb;
+ float attackTimeMs;
+ float releaseTimeMs;
+ float ratio;
+ float thresholdDb;
+ float kneeWidthDb;
+ float noiseGateThresholdDb;
+ float expanderRatio;
+
+ //Historic values
+ float previousEnvelope;
+ };
+
+ bool mPreEqInUse;
+ bool mPreEqEnabled;
+ std::vector<EqBandParams> mPreEqBands;
+
+ bool mMbcInUse;
+ bool mMbcEnabled;
+ std::vector<MbcBandParams> mMbcBands;
+
+ bool mPostEqInUse;
+ bool mPostEqEnabled;
+ std::vector<EqBandParams> mPostEqBands;
+
+ bool mLimiterInUse;
+ bool mLimiterEnabled;
+ FloatVec mPreEqFactorVector; // temp pre-computed vector to shape spectrum at preEQ stage
+ FloatVec mPostEqFactorVector; // temp pre-computed vector to shape spectrum at postEQ stage
+
+ void initBuffers(unsigned int blockSize, unsigned int overlapSize, unsigned int halfFftSize,
+ unsigned int samplingRate, DPBase &dpBase);
+ void computeBinStartStop(BandParams &bp, size_t binStart);
+private:
+ unsigned int mSamplingRate;
+ unsigned int mBlockSize;
+
+};
+
+class DPFrequency : public DPBase {
+public:
+ virtual size_t processSamples(const float *in, float *out, size_t samples);
+ virtual void reset();
+ void configure(size_t blockSize, size_t overlapSize, size_t samplingRate);
+ static size_t getMinBockSize();
+ static size_t getMaxBockSize();
+
+private:
+ void updateParameters(ChannelBuffer &cb, int channelIndex);
+ size_t processMono(ChannelBuffer &cb);
+ size_t processOneVector(FloatVec &output, FloatVec &input, ChannelBuffer &cb);
+
+ size_t mBlockSize;
+ size_t mHalfFFTSize;
+ size_t mOverlapSize;
+ size_t mSamplingRate;
+
+ std::vector<ChannelBuffer> mChannelBuffers;
+
+ //dsp
+ FloatVec mVWindow; //window class.
+ Eigen::VectorXcf mComplexTemp;
+ Eigen::FFT<float> mFftServer;
+};
+
+} //namespace dp_fx
+
+#endif // DPFREQUENCY_H_
diff --git a/media/libeffects/dynamicsproc/dsp/RDsp.h b/media/libeffects/dynamicsproc/dsp/RDsp.h
new file mode 100644
index 0000000..1048442
--- /dev/null
+++ b/media/libeffects/dynamicsproc/dsp/RDsp.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2018 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 RDSP_H
+#define RDSP_H
+
+#include <complex>
+#include <log/log.h>
+#include <vector>
+using FloatVec = std::vector<float>;
+using ComplexVec = std::vector<std::complex<float>>;
+
+// =======
+// DSP window creation
+// =======
+
+#define TWOPI (M_PI * 2)
+
+enum rdsp_window_type {
+ RDSP_WINDOW_RECTANGULAR,
+ RDSP_WINDOW_TRIANGULAR,
+ RDSP_WINDOW_TRIANGULAR_FLAT_TOP,
+ RDSP_WINDOW_HAMMING,
+ RDSP_WINDOW_HAMMING_FLAT_TOP,
+ RDSP_WINDOW_HANNING,
+ RDSP_WINDOW_HANNING_FLAT_TOP,
+};
+
+template <typename T>
+static void fillRectangular(T &v) {
+ const size_t size = v.size();
+ for (size_t i = 0; i < size; i++) {
+ v[i] = 1.0;
+ }
+} //rectangular
+
+template <typename T>
+static void fillTriangular(T &v, size_t overlap) {
+ const size_t size = v.size();
+ //ramp up
+ size_t i = 0;
+ if (overlap > 0) {
+ for (; i < overlap; i++) {
+ v[i] = (2.0 * i + 1) / (2 * overlap);
+ }
+ }
+
+ //flat top
+ for (; i < size - overlap; i++) {
+ v[i] = 1.0;
+ }
+
+ //ramp down
+ if (overlap > 0) {
+ for (; i < size; i++) {
+ v[i] = (2.0 * (size - i) - 1) / (2 * overlap);
+ }
+ }
+} //triangular
+
+template <typename T>
+static void fillHamming(T &v, size_t overlap) {
+ const size_t size = v.size();
+ const size_t twoOverlap = 2 * overlap;
+ size_t i = 0;
+ if (overlap > 0) {
+ for (; i < overlap; i++) {
+ v[i] = 0.54 - 0.46 * cos(TWOPI * i /(twoOverlap - 1));
+ }
+ }
+
+ //flat top
+ for (; i < size - overlap; i++) {
+ v[i] = 1.0;
+ }
+
+ //ramp down
+ if (overlap > 0) {
+ for (; i < size; i++) {
+ int k = i - ((int)size - 2 * overlap);
+ v[i] = 0.54 - 0.46 * cos(TWOPI * k / (twoOverlap - 1));
+ }
+ }
+} //hamming
+
+template <typename T>
+static void fillHanning(T &v, size_t overlap) {
+ const size_t size = v.size();
+ const size_t twoOverlap = 2 * overlap;
+ //ramp up
+ size_t i = 0;
+ if (overlap > 0) {
+ for (; i < overlap; i++) {
+ v[i] = 0.5 * (1.0 - cos(TWOPI * i / (twoOverlap - 1)));
+ }
+ }
+
+ //flat top
+ for (; i < size - overlap; i++) {
+ v[i] = 1.0;
+ }
+
+ //ramp down
+ if (overlap > 0) {
+ for (; i < size; i++) {
+ int k = i - ((int)size - 2 * overlap);
+ v[i] = 0.5 * (1.0 - cos(TWOPI * k / (twoOverlap - 1)));
+ }
+ }
+}
+
+template <typename T>
+static void fill_window(T &v, int type, size_t size, size_t overlap) {
+ if (overlap > size / 2) {
+ overlap = size / 2;
+ }
+ v.resize(size);
+
+ switch (type) {
+ case RDSP_WINDOW_RECTANGULAR:
+ fillRectangular(v);
+ break;
+ case RDSP_WINDOW_TRIANGULAR:
+ fillTriangular(v, size / 2);
+ break;
+ case RDSP_WINDOW_TRIANGULAR_FLAT_TOP:
+ fillTriangular(v, overlap);
+ break;
+ case RDSP_WINDOW_HAMMING:
+ fillHamming(v, size / 2);
+ break;
+ case RDSP_WINDOW_HAMMING_FLAT_TOP:
+ fillHamming(v, overlap);
+ break;
+ case RDSP_WINDOW_HANNING:
+ fillHanning(v, size / 2);
+ break;
+ case RDSP_WINDOW_HANNING_FLAT_TOP:
+ fillHanning(v, overlap);
+ break;
+ default:
+ ALOGE("Error: unknown window type %d", type);
+ }
+}
+
+//};
+#endif //RDSP_H
diff --git a/media/libeffects/dynamicsproc/dsp/SHCircularBuffer.h b/media/libeffects/dynamicsproc/dsp/SHCircularBuffer.h
new file mode 100644
index 0000000..c139cd8
--- /dev/null
+++ b/media/libeffects/dynamicsproc/dsp/SHCircularBuffer.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 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 SHCIRCULARBUFFER_H
+#define SHCIRCULARBUFFER_H
+
+#include <log/log.h>
+#include <vector>
+
+template <class T>
+class SHCircularBuffer {
+
+public:
+ SHCircularBuffer() : mReadIndex(0), mWriteIndex(0), mReadAvailable(0) {
+ }
+
+ explicit SHCircularBuffer(size_t maxSize) {
+ resize(maxSize);
+ }
+ void resize(size_t maxSize) {
+ mBuffer.resize(maxSize);
+ mReadIndex = 0;
+ mWriteIndex = 0;
+ mReadAvailable = 0;
+ }
+ inline void write(T value) {
+ if (availableToWrite()) {
+ mBuffer[mWriteIndex++] = value;
+ if (mWriteIndex >= getSize()) {
+ mWriteIndex = 0;
+ }
+ mReadAvailable++;
+ } else {
+ ALOGE("Error: SHCircularBuffer no space to write. allocated size %zu ", getSize());
+ }
+ }
+ inline T read() {
+ T value = T();
+ if (availableToRead()) {
+ value = mBuffer[mReadIndex++];
+ if (mReadIndex >= getSize()) {
+ mReadIndex = 0;
+ }
+ mReadAvailable--;
+ } else {
+ ALOGW("Warning: SHCircularBuffer no data available to read. Default value returned");
+ }
+ return value;
+ }
+ inline size_t availableToRead() const {
+ return mReadAvailable;
+ }
+ inline size_t availableToWrite() const {
+ return getSize() - mReadAvailable;
+ }
+ inline size_t getSize() const {
+ return mBuffer.size();
+ }
+
+private:
+ std::vector<T> mBuffer;
+ size_t mReadIndex;
+ size_t mWriteIndex;
+ size_t mReadAvailable;
+};
+
+
+#endif //SHCIRCULARBUFFER_H