blob: 16dd1c506ad16d738d910198697911e47dd7b96f [file] [log] [blame]
/* //device/servers/AudioFlinger/AudioBiquadFilter.cpp
**
** Copyright 2009, 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 <string.h>
#include <assert.h>
#include <cutils/compiler.h>
#include "AudioBiquadFilter.h"
namespace android {
const audio_coef_t AudioBiquadFilter::IDENTITY_COEFS[AudioBiquadFilter::NUM_COEFS] = { AUDIO_COEF_ONE, 0, 0, 0, 0 };
AudioBiquadFilter::AudioBiquadFilter(int nChannels, int sampleRate) {
configure(nChannels, sampleRate);
reset();
}
void AudioBiquadFilter::configure(int nChannels, int sampleRate) {
assert(nChannels > 0 && nChannels <= MAX_CHANNELS);
assert(sampleRate > 0);
mNumChannels = nChannels;
mMaxDelta = static_cast<int64_t>(MAX_DELTA_PER_SEC)
* AUDIO_COEF_ONE
/ sampleRate;
clear();
}
void AudioBiquadFilter::reset() {
memcpy(mCoefs, IDENTITY_COEFS, sizeof(mCoefs));
mCoefDirtyBits = 0;
setState(STATE_BYPASS);
}
void AudioBiquadFilter::clear() {
memset(mDelays, 0, sizeof(mDelays));
}
void AudioBiquadFilter::setCoefs(const audio_coef_t coefs[NUM_COEFS], bool immediate) {
memcpy(mTargetCoefs, coefs, sizeof(mTargetCoefs));
if (mState & STATE_ENABLED_MASK) {
if (CC_UNLIKELY(immediate)) {
memcpy(mCoefs, coefs, sizeof(mCoefs));
setState(STATE_NORMAL);
} else {
setState(STATE_TRANSITION_TO_NORMAL);
}
}
}
void AudioBiquadFilter::process(const audio_sample_t in[], audio_sample_t out[],
int frameCount) {
(this->*mCurProcessFunc)(in, out, frameCount);
}
void AudioBiquadFilter::enable(bool immediate) {
if (CC_UNLIKELY(immediate)) {
memcpy(mCoefs, mTargetCoefs, sizeof(mCoefs));
setState(STATE_NORMAL);
} else {
setState(STATE_TRANSITION_TO_NORMAL);
}
}
void AudioBiquadFilter::disable(bool immediate) {
if (CC_UNLIKELY(immediate)) {
memcpy(mCoefs, IDENTITY_COEFS, sizeof(mCoefs));
setState(STATE_BYPASS);
} else {
setState(STATE_TRANSITION_TO_BYPASS);
}
}
void AudioBiquadFilter::setState(state_t state) {
switch (state) {
case STATE_BYPASS:
mCurProcessFunc = &AudioBiquadFilter::process_bypass;
break;
case STATE_TRANSITION_TO_BYPASS:
if (mNumChannels == 1) {
mCurProcessFunc = &AudioBiquadFilter::process_transition_bypass_mono;
} else {
mCurProcessFunc = &AudioBiquadFilter::process_transition_bypass_multi;
}
mCoefDirtyBits = (1 << NUM_COEFS) - 1;
break;
case STATE_TRANSITION_TO_NORMAL:
if (mNumChannels == 1) {
mCurProcessFunc = &AudioBiquadFilter::process_transition_normal_mono;
} else {
mCurProcessFunc = &AudioBiquadFilter::process_transition_normal_multi;
}
mCoefDirtyBits = (1 << NUM_COEFS) - 1;
break;
case STATE_NORMAL:
if (mNumChannels == 1) {
mCurProcessFunc = &AudioBiquadFilter::process_normal_mono;
} else {
mCurProcessFunc = &AudioBiquadFilter::process_normal_multi;
}
break;
}
mState = state;
}
bool AudioBiquadFilter::updateCoefs(const audio_coef_t coefs[NUM_COEFS],
int frameCount) {
int64_t maxDelta = mMaxDelta * frameCount;
for (int i = 0; i < NUM_COEFS; ++i) {
if (mCoefDirtyBits & (1<<i)) {
audio_coef_t diff = coefs[i] - mCoefs[i];
if (diff > maxDelta) {
mCoefs[i] += maxDelta;
} else if (diff < -maxDelta) {
mCoefs[i] -= maxDelta;
} else {
mCoefs[i] = coefs[i];
mCoefDirtyBits ^= (1<<i);
}
}
}
return mCoefDirtyBits == 0;
}
void AudioBiquadFilter::process_bypass(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
// The common case is in-place processing, because this is what the EQ does.
if (CC_UNLIKELY(in != out)) {
memcpy(out, in, frameCount * mNumChannels * sizeof(audio_sample_t));
}
}
void AudioBiquadFilter::process_normal_mono(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
size_t nFrames = frameCount;
audio_sample_t x1 = mDelays[0][0];
audio_sample_t x2 = mDelays[0][1];
audio_sample_t y1 = mDelays[0][2];
audio_sample_t y2 = mDelays[0][3];
const audio_coef_t b0 = mCoefs[0];
const audio_coef_t b1 = mCoefs[1];
const audio_coef_t b2 = mCoefs[2];
const audio_coef_t a1 = mCoefs[3];
const audio_coef_t a2 = mCoefs[4];
while (nFrames-- > 0) {
audio_sample_t x0 = *(in++);
audio_coef_sample_acc_t acc;
acc = mul_coef_sample(b0, x0);
acc = mac_coef_sample(b1, x1, acc);
acc = mac_coef_sample(b2, x2, acc);
acc = mac_coef_sample(a1, y1, acc);
acc = mac_coef_sample(a2, y2, acc);
audio_sample_t y0 = coef_sample_acc_to_sample(acc);
y2 = y1;
y1 = y0;
x2 = x1;
x1 = x0;
(*out++) = y0;
}
mDelays[0][0] = x1;
mDelays[0][1] = x2;
mDelays[0][2] = y1;
mDelays[0][3] = y2;
}
void AudioBiquadFilter::process_transition_normal_mono(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
if (updateCoefs(mTargetCoefs, frameCount)) {
setState(STATE_NORMAL);
}
process_normal_mono(in, out, frameCount);
}
void AudioBiquadFilter::process_transition_bypass_mono(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
if (updateCoefs(IDENTITY_COEFS, frameCount)) {
setState(STATE_NORMAL);
}
process_normal_mono(in, out, frameCount);
}
void AudioBiquadFilter::process_normal_multi(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
const audio_coef_t b0 = mCoefs[0];
const audio_coef_t b1 = mCoefs[1];
const audio_coef_t b2 = mCoefs[2];
const audio_coef_t a1 = mCoefs[3];
const audio_coef_t a2 = mCoefs[4];
for (int ch = 0; ch < mNumChannels; ++ch) {
size_t nFrames = frameCount;
audio_sample_t x1 = mDelays[ch][0];
audio_sample_t x2 = mDelays[ch][1];
audio_sample_t y1 = mDelays[ch][2];
audio_sample_t y2 = mDelays[ch][3];
while (nFrames-- > 0) {
audio_sample_t x0 = *in;
audio_coef_sample_acc_t acc;
acc = mul_coef_sample(b0, x0);
acc = mac_coef_sample(b1, x1, acc);
acc = mac_coef_sample(b2, x2, acc);
acc = mac_coef_sample(a1, y1, acc);
acc = mac_coef_sample(a2, y2, acc);
audio_sample_t y0 = coef_sample_acc_to_sample(acc);
y2 = y1;
y1 = y0;
x2 = x1;
x1 = x0;
*out = y0;
in += mNumChannels;
out += mNumChannels;
}
mDelays[ch][0] = x1;
mDelays[ch][1] = x2;
mDelays[ch][2] = y1;
mDelays[ch][3] = y2;
in -= frameCount * mNumChannels - 1;
out -= frameCount * mNumChannels - 1;
}
}
void AudioBiquadFilter::process_transition_normal_multi(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
if (updateCoefs(mTargetCoefs, frameCount)) {
setState(STATE_NORMAL);
}
process_normal_multi(in, out, frameCount);
}
void AudioBiquadFilter::process_transition_bypass_multi(const audio_sample_t * in,
audio_sample_t * out,
int frameCount) {
if (updateCoefs(IDENTITY_COEFS, frameCount)) {
setState(STATE_NORMAL);
}
process_normal_multi(in, out, frameCount);
}
}