AudioResampler: Add configurable resampler design
Can be modified through properties.
Parameters enabled for 48kHz output sample rate and higher.
Default 90dB stopband attenuation.
Default cutoff frequency at Nyquist.
Test: native gtest resampler_tests
Bug: 66091148
Change-Id: Ieb8a959b7473613ef21121ccea0aa7ea3e2f1210
diff --git a/media/libaudioprocessing/AudioResamplerDyn.cpp b/media/libaudioprocessing/AudioResamplerDyn.cpp
index 8f7b982..eeeecce 100644
--- a/media/libaudioprocessing/AudioResamplerDyn.cpp
+++ b/media/libaudioprocessing/AudioResamplerDyn.cpp
@@ -38,6 +38,9 @@
//#define DEBUG_RESAMPLER
+// use this for our buffer alignment. Should be at least 32 bytes.
+constexpr size_t CACHE_LINE_SIZE = 64;
+
namespace android {
/*
@@ -94,7 +97,10 @@
// create new buffer
TI* state = NULL;
- (void)posix_memalign(reinterpret_cast<void**>(&state), 32, stateCount*sizeof(*state));
+ (void)posix_memalign(
+ reinterpret_cast<void **>(&state),
+ CACHE_LINE_SIZE /* alignment */,
+ stateCount * sizeof(*state));
memset(state, 0, stateCount*sizeof(*state));
// attempt to preserve state
@@ -185,6 +191,16 @@
// setSampleRate() for 1:1. (May be removed if precalculated filters are used.)
mInSampleRate = 0;
mConstants.set(128, 8, mSampleRate, mSampleRate); // TODO: set better
+
+ // fetch property based resampling parameters
+ mPropertyEnableAtSampleRate = property_get_int32(
+ "ro.audio.resampler.psd.enable_at_samplerate", mPropertyEnableAtSampleRate);
+ mPropertyHalfFilterLength = property_get_int32(
+ "ro.audio.resampler.psd.halflength", mPropertyHalfFilterLength);
+ mPropertyStopbandAttenuation = property_get_int32(
+ "ro.audio.resampler.psd.stopband", mPropertyStopbandAttenuation);
+ mPropertyCutoffPercent = property_get_int32(
+ "ro.audio.resampler.psd.cutoff_percent", mPropertyCutoffPercent);
}
template<typename TC, typename TI, typename TO>
@@ -215,6 +231,8 @@
}
}
+// TODO: update to C++11
+
template<typename T> T max(T a, T b) {return a > b ? a : b;}
template<typename T> T absdiff(T a, T b) {return a > b ? a - b : b - a;}
@@ -223,37 +241,74 @@
void AudioResamplerDyn<TC, TI, TO>::createKaiserFir(Constants &c,
double stopBandAtten, int inSampleRate, int outSampleRate, double tbwCheat)
{
- TC* buf = NULL;
- static const double atten = 0.9998; // to avoid ripple overflow
- double fcr;
- double tbw = firKaiserTbw(c.mHalfNumCoefs, stopBandAtten);
+ // compute the normalized transition bandwidth
+ const double tbw = firKaiserTbw(c.mHalfNumCoefs, stopBandAtten);
+ const double halfbw = tbw / 2.;
- (void)posix_memalign(reinterpret_cast<void**>(&buf), 32, (c.mL+1)*c.mHalfNumCoefs*sizeof(TC));
+ double fcr; // compute fcr, the 3 dB amplitude cut-off.
if (inSampleRate < outSampleRate) { // upsample
- fcr = max(0.5*tbwCheat - tbw/2, tbw/2);
+ fcr = max(0.5 * tbwCheat - halfbw, halfbw);
} else { // downsample
- fcr = max(0.5*tbwCheat*outSampleRate/inSampleRate - tbw/2, tbw/2);
+ fcr = max(0.5 * tbwCheat * outSampleRate / inSampleRate - halfbw, halfbw);
}
- // create and set filter
- firKaiserGen(buf, c.mL, c.mHalfNumCoefs, stopBandAtten, fcr, atten);
- c.mFirCoefs = buf;
- if (mCoefBuffer) {
- free(mCoefBuffer);
- }
- mCoefBuffer = buf;
-#ifdef DEBUG_RESAMPLER
+ createKaiserFir(c, stopBandAtten, fcr);
+}
+
+template<typename TC, typename TI, typename TO>
+void AudioResamplerDyn<TC, TI, TO>::createKaiserFir(Constants &c,
+ double stopBandAtten, double fcr) {
+ // compute the normalized transition bandwidth
+ const double tbw = firKaiserTbw(c.mHalfNumCoefs, stopBandAtten);
+ const int phases = c.mL;
+ const int halfLength = c.mHalfNumCoefs;
+
+ // create buffer
+ TC *coefs = nullptr;
+ int ret = posix_memalign(
+ reinterpret_cast<void **>(&coefs),
+ CACHE_LINE_SIZE /* alignment */,
+ (phases + 1) * halfLength * sizeof(TC));
+ LOG_ALWAYS_FATAL_IF(ret != 0, "Cannot allocate buffer memory, ret %d", ret);
+ c.mFirCoefs = coefs;
+ free(mCoefBuffer);
+ mCoefBuffer = coefs;
+
+ // square the computed minimum passband value (extra safety).
+ double attenuation =
+ computeWindowedSincMinimumPassbandValue(stopBandAtten);
+ attenuation *= attenuation;
+
+ // design filter
+ firKaiserGen(coefs, phases, halfLength, stopBandAtten, fcr, attenuation);
+
+ // update the design criteria
+ mNormalizedCutoffFrequency = fcr;
+ mNormalizedTransitionBandwidth = tbw;
+ mFilterAttenuation = attenuation;
+ mStopbandAttenuationDb = stopBandAtten;
+ mPassbandRippleDb = computeWindowedSincPassbandRippleDb(stopBandAtten);
+
+#if 0
+ // Keep this debug code in case an app causes resampler design issues.
+ const double halfbw = tbw / 2.;
// print basic filter stats
- printf("L:%d hnc:%d stopBandAtten:%lf fcr:%lf atten:%lf tbw:%lf\n",
- c.mL, c.mHalfNumCoefs, stopBandAtten, fcr, atten, tbw);
- // test the filter and report results
- double fp = (fcr - tbw/2)/c.mL;
- double fs = (fcr + tbw/2)/c.mL;
+ ALOGD("L:%d hnc:%d stopBandAtten:%lf fcr:%lf atten:%lf tbw:%lf\n",
+ c.mL, c.mHalfNumCoefs, stopBandAtten, fcr, attenuation, tbw);
+
+ // test the filter and report results.
+ // Since this is a polyphase filter, normalized fp and fs must be scaled.
+ const double fp = (fcr - halfbw) / phases;
+ const double fs = (fcr + halfbw) / phases;
+
double passMin, passMax, passRipple;
double stopMax, stopRipple;
- testFir(buf, c.mL, c.mHalfNumCoefs, fp, fs, /*passSteps*/ 1000, /*stopSteps*/ 100000,
+
+ const int32_t passSteps = 1000;
+
+ testFir(coefs, c.mL, c.mHalfNumCoefs, fp, fs, passSteps, passSteps * c.ML /*stopSteps*/,
passMin, passMax, passRipple, stopMax, stopRipple);
- printf("passband(%lf, %lf): %.8lf %.8lf %.8lf\n", 0., fp, passMin, passMax, passRipple);
- printf("stopband(%lf, %lf): %.8lf %.3lf\n", fs, 0.5, stopMax, stopRipple);
+ ALOGD("passband(%lf, %lf): %.8lf %.8lf %.8lf\n", 0., fp, passMin, passMax, passRipple);
+ ALOGD("stopband(%lf, %lf): %.8lf %.3lf\n", fs, 0.5, stopMax, stopRipple);
#endif
}
@@ -304,6 +359,11 @@
mFilterSampleRate = inSampleRate;
mFilterQuality = getQuality();
+ double stopBandAtten;
+ double tbwCheat = 1.; // how much we "cheat" into aliasing
+ int halfLength;
+ double fcr = 0.;
+
// Begin Kaiser Filter computation
//
// The quantization floor for S16 is about 96db - 10*log_10(#length) + 3dB.
@@ -313,52 +373,60 @@
// 96-98dB
//
- double stopBandAtten;
- double tbwCheat = 1.; // how much we "cheat" into aliasing
- int halfLength;
- if (mFilterQuality == DYN_HIGH_QUALITY) {
- // 32b coefficients, 64 length
+ if (mPropertyEnableAtSampleRate >= 0 && mSampleRate >= mPropertyEnableAtSampleRate) {
+ // An alternative method which allows allows a greater fcr
+ // at the expense of potential aliasing.
+ halfLength = mPropertyHalfFilterLength;
+ stopBandAtten = mPropertyStopbandAttenuation;
useS32 = true;
- stopBandAtten = 98.;
- if (inSampleRate >= mSampleRate * 4) {
- halfLength = 48;
- } else if (inSampleRate >= mSampleRate * 2) {
- halfLength = 40;
- } else {
- halfLength = 32;
- }
- } else if (mFilterQuality == DYN_LOW_QUALITY) {
- // 16b coefficients, 16-32 length
- useS32 = false;
- stopBandAtten = 80.;
- if (inSampleRate >= mSampleRate * 4) {
- halfLength = 24;
- } else if (inSampleRate >= mSampleRate * 2) {
- halfLength = 16;
- } else {
- halfLength = 8;
- }
- if (inSampleRate <= mSampleRate) {
- tbwCheat = 1.05;
- } else {
- tbwCheat = 1.03;
- }
- } else { // DYN_MED_QUALITY
- // 16b coefficients, 32-64 length
- // note: > 64 length filters with 16b coefs can have quantization noise problems
- useS32 = false;
- stopBandAtten = 84.;
- if (inSampleRate >= mSampleRate * 4) {
- halfLength = 32;
- } else if (inSampleRate >= mSampleRate * 2) {
- halfLength = 24;
- } else {
- halfLength = 16;
- }
- if (inSampleRate <= mSampleRate) {
- tbwCheat = 1.03;
- } else {
- tbwCheat = 1.01;
+ fcr = mInSampleRate <= mSampleRate
+ ? 0.5 : 0.5 * mSampleRate / mInSampleRate;
+ fcr *= mPropertyCutoffPercent / 100.;
+ } else {
+ if (mFilterQuality == DYN_HIGH_QUALITY) {
+ // 32b coefficients, 64 length
+ useS32 = true;
+ stopBandAtten = 98.;
+ if (inSampleRate >= mSampleRate * 4) {
+ halfLength = 48;
+ } else if (inSampleRate >= mSampleRate * 2) {
+ halfLength = 40;
+ } else {
+ halfLength = 32;
+ }
+ } else if (mFilterQuality == DYN_LOW_QUALITY) {
+ // 16b coefficients, 16-32 length
+ useS32 = false;
+ stopBandAtten = 80.;
+ if (inSampleRate >= mSampleRate * 4) {
+ halfLength = 24;
+ } else if (inSampleRate >= mSampleRate * 2) {
+ halfLength = 16;
+ } else {
+ halfLength = 8;
+ }
+ if (inSampleRate <= mSampleRate) {
+ tbwCheat = 1.05;
+ } else {
+ tbwCheat = 1.03;
+ }
+ } else { // DYN_MED_QUALITY
+ // 16b coefficients, 32-64 length
+ // note: > 64 length filters with 16b coefs can have quantization noise problems
+ useS32 = false;
+ stopBandAtten = 84.;
+ if (inSampleRate >= mSampleRate * 4) {
+ halfLength = 32;
+ } else if (inSampleRate >= mSampleRate * 2) {
+ halfLength = 24;
+ } else {
+ halfLength = 16;
+ }
+ if (inSampleRate <= mSampleRate) {
+ tbwCheat = 1.03;
+ } else {
+ tbwCheat = 1.01;
+ }
}
}
@@ -390,8 +458,12 @@
// create the filter
mConstants.set(phases, halfLength, inSampleRate, mSampleRate);
- createKaiserFir(mConstants, stopBandAtten,
- inSampleRate, mSampleRate, tbwCheat);
+ if (fcr > 0.) {
+ createKaiserFir(mConstants, stopBandAtten, fcr);
+ } else {
+ createKaiserFir(mConstants, stopBandAtten,
+ inSampleRate, mSampleRate, tbwCheat);
+ }
} // End Kaiser filter
// update phase and state based on the new filter.
diff --git a/media/libaudioprocessing/AudioResamplerDyn.h b/media/libaudioprocessing/AudioResamplerDyn.h
index 1840fc7..92144d0 100644
--- a/media/libaudioprocessing/AudioResamplerDyn.h
+++ b/media/libaudioprocessing/AudioResamplerDyn.h
@@ -55,6 +55,39 @@
virtual size_t resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider);
+ // Make available key design criteria for testing
+ int getHalfLength() const {
+ return mConstants.mHalfNumCoefs;
+ }
+
+ const TC *getFilterCoefs() const {
+ return mConstants.mFirCoefs;
+ }
+
+ int getPhases() const {
+ return mConstants.mL;
+ }
+
+ double getStopbandAttenuationDb() const {
+ return mStopbandAttenuationDb;
+ }
+
+ double getPassbandRippleDb() const {
+ return mPassbandRippleDb;
+ }
+
+ double getNormalizedTransitionBandwidth() const {
+ return mNormalizedTransitionBandwidth;
+ }
+
+ double getFilterAttenuation() const {
+ return mFilterAttenuation;
+ }
+
+ double getNormalizedCutoffFrequency() const {
+ return mNormalizedCutoffFrequency;
+ }
+
private:
class Constants { // stores the filter constants.
@@ -112,6 +145,8 @@
void createKaiserFir(Constants &c, double stopBandAtten,
int inSampleRate, int outSampleRate, double tbwCheat);
+ void createKaiserFir(Constants &c, double stopBandAtten, double fcr);
+
template<int CHANNELS, bool LOCKED, int STRIDE>
size_t resample(TO* out, size_t outFrameCount, AudioBufferProvider* provider);
@@ -127,6 +162,38 @@
int32_t mFilterSampleRate; // designed filter sample rate.
src_quality mFilterQuality; // designed filter quality.
void* mCoefBuffer; // if a filter is created, this is not null
+
+ // Property selected design parameters.
+ // This will enable fixed high quality resampling.
+
+ // 32 char PROP_NAME_MAX limit enforced before Android O
+
+ // Use for sample rates greater than or equal to this value.
+ // Set to non-negative to enable, negative to disable.
+ int32_t mPropertyEnableAtSampleRate = 48000;
+ // "ro.audio.resampler.psd.enable_at_samplerate"
+
+ // Specify HALF the resampling filter length.
+ // Set to a value which is a multiple of 4.
+ int32_t mPropertyHalfFilterLength = 32;
+ // "ro.audio.resampler.psd.halflength"
+
+ // Specify the stopband attenuation in positive dB.
+ // Set to a value greater or equal to 20.
+ int32_t mPropertyStopbandAttenuation = 90;
+ // "ro.audio.resampler.psd.stopband"
+
+ // Specify the cutoff frequency as a percentage of Nyquist.
+ // Set to a value between 50 and 100.
+ int32_t mPropertyCutoffPercent = 100;
+ // "ro.audio.resampler.psd.cutoff_percent"
+
+ // Filter creation design parameters, see setSampleRate()
+ double mStopbandAttenuationDb = 0.;
+ double mPassbandRippleDb = 0.;
+ double mNormalizedTransitionBandwidth = 0.;
+ double mFilterAttenuation = 0.;
+ double mNormalizedCutoffFrequency = 0.;
};
} // namespace android
diff --git a/media/libaudioprocessing/AudioResamplerFirGen.h b/media/libaudioprocessing/AudioResamplerFirGen.h
index ad18965..39cafeb 100644
--- a/media/libaudioprocessing/AudioResamplerFirGen.h
+++ b/media/libaudioprocessing/AudioResamplerFirGen.h
@@ -546,8 +546,9 @@
}
wstart += wstep;
}
- // renormalize - this is only needed for integer filter types
- double norm = 1./((1ULL<<(sizeof(T)*8-1))*L);
+ // renormalize - this is needed for integer filter types, use 1 for float or double.
+ constexpr int64_t integralShift = std::is_integral<T>::value ? (sizeof(T) * 8 - 1) : 0;
+ const double norm = 1. / (L << integralShift);
firMin = fmin * norm;
firMax = fmax * norm;
@@ -557,9 +558,12 @@
* evaluates the |H(f)| lowpass band characteristics.
*
* This function tests the lowpass characteristics for the overall polyphase filter,
- * and is used to verify the design. For this case, fp should be set to the
+ * and is used to verify the design.
+ *
+ * For a polyphase filter (L > 1), typically fp should be set to the
* passband normalized frequency from 0 to 0.5 for the overall filter (thus it
* is the designed polyphase bank value / L). Likewise for fs.
+ * Similarly the stopSteps should be L * passSteps for equivalent accuracy.
*
* @param coef is the designed polyphase filter banks
*
@@ -610,6 +614,74 @@
}
/*
+ * Estimate the windowed sinc minimum passband value.
+ *
+ * This is the minimum value for a windowed sinc filter in its passband,
+ * which is identical to the scaling required not to cause overflow of a 0dBFS signal.
+ * The actual value used to attenuate the filter amplitude should be slightly
+ * smaller than this (suggest squaring) as this is just an estimate.
+ *
+ * As a windowed sinc has a passband ripple commensurate to the stopband attenuation
+ * due to Gibb's phenomenon from truncating the sinc, we derive this value from
+ * the design stopbandAttenuationDb (a positive value).
+ */
+static inline double computeWindowedSincMinimumPassbandValue(
+ double stopBandAttenuationDb) {
+ return 1. - pow(10. /* base */, stopBandAttenuationDb * (-1. / 20.));
+}
+
+/*
+ * Compute the windowed sinc passband ripple from stopband attenuation.
+ *
+ * As a windowed sinc has an passband ripple commensurate to the stopband attenuation
+ * due to Gibb's phenomenon from truncating the sinc, we derive this value from
+ * the design stopbandAttenuationDb (a positive value).
+ */
+static inline double computeWindowedSincPassbandRippleDb(
+ double stopBandAttenuationDb) {
+ return -20. * log10(computeWindowedSincMinimumPassbandValue(stopBandAttenuationDb));
+}
+
+/*
+ * Kaiser window Beta value
+ *
+ * Formula 3.2.5, 3.2.7, Vaidyanathan, _Multirate Systems and Filter Banks_, p. 48
+ * Formula 7.75, Oppenheim and Schafer, _Discrete-time Signal Processing, 3e_, p. 542
+ *
+ * See also: http://melodi.ee.washington.edu/courses/ee518/notes/lec17.pdf
+ *
+ * Kaiser window and beta parameter
+ *
+ * | 0.1102*(A - 8.7) A > 50
+ * Beta = | 0.5842*(A - 21)^0.4 + 0.07886*(A - 21) 21 < A <= 50
+ * | 0. A <= 21
+ *
+ * with A is the desired stop-band attenuation in positive dBFS
+ *
+ * 30 dB 2.210
+ * 40 dB 3.384
+ * 50 dB 4.538
+ * 60 dB 5.658
+ * 70 dB 6.764
+ * 80 dB 7.865
+ * 90 dB 8.960
+ * 100 dB 10.056
+ *
+ * For some values of stopBandAttenuationDb the function may be computed
+ * at compile time.
+ */
+static inline constexpr double computeBeta(double stopBandAttenuationDb) {
+ if (stopBandAttenuationDb > 50.) {
+ return 0.1102 * (stopBandAttenuationDb - 8.7);
+ }
+ const double offset = stopBandAttenuationDb - 21.;
+ if (offset > 0.) {
+ return 0.5842 * pow(offset, 0.4) + 0.07886 * offset;
+ }
+ return 0.;
+}
+
+/*
* Calculates the overall polyphase filter based on a windowed sinc function.
*
* The windowed sinc is an odd length symmetric filter of exactly L*halfNumCoef*2+1
@@ -642,31 +714,8 @@
template <typename T>
static inline void firKaiserGen(T* coef, int L, int halfNumCoef,
double stopBandAtten, double fcr, double atten) {
- //
- // Formula 3.2.5, 3.2.7, Vaidyanathan, _Multirate Systems and Filter Banks_, p. 48
- // Formula 7.75, Oppenheim and Schafer, _Discrete-time Signal Processing, 3e_, p. 542
- //
- // See also: http://melodi.ee.washington.edu/courses/ee518/notes/lec17.pdf
- //
- // Kaiser window and beta parameter
- //
- // | 0.1102*(A - 8.7) A > 50
- // beta = | 0.5842*(A - 21)^0.4 + 0.07886*(A - 21) 21 <= A <= 50
- // | 0. A < 21
- //
- // with A is the desired stop-band attenuation in dBFS
- //
- // 30 dB 2.210
- // 40 dB 3.384
- // 50 dB 4.538
- // 60 dB 5.658
- // 70 dB 6.764
- // 80 dB 7.865
- // 90 dB 8.960
- // 100 dB 10.056
-
const int N = L * halfNumCoef; // non-negative half
- const double beta = 0.1102 * (stopBandAtten - 8.7); // >= 50dB always
+ const double beta = computeBeta(stopBandAtten);
const double xstep = (2. * M_PI) * fcr / L;
const double xfrac = 1. / N;
const double yscale = atten * L / (I0(beta) * M_PI);
@@ -696,9 +745,9 @@
sg.advance();
}
- if (is_same<T, int16_t>::value) { // int16_t needs noise shaping
+ if (std::is_same<T, int16_t>::value) { // int16_t needs noise shaping
*coef++ = static_cast<T>(toint(y, 1ULL<<(sizeof(T)*8-1), err));
- } else if (is_same<T, int32_t>::value) {
+ } else if (std::is_same<T, int32_t>::value) {
*coef++ = static_cast<T>(toint(y, 1ULL<<(sizeof(T)*8-1)));
} else { // assumed float or double
*coef++ = static_cast<T>(y);
diff --git a/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh b/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh
index 704d095..efef417 100755
--- a/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh
+++ b/media/libaudioprocessing/tests/build_and_run_all_unit_tests.sh
@@ -14,8 +14,8 @@
echo "waiting for device"
adb root && adb wait-for-device remount
-adb push $OUT/system/lib/libaudioresampler.so /system/lib
-adb push $OUT/system/lib64/libaudioresampler.so /system/lib64
+adb push $OUT/system/lib/libaudioprocessing.so /system/lib
+adb push $OUT/system/lib64/libaudioprocessing.so /system/lib64
adb push $OUT/data/nativetest/resampler_tests/resampler_tests /data/nativetest/resampler_tests/resampler_tests
adb push $OUT/data/nativetest64/resampler_tests/resampler_tests /data/nativetest64/resampler_tests/resampler_tests
diff --git a/media/libaudioprocessing/tests/resampler_tests.cpp b/media/libaudioprocessing/tests/resampler_tests.cpp
index a23c000..e1623f7 100644
--- a/media/libaudioprocessing/tests/resampler_tests.cpp
+++ b/media/libaudioprocessing/tests/resampler_tests.cpp
@@ -29,6 +29,7 @@
#include <unistd.h>
#include <iostream>
+#include <memory>
#include <utility>
#include <vector>
@@ -37,6 +38,8 @@
#include <media/AudioBufferProvider.h>
#include <media/AudioResampler.h>
+#include "../AudioResamplerDyn.h"
+#include "../AudioResamplerFirGen.h"
#include "test_utils.h"
template <typename T>
@@ -242,6 +245,60 @@
delete resampler;
}
+void testFilterResponse(
+ size_t channels, unsigned inputFreq, unsigned outputFreq)
+{
+ // create resampler
+ using ResamplerType = android::AudioResamplerDyn<float, float, float>;
+ std::unique_ptr<ResamplerType> rdyn(
+ static_cast<ResamplerType *>(
+ android::AudioResampler::create(
+ AUDIO_FORMAT_PCM_FLOAT,
+ channels,
+ outputFreq,
+ android::AudioResampler::DYN_HIGH_QUALITY)));
+ rdyn->setSampleRate(inputFreq);
+
+ // get design parameters
+ const int phases = rdyn->getPhases();
+ const int halfLength = rdyn->getHalfLength();
+ const float *coefs = rdyn->getFilterCoefs();
+ const double fcr = rdyn->getNormalizedCutoffFrequency();
+ const double tbw = rdyn->getNormalizedTransitionBandwidth();
+ const double attenuation = rdyn->getFilterAttenuation();
+ const double stopbandDb = rdyn->getStopbandAttenuationDb();
+ const double passbandDb = rdyn->getPassbandRippleDb();
+ const double fp = fcr - tbw / 2;
+ const double fs = fcr + tbw / 2;
+
+ printf("inputFreq:%d outputFreq:%d design"
+ " phases:%d halfLength:%d"
+ " fcr:%lf fp:%lf fs:%lf tbw:%lf"
+ " attenuation:%lf stopRipple:%.lf passRipple:%lf"
+ "\n",
+ inputFreq, outputFreq,
+ phases, halfLength,
+ fcr, fp, fs, tbw,
+ attenuation, stopbandDb, passbandDb);
+
+ // verify design parameters
+ constexpr int32_t passSteps = 1000;
+ double passMin, passMax, passRipple, stopMax, stopRipple;
+ android::testFir(coefs, phases, halfLength, fp / phases, fs / phases,
+ passSteps, phases * passSteps /* stopSteps */,
+ passMin, passMax, passRipple,
+ stopMax, stopRipple);
+ printf("inputFreq:%d outputFreq:%d verify"
+ " passMin:%lf passMax:%lf passRipple:%lf stopMax:%lf stopRipple:%lf"
+ "\n",
+ inputFreq, outputFreq,
+ passMin, passMax, passRipple, stopMax, stopRipple);
+
+ ASSERT_GT(stopRipple, 60.); // enough stopband attenuation
+ ASSERT_LT(passRipple, 0.2); // small passband ripple
+ ASSERT_GT(passMin, 0.99); // we do not attenuate the signal (ideally 1.)
+}
+
/* Buffer increment test
*
* We compare a reference output, where we consume and process the entire
@@ -484,3 +541,30 @@
}
}
+TEST(audioflinger_resampler, filterresponse) {
+ std::vector<int> inSampleRates{
+ 8000,
+ 11025,
+ 12000,
+ 16000,
+ 22050,
+ 24000,
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ };
+ std::vector<int> outSampleRates{
+ 48000,
+ 96000,
+ };
+
+ for (int outSampleRate : outSampleRates) {
+ for (int inSampleRate : inSampleRates) {
+ testFilterResponse(2 /* channels */, inSampleRate, outSampleRate);
+ }
+ }
+}