diff options
Diffstat (limited to 'libs/audioflinger/AudioDSP.cpp')
-rw-r--r-- | libs/audioflinger/AudioDSP.cpp | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/libs/audioflinger/AudioDSP.cpp b/libs/audioflinger/AudioDSP.cpp new file mode 100644 index 0000000..c56773b --- /dev/null +++ b/libs/audioflinger/AudioDSP.cpp @@ -0,0 +1,651 @@ +/* //device/include/server/AudioFlinger/AudioDSP.cpp +** +** Copyright 2010, Antti S. Lankila +** +** 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 <math.h> +#include <stdio.h> + +#include "AudioDSP.h" + +namespace android { + +/* Keep this in sync with AudioMixer's FP decimal count. We + * use this count to generate the dither for ditherAndClamp(), + * among other things. */ +static const int32_t fixedPointDecimals = 12; +static const int32_t fixedPointBits = (1 << fixedPointDecimals) - 1; + +static int16_t toFixedPoint(float x) +{ + return int16_t(x * (1 << fixedPointDecimals) + 0.5f); +} + + +/*************************************************************************** + * Delay * + ***************************************************************************/ +Delay::Delay() + : mState(0), mIndex(0), mLength(0) +{ +} + +Delay::~Delay() +{ + if (mState != 0) { + delete[] mState; + mState = 0; + } +} + +void Delay::setParameters(float samplingFrequency, float time) +{ + mLength = int32_t(time * samplingFrequency + 0.5f); + if (mState != 0) { + delete[] mState; + } + mState = new int32_t[mLength]; + memset(mState, 0, mLength * sizeof(int32_t)); + mIndex = 0; +} + +inline int32_t Delay::process(int32_t x0) +{ + int32_t y0 = mState[mIndex]; + mState[mIndex] = x0; + mIndex = (mIndex + 1) % mLength; + return y0; +} + + +/*************************************************************************** + * Allpass * + ***************************************************************************/ +Allpass::Allpass() + : mK(0), mState(0), mIndex(0), mLength(0) +{ +} + +Allpass::~Allpass() +{ + if (mState != 0) { + delete[] mState; + mState = 0; + } +} + +void Allpass::setParameters(float samplingFrequency, float k, float time) +{ + mK = toFixedPoint(k); + mLength = int32_t(time * samplingFrequency + 0.5f); + if (mState != 0) { + delete[] mState; + } + mState = new int32_t[mLength]; + memset(mState, 0, mLength * sizeof(int32_t)); + mIndex = 0; +} + +inline int32_t Allpass::process(int32_t x0) +{ + int32_t tmp = x0 - mK * (mState[mIndex] >> fixedPointDecimals); + int32_t y0 = mState[mIndex] + mK * (tmp >> fixedPointDecimals); + mState[mIndex] = tmp; + mIndex = (mIndex + 1) % mLength; + return y0; +} + + +/*************************************************************************** + * Biquad * + ***************************************************************************/ +Biquad::Biquad() + : mB0(0), mY0(0) +{ + state.i32.mA = 0; + state.i32.mB = 0; + state.i32.mX = 0; + state.i32.mY = 0; +} + +void Biquad::setCoefficients(float a0, float a1, float a2, float b0, float b1, float b2) +{ + state.i16.mA1 = -toFixedPoint(a1/a0); + state.i16.mA2 = -toFixedPoint(a2/a0); + mB0 = toFixedPoint(b0/a0); + state.i16.mB1 = toFixedPoint(b1/a0); + state.i16.mB2 = toFixedPoint(b2/a0); +} + +void Biquad::setRC(float center_frequency, float sampling_frequency) +{ + float DT_div_RC = 2 * (float) M_PI * center_frequency / sampling_frequency; + float b0 = DT_div_RC / (1 + DT_div_RC); + float a1 = -1 + b0; + + setCoefficients(1, a1, 0, b0, 0, 0); +} + +void Biquad::reset() +{ + mY0 = 0; + state.i32.mX = 0; + state.i32.mY = 0; +} + +/* + * Peaking equalizer, low shelf and high shelf are taken from + * the good old Audio EQ Cookbook by Robert Bristow-Johnson. + */ +void Biquad::setPeakingEqualizer(float center_frequency, float sampling_frequency, float db_gain, float bandwidth) +{ + float w0 = 2 * (float) M_PI * center_frequency / sampling_frequency; + float A = powf(10, db_gain/40); + + float alpha = sinf(w0)/2 * sinhf( logf(2)/2 * bandwidth * w0/sinf(w0) ); + float b0 = 1 + alpha*A; + float b1 = -2*cosf(w0); + float b2 = 1 - alpha*A; + float a0 = 1 + alpha/A; + float a1 = -2*cosf(w0); + float a2 = 1 - alpha/A; + + setCoefficients(a0, a1, a2, b0, b1, b2); +} + +void Biquad::setLowShelf(float center_frequency, float sampling_frequency, float db_gain, float slope) +{ + float w0 = 2 * (float) M_PI * center_frequency / sampling_frequency; + float A = powf(10, db_gain/40); + float alpha = sinf(w0)/2 * sqrtf( (A + 1/A)*(1/slope - 1) + 2 ); + + float b0 = A*( (A+1) - (A-1)*cosf(w0) + 2*sqrtf(A)*alpha ); + float b1 = 2*A*( (A-1) - (A+1)*cosf(w0) ); + float b2 = A*( (A+1) - (A-1)*cosf(w0) - 2*sqrtf(A)*alpha ); + float a0 = (A+1) + (A-1)*cosf(w0) + 2*sqrtf(A)*alpha ; + float a1 = -2*( (A-1) + (A+1)*cosf(w0) ); + float a2 = (A+1) + (A-1)*cosf(w0) - 2*sqrtf(A)*alpha ; + + setCoefficients(a0, a1, a2, b0, b1, b2); +} + +void Biquad::setHighShelf(float center_frequency, float sampling_frequency, float db_gain, float slope) +{ + float w0 = 2 * (float) M_PI * center_frequency / sampling_frequency; + float A = powf(10, db_gain/40); + float alpha = sinf(w0)/2 * sqrtf( (A + 1/A)*(1/slope - 1) + 2 ); + + float b0 = A*( (A+1) + (A-1)*cosf(w0) + 2*sqrtf(A)*alpha ); + float b1 = -2*A*( (A-1) + (A+1)*cosf(w0) ); + float b2 = A*( (A+1) + (A-1)*cosf(w0) - 2*sqrtf(A)*alpha ); + float a0 = (A+1) - (A-1)*cosf(w0) + 2*sqrtf(A)*alpha ; + float a1 = 2*( (A-1) - (A+1)*cosf(w0) ); + float a2 = (A+1) - (A-1)*cosf(w0) - 2*sqrtf(A)*alpha ; + + setCoefficients(a0, a1, a2, b0, b1, b2); +} + +void Biquad::setBandPass(float center_frequency, float sampling_frequency, float resonance) +{ + float w0 = 2 * (float) M_PI * center_frequency / sampling_frequency; + float alpha = sinf(w0) / (2*resonance); + + float b0 = sinf(w0)/2; + float b1 = 0; + float b2 = -sinf(w0)/2; + float a0 = 1 + alpha; + float a1 = -2*cosf(w0); + float a2 = 1 - alpha; + + setCoefficients(a0, a1, a2, b0, b1, b2); +} + +/* returns output scaled by fixedPoint factor */ +inline int32_t Biquad::process(int16_t x0) +{ + /* mY0 holds error from previous integer truncation. */ + int32_t y0 = mY0 + mB0 * x0; + +#if defined(__arm__) && !defined(__thumb__) + asm( + "smlatt %[y0], %[i], %[j], %[y0]\n" + "smlabb %[y0], %[i], %[j], %[y0]\n" + "smlatt %[y0], %[k], %[l], %[y0]\n" + "smlabb %[y0], %[k], %[l], %[y0]\n" + : [y0]"+r"(y0) + : [i]"r"(state.i32.mA), [j]"r"(state.i32.mY), + [k]"r"(state.i32.mB), [l]"r"(state.i32.mX) + : ); + + /* GCC is going to issue loads for the state.i16, so I do it + * like this because the state.i32 is already in registers. + * ARM appears to have instructions that can handle these + * bit manipulations well, such as "orr r0, r0, r1, lsl #16". + */ + state.i32.mY = (state.i32.mY << 16) | ((y0 >> fixedPointDecimals) & 0xffff); + state.i32.mX = (state.i32.mX << 16) | (x0 & 0xffff); +#else + y0 += state.i16.mB1 * state.i16.mX1 + + state.i16.mB2 * state.i16.mX2 + + state.i16.mY1 * state.i16.mA1 + + state.i16.mY2 * state.i16.mA2; + + state.i16.mY2 = state.i16.mY1; + state.i16.mY1 = y0 >> fixedPointDecimals; + + state.i16.mX2 = state.i16.mX1; + state.i16.mX1 = x0; +#endif + + mY0 = y0 & fixedPointBits; + return y0; +} + + +/*************************************************************************** + * Effect * + ***************************************************************************/ +Effect::Effect() +{ + configure(44100); +} + +Effect::~Effect() { +} + +void Effect::configure(const float samplingFrequency) { + mSamplingFrequency = samplingFrequency; +} + + +EffectCompression::EffectCompression() + : mCompressionRatio(2.0) +{ +} + +EffectCompression::~EffectCompression() +{ +} + +void EffectCompression::configure(const float samplingFrequency) +{ + Effect::configure(samplingFrequency); + mWeighter.setBandPass(1000, samplingFrequency, sqrtf(2)/2); +} + +void EffectCompression::setRatio(float compressionRatio) +{ + mCompressionRatio = compressionRatio; +} + +void EffectCompression::process(int32_t* inout, int32_t frames) +{ +} + +float EffectCompression::estimateLevel(const int16_t* audioData, int32_t frames, int32_t samplesPerFrame) +{ + mWeighter.reset(); + uint32_t power = 0; + uint32_t powerFraction = 0; + for (int32_t i = 0; i < frames; i ++) { + int16_t tmp = *audioData; + audioData += samplesPerFrame; + + int32_t out = mWeighter.process(tmp) >> 12; + powerFraction += out * out; + power += powerFraction >> 16; + powerFraction &= 0xffff; + } + + /* peak-to-peak is -32768 to 32767, but we are squared here. */ + return (65536.0f * power + powerFraction) / (32768.0f * 32768.0f) / frames; +} + + +EffectTone::EffectTone() +{ + for (int32_t i = 0; i < 5; i ++) { + mBand[i] = 0; + } + for (int32_t i = 0; i < 5; i ++) { + setBand(i, 0); + } +} + +EffectTone::~EffectTone() { + for (int32_t i = 0; i < 4; i ++) { + delete &mFilterL[i]; + delete &mFilterR[i]; + } +} + +void EffectTone::configure(const float samplingFrequency) { + Effect::configure(samplingFrequency); + refreshBands(); +} + +void EffectTone::setBand(int32_t band, float dB) +{ + mBand[band] = dB; + refreshBands(); +} + +void EffectTone::refreshBands() { + mGain = toFixedPoint(powf(10, mBand[0] / 20)); + + for (int32_t band = 0; band < 3; band ++) { + float dB = mBand[band + 1] - mBand[0]; + float centerFrequency = 250.0f * powf(4, band); + + mFilterL[band].setPeakingEqualizer(centerFrequency, mSamplingFrequency, dB, 3.0f); + mFilterR[band].setPeakingEqualizer(centerFrequency, mSamplingFrequency, dB, 3.0f); + } + + { + int32_t band = 3; + + float dB = mBand[band + 1] - mBand[0]; + float centerFrequency = 250.0f * powf(4, band); + + mFilterL[band].setHighShelf(centerFrequency * 0.5f, mSamplingFrequency, dB, 1.0f); + mFilterR[band].setHighShelf(centerFrequency * 0.5f, mSamplingFrequency, dB, 1.0f); + } +} + +void EffectTone::process(int32_t* inout, int32_t frames) +{ + for (int32_t i = 0; i < frames; i ++) { + int32_t tmpL = inout[0] >> fixedPointDecimals; + int32_t tmpR = inout[1] >> fixedPointDecimals; + /* 16 bits */ + + /* bass control is really a global gain compensated by other + * controls */ + tmpL = tmpL * mGain; + tmpR = tmpR * mGain; + /* 28 bits */ + + /* evaluate the other filters. + * I'm ignoring the integer truncation problem here, but in reality + * it should be accounted for. */ + for (int32_t j = 0; j < 4; j ++) { + tmpL = mFilterL[j].process(tmpL >> fixedPointDecimals); + tmpR = mFilterR[j].process(tmpR >> fixedPointDecimals); + } + /* 28 bits */ + + inout[0] = tmpL; + inout[1] = tmpR; + inout += 2; + } +} + +EffectHeadphone::EffectHeadphone() + : mDeep(true), mWide(true), + mDelayDataL(0), mDelayDataR(0) +{ + setLevel(0); +} + +EffectHeadphone::~EffectHeadphone() +{ + delete &mReverbDelayL; + delete &mReverbDelayR; + delete &mDelayL; + delete &mDelayR; + for (int32_t i = 0; i < 3; i ++) { + delete &mAllpassL[i]; + delete &mAllpassR[i]; + } + delete &mLowpassL; + delete &mLowpassR; +} + +void EffectHeadphone::configure(const float samplingFrequency) { + Effect::configure(samplingFrequency); + + mReverbDelayL.setParameters(mSamplingFrequency, 0.030f); + mReverbDelayR.setParameters(mSamplingFrequency, 0.030f); + mDelayL.setParameters(mSamplingFrequency, 0.00045f); + mDelayR.setParameters(mSamplingFrequency, 0.00045f); + mAllpassL[0].setParameters(mSamplingFrequency, 0.4f, 0.00031f); + mAllpassR[0].setParameters(mSamplingFrequency, 0.4f, 0.00031f); + mAllpassL[1].setParameters(mSamplingFrequency, 0.4f, 0.00021f); + mAllpassR[1].setParameters(mSamplingFrequency, 0.4f, 0.00021f); + mAllpassL[2].setParameters(mSamplingFrequency, 0.4f, 0.00011f); + mAllpassR[2].setParameters(mSamplingFrequency, 0.4f, 0.00011f); + mLowpassL.setRC(4000.0f, mSamplingFrequency); + mLowpassR.setRC(4000.0f, mSamplingFrequency); +} + +void EffectHeadphone::setDeep(bool deep) +{ + mDeep = deep; +} + +void EffectHeadphone::setWide(bool wide) +{ + mWide = wide; +} + +void EffectHeadphone::setLevel(float level) +{ + mLevel = toFixedPoint(powf(10, (level - 15.0f) / 20.0f)); +} + +void EffectHeadphone::process(int32_t* inout, int32_t frames) +{ + for (int32_t i = 0; i < frames; i ++) { + /* calculate reverb wet into dataL, dataR */ + int32_t dryL = inout[0]; + int32_t dryR = inout[1]; + int32_t dataL = dryL; + int32_t dataR = dryR; + /* 28 bits */ + + if (mDeep) { + dataL += mDelayDataR; + dataR += mDelayDataL; + } + + dataL = mReverbDelayL.process(dataL); + dataR = mReverbDelayR.process(dataR); + /* 28 bits */ + + if (mWide) { + dataR = -dataR; + } + + dataL = (dataL >> fixedPointDecimals) * mLevel; + dataR = (dataR >> fixedPointDecimals) * mLevel; + /* 28 bits */ + + mDelayDataL = dataL; + mDelayDataR = dataR; + + /* Reverb wet done; mix with dry and do headphone virtualization */ + dataL += dryL; + dataR += dryR; + + /* Add fixed ear-to-ear propagation delay of about 10 cm, based + * on the idea that ear-to-ear distance is 30 cm and the speakers + * are placed in front of the listener, which means that the actual + * time delay will be somewhat less than the maximum. */ + dataL = mDelayL.process(dataL); + dataR = mDelayR.process(dataR); + for (int32_t j = 0; j < 3; j ++) { + /* Confuse phase, simulating shoulder echoes and whatnot. */ + dataL = mAllpassL[j].process(dataL); + dataR = mAllpassR[j].process(dataR); + } + + /* Lowpass filter to estimate head shadow. */ + dataL = mLowpassL.process(dataL >> fixedPointDecimals); + dataR = mLowpassR.process(dataR >> fixedPointDecimals); + /* 28 bits */ + + /* Mix right-to-left and vice versa. */ + inout[0] += dataR; + inout[1] += dataL; + inout += 2; + } +} + + +/*************************************************************************** + * AudioDSP * + ***************************************************************************/ +const String8 AudioDSP::keyCompressionEnable = String8("dsp.compression.enable"); +const String8 AudioDSP::keyCompressionRatio = String8("dsp.compression.ratio"); + +const String8 AudioDSP::keyToneEnable = String8("dsp.tone.enable"); +const String8 AudioDSP::keyToneEq1 = String8("dsp.tone.eq1"); +const String8 AudioDSP::keyToneEq2 = String8("dsp.tone.eq2"); +const String8 AudioDSP::keyToneEq3 = String8("dsp.tone.eq3"); +const String8 AudioDSP::keyToneEq4 = String8("dsp.tone.eq4"); +const String8 AudioDSP::keyToneEq5 = String8("dsp.tone.eq5"); + +const String8 AudioDSP::keyHeadphoneEnable = String8("dsp.headphone.enable"); +const String8 AudioDSP::keyHeadphoneDeep = String8("dsp.headphone.deep"); +const String8 AudioDSP::keyHeadphoneWide = String8("dsp.headphone.wide"); +const String8 AudioDSP::keyHeadphoneLevel = String8("dsp.headphone.level"); + +AudioDSP::AudioDSP() + : mCompressionEnable(false), mToneEnable(false), mHeadphoneEnable(false) +{ +} + +AudioDSP::~AudioDSP() +{ + delete &mCompression; + delete &mTone; + delete &mHeadphone; +} + +void AudioDSP::configure(const float samplingRate) +{ + mCompression.configure(samplingRate); + mTone.configure(samplingRate); + mHeadphone.configure(samplingRate); +} + +void AudioDSP::setParameters(const String8& keyValuePairs) +{ + int intValue; + float floatValue; + status_t result; + AudioParameter param = AudioParameter(keyValuePairs); + + result = param.getInt(keyCompressionEnable, intValue); + if (result == NO_ERROR) { + mCompressionEnable = intValue != 0; + } + result = param.getFloat(keyCompressionRatio, floatValue); + if (result == NO_ERROR) { + mCompression.setRatio(floatValue); + } + + result = param.getInt(keyToneEnable, intValue); + if (result == NO_ERROR) { + mToneEnable = intValue != 0; + } + result = param.getFloat(keyToneEq1, floatValue); + if (result == NO_ERROR) { + mTone.setBand(0, floatValue); + } + result = param.getFloat(keyToneEq2, floatValue); + if (result == NO_ERROR) { + mTone.setBand(1, floatValue); + } + result = param.getFloat(keyToneEq3, floatValue); + if (result == NO_ERROR) { + mTone.setBand(2, floatValue); + } + result = param.getFloat(keyToneEq4, floatValue); + if (result == NO_ERROR) { + mTone.setBand(3, floatValue); + } + result = param.getFloat(keyToneEq5, floatValue); + if (result == NO_ERROR) { + mTone.setBand(4, floatValue); + } + + result = param.getInt(keyHeadphoneEnable, intValue); + if (result == NO_ERROR) { + mHeadphoneEnable = intValue != 0; + } + result = param.getInt(keyHeadphoneDeep, intValue); + if (result == NO_ERROR) { + mHeadphone.setDeep(intValue != 0); + } + result = param.getInt(keyHeadphoneWide, intValue); + if (result == NO_ERROR) { + mHeadphone.setWide(intValue != 0); + } + result = param.getFloat(keyHeadphoneLevel, floatValue); + if (result == NO_ERROR) { + mHeadphone.setLevel(floatValue); + } +} + +int32_t AudioDSP::estimateLevel(const int16_t* input, int32_t frames, int32_t samplesPerFrame) +{ + if (! mCompressionEnable) { + return 65536; + } + + /* Analyze both channels separately, pick the maximum power measured. */ + float maximumPowerSquared = 0; + for (int channel = 0; channel < samplesPerFrame; channel ++) { + float candidatePowerSquared = mCompression.estimateLevel(input + channel, frames, samplesPerFrame); + if (candidatePowerSquared > maximumPowerSquared) { + maximumPowerSquared = candidatePowerSquared; + } + } + + /* -100 .. 0 dB. */ + float signalPowerDb = logf(maximumPowerSquared + 1e-10f) / logf(10.0f) * 10.0f; + + /* target 83 dB SPL, and add 6 dB to compensate for the weighter, whose + * peak is at -3 dB. */ + signalPowerDb += 96.0f - 83.0f + 6.0f; + + /* now we have an estimate of the signal power, with 0 level around 83 dB. + * we now select the level to boost to. */ + float desiredLevelDb = signalPowerDb / mCompression.mCompressionRatio; + + /* turn back to multiplier */ + float correctionDb = desiredLevelDb - signalPowerDb; + + /* Reduce extreme boost by a smooth ramp. + * New range -50 .. 0 dB */ + correctionDb -= powf(correctionDb/100, 2.0f) * (100.0f / 2.0f); + + return int32_t(65536.0f * powf(10.0f, correctionDb / 20.0f)); +} + +/* input is 28-bit interleaved stereo in integer format */ +void AudioDSP::process(int32_t* audioData, int32_t frames) +{ + if (mToneEnable) { + mTone.process(audioData, frames); + } + + if (mHeadphoneEnable) { + mHeadphone.process(audioData, frames); + } +} + +} |