/* * Copyright (C) 2014 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_NDEBUG 0 #define LOG_TAG "audioflinger_resampler_tests" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../AudioResamplerDyn.h" #include "../AudioResamplerFirGen.h" #include "test_utils.h" template static void printData(T *data, size_t size) { const size_t stride = 8; for (size_t i = 0; i < size; ) { for (size_t j = 0; j < stride && i < size; ++j) { std::cout << data[i++] << ' '; // extra space before newline } std::cout << '\n'; // or endl } } void resample(int channels, void *output, size_t outputFrames, const std::vector &outputIncr, android::AudioBufferProvider *provider, android::AudioResampler *resampler) { for (size_t i = 0, j = 0; i < outputFrames; ) { size_t thisFrames = outputIncr[j++]; if (j >= outputIncr.size()) { j = 0; } if (thisFrames == 0 || thisFrames > outputFrames - i) { thisFrames = outputFrames - i; } size_t framesResampled = resampler->resample( (int32_t*) output + channels*i, thisFrames, provider); // we should have enough buffer space, so there is no short count. ASSERT_EQ(thisFrames, framesResampled); i += thisFrames; } } void buffercmp(const void *reference, const void *test, size_t outputFrameSize, size_t outputFrames) { for (size_t i = 0; i < outputFrames; ++i) { int check = memcmp((const char*)reference + i * outputFrameSize, (const char*)test + i * outputFrameSize, outputFrameSize); if (check) { ALOGE("Failure at frame %zu", i); ASSERT_EQ(check, 0); /* fails */ } } } void testBufferIncrement(size_t channels, bool useFloat, unsigned inputFreq, unsigned outputFreq, enum android::AudioResampler::src_quality quality) { const audio_format_t format = useFloat ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT; // create the provider std::vector inputIncr; SignalProvider provider; if (useFloat) { provider.setChirp(channels, 0., outputFreq/2., outputFreq, outputFreq/2000.); } else { provider.setChirp(channels, 0., outputFreq/2., outputFreq, outputFreq/2000.); } provider.setIncr(inputIncr); // calculate the output size size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq; size_t outputFrameSize = (channels == 1 ? 2 : channels) * (useFloat ? sizeof(float) : sizeof(int32_t)); size_t outputSize = outputFrameSize * outputFrames; outputSize &= ~7; // create the resampler android::AudioResampler* resampler; resampler = android::AudioResampler::create(format, channels, outputFreq, quality); resampler->setSampleRate(inputFreq); resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT, android::AudioResampler::UNITY_GAIN_FLOAT); // set up the reference run std::vector refIncr; refIncr.push_back(outputFrames); void* reference = calloc(outputFrames, outputFrameSize); resample(channels, reference, outputFrames, refIncr, &provider, resampler); provider.reset(); #if 0 /* this test will fail - API interface issue: reset() does not clear internal buffers */ resampler->reset(); #else delete resampler; resampler = android::AudioResampler::create(format, channels, outputFreq, quality); resampler->setSampleRate(inputFreq); resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT, android::AudioResampler::UNITY_GAIN_FLOAT); #endif // set up the test run std::vector outIncr; outIncr.push_back(1); outIncr.push_back(2); outIncr.push_back(3); void* test = calloc(outputFrames, outputFrameSize); inputIncr.push_back(1); inputIncr.push_back(3); provider.setIncr(inputIncr); resample(channels, test, outputFrames, outIncr, &provider, resampler); // check buffercmp(reference, test, outputFrameSize, outputFrames); free(reference); free(test); delete resampler; } template inline double sqr(T v) { double dv = static_cast(v); return dv * dv; } template double signalEnergy(T *start, T *end, unsigned stride) { double accum = 0; for (T *p = start; p < end; p += stride) { accum += sqr(*p); } unsigned count = (end - start + stride - 1) / stride; return accum / count; } // TI = resampler input type, int16_t or float // TO = resampler output type, int32_t or float template void testStopbandDownconversion(size_t channels, unsigned inputFreq, unsigned outputFreq, unsigned passband, unsigned stopband, enum android::AudioResampler::src_quality quality) { // create the provider std::vector inputIncr; SignalProvider provider; provider.setChirp(channels, 0., inputFreq/2., inputFreq, inputFreq/2000.); provider.setIncr(inputIncr); // calculate the output size size_t outputFrames = ((int64_t) provider.getNumFrames() * outputFreq) / inputFreq; size_t outputFrameSize = (channels == 1 ? 2 : channels) * sizeof(TO); size_t outputSize = outputFrameSize * outputFrames; outputSize &= ~7; // create the resampler android::AudioResampler* resampler; resampler = android::AudioResampler::create( is_same::value ? AUDIO_FORMAT_PCM_16_BIT : AUDIO_FORMAT_PCM_FLOAT, channels, outputFreq, quality); resampler->setSampleRate(inputFreq); resampler->setVolume(android::AudioResampler::UNITY_GAIN_FLOAT, android::AudioResampler::UNITY_GAIN_FLOAT); // set up the reference run std::vector refIncr; refIncr.push_back(outputFrames); void* reference = calloc(outputFrames, outputFrameSize); resample(channels, reference, outputFrames, refIncr, &provider, resampler); TO *out = reinterpret_cast(reference); // check signal energy in passband const unsigned passbandFrame = passband * outputFreq / 1000.; const unsigned stopbandFrame = stopband * outputFreq / 1000.; // check each channel separately if (channels == 1) channels = 2; // workaround (mono duplicates output channel) for (size_t i = 0; i < channels; ++i) { double passbandEnergy = signalEnergy(out, out + passbandFrame * channels, channels); double stopbandEnergy = signalEnergy(out + stopbandFrame * channels, out + outputFrames * channels, channels); double dbAtten = -10. * log10(stopbandEnergy / passbandEnergy); ASSERT_GT(dbAtten, 60.); #if 0 // internal verification printf("if:%d of:%d pbf:%d sbf:%d sbe: %f pbe: %f db: %.2f\n", provider.getNumFrames(), outputFrames, passbandFrame, stopbandFrame, stopbandEnergy, passbandEnergy, dbAtten); for (size_t i = 0; i < 10; ++i) { std::cout << out[i+passbandFrame*channels] << std::endl; } for (size_t i = 0; i < 10; ++i) { std::cout << out[i+stopbandFrame*channels] << std::endl; } #endif } free(reference); delete resampler; } void testFilterResponse( size_t channels, unsigned inputFreq, unsigned outputFreq, android::AudioResampler::src_quality quality = android::AudioResampler::DYN_HIGH_QUALITY) { // create resampler using ResamplerType = android::AudioResamplerDyn; std::unique_ptr rdyn( static_cast( android::AudioResampler::create( AUDIO_FORMAT_PCM_FLOAT, channels, outputFreq, 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 * 0.5; const double fs = fcr + tbw * 0.5; const double idealfs = inputFreq <= outputFreq ? 0.5 // upsample : 0.5 * outputFreq / inputFreq; // downsample printf("inputFreq:%d outputFreq:%d design quality %d" " phases:%d halfLength:%d" " fcr:%lf fp:%lf fs:%lf tbw:%lf fcrp:%lf" " attenuation:%lf stopRipple:%.lf passRipple:%lf" "\n", inputFreq, outputFreq, quality, phases, halfLength, fcr, fp, fs, tbw, fcr * 100. / idealfs, 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 * buffer at a time, and a test output, where we provide small chunks of input * data and process small chunks of output (which may not be equivalent in size). * * Two subtests - fixed phase (3:2 down) and interpolated phase (147:320 up) */ TEST(audioflinger_resampler, bufferincrement_fixedphase) { // all of these work static const enum android::AudioResampler::src_quality kQualityArray[] = { android::AudioResampler::LOW_QUALITY, android::AudioResampler::MED_QUALITY, android::AudioResampler::HIGH_QUALITY, android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testBufferIncrement(2, false, 48000, 32000, kQualityArray[i]); } } TEST(audioflinger_resampler, bufferincrement_interpolatedphase) { // all of these work except low quality static const enum android::AudioResampler::src_quality kQualityArray[] = { // android::AudioResampler::LOW_QUALITY, android::AudioResampler::MED_QUALITY, android::AudioResampler::HIGH_QUALITY, android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testBufferIncrement(2, false, 22050, 48000, kQualityArray[i]); } } TEST(audioflinger_resampler, bufferincrement_fixedphase_multi) { // only dynamic quality static const enum android::AudioResampler::src_quality kQualityArray[] = { android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testBufferIncrement(4, false, 48000, 32000, kQualityArray[i]); } } TEST(audioflinger_resampler, bufferincrement_interpolatedphase_multi_float) { // only dynamic quality static const enum android::AudioResampler::src_quality kQualityArray[] = { android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testBufferIncrement(8, true, 22050, 48000, kQualityArray[i]); } } /* Simple aliasing test * * This checks stopband response of the chirp signal to make sure frequencies * are properly suppressed. It uses downsampling because the stopband can be * clearly isolated by input frequencies exceeding the output sample rate (nyquist). */ TEST(audioflinger_resampler, stopbandresponse_integer) { // not all of these may work (old resamplers fail on downsampling) static const enum android::AudioResampler::src_quality kQualityArray[] = { //android::AudioResampler::LOW_QUALITY, //android::AudioResampler::MED_QUALITY, //android::AudioResampler::HIGH_QUALITY, //android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; // in this test we assume a maximum transition band between 12kHz and 20kHz. // there must be at least 60dB relative attenuation between stopband and passband. for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 2, 48000, 32000, 12000, 20000, kQualityArray[i]); } // in this test we assume a maximum transition band between 7kHz and 15kHz. // there must be at least 60dB relative attenuation between stopband and passband. // (the weird ratio triggers interpolative resampling) for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 2, 48000, 22101, 7000, 15000, kQualityArray[i]); } } TEST(audioflinger_resampler, stopbandresponse_integer_mono) { // not all of these may work (old resamplers fail on downsampling) static const enum android::AudioResampler::src_quality kQualityArray[] = { //android::AudioResampler::LOW_QUALITY, //android::AudioResampler::MED_QUALITY, //android::AudioResampler::HIGH_QUALITY, //android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; // in this test we assume a maximum transition band between 12kHz and 20kHz. // there must be at least 60dB relative attenuation between stopband and passband. for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 1, 48000, 32000, 12000, 20000, kQualityArray[i]); } // in this test we assume a maximum transition band between 7kHz and 15kHz. // there must be at least 60dB relative attenuation between stopband and passband. // (the weird ratio triggers interpolative resampling) for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 1, 48000, 22101, 7000, 15000, kQualityArray[i]); } } TEST(audioflinger_resampler, stopbandresponse_integer_multichannel) { // not all of these may work (old resamplers fail on downsampling) static const enum android::AudioResampler::src_quality kQualityArray[] = { //android::AudioResampler::LOW_QUALITY, //android::AudioResampler::MED_QUALITY, //android::AudioResampler::HIGH_QUALITY, //android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; // in this test we assume a maximum transition band between 12kHz and 20kHz. // there must be at least 60dB relative attenuation between stopband and passband. for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 8, 48000, 32000, 12000, 20000, kQualityArray[i]); } // in this test we assume a maximum transition band between 7kHz and 15kHz. // there must be at least 60dB relative attenuation between stopband and passband. // (the weird ratio triggers interpolative resampling) for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 8, 48000, 22101, 7000, 15000, kQualityArray[i]); } } TEST(audioflinger_resampler, stopbandresponse_float) { // not all of these may work (old resamplers fail on downsampling) static const enum android::AudioResampler::src_quality kQualityArray[] = { //android::AudioResampler::LOW_QUALITY, //android::AudioResampler::MED_QUALITY, //android::AudioResampler::HIGH_QUALITY, //android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; // in this test we assume a maximum transition band between 12kHz and 20kHz. // there must be at least 60dB relative attenuation between stopband and passband. for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 2, 48000, 32000, 12000, 20000, kQualityArray[i]); } // in this test we assume a maximum transition band between 7kHz and 15kHz. // there must be at least 60dB relative attenuation between stopband and passband. // (the weird ratio triggers interpolative resampling) for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 2, 48000, 22101, 7000, 15000, kQualityArray[i]); } } TEST(audioflinger_resampler, stopbandresponse_float_mono) { // not all of these may work (old resamplers fail on downsampling) static const enum android::AudioResampler::src_quality kQualityArray[] = { //android::AudioResampler::LOW_QUALITY, //android::AudioResampler::MED_QUALITY, //android::AudioResampler::HIGH_QUALITY, //android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; // in this test we assume a maximum transition band between 12kHz and 20kHz. // there must be at least 60dB relative attenuation between stopband and passband. for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 1, 48000, 32000, 12000, 20000, kQualityArray[i]); } // in this test we assume a maximum transition band between 7kHz and 15kHz. // there must be at least 60dB relative attenuation between stopband and passband. // (the weird ratio triggers interpolative resampling) for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 1, 48000, 22101, 7000, 15000, kQualityArray[i]); } } TEST(audioflinger_resampler, stopbandresponse_float_multichannel) { // not all of these may work (old resamplers fail on downsampling) static const enum android::AudioResampler::src_quality kQualityArray[] = { //android::AudioResampler::LOW_QUALITY, //android::AudioResampler::MED_QUALITY, //android::AudioResampler::HIGH_QUALITY, //android::AudioResampler::VERY_HIGH_QUALITY, android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; // in this test we assume a maximum transition band between 12kHz and 20kHz. // there must be at least 60dB relative attenuation between stopband and passband. for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 8, 48000, 32000, 12000, 20000, kQualityArray[i]); } // in this test we assume a maximum transition band between 7kHz and 15kHz. // there must be at least 60dB relative attenuation between stopband and passband. // (the weird ratio triggers interpolative resampling) for (size_t i = 0; i < ARRAY_SIZE(kQualityArray); ++i) { testStopbandDownconversion( 8, 48000, 22101, 7000, 15000, kQualityArray[i]); } } // Selected downsampling responses for various frequencies relating to hearing aid. TEST(audioflinger_resampler, downsamplingresponse) { static constexpr android::AudioResampler::src_quality qualities[] = { android::AudioResampler::DYN_LOW_QUALITY, android::AudioResampler::DYN_MED_QUALITY, android::AudioResampler::DYN_HIGH_QUALITY, }; static constexpr int inSampleRates[] = { 32000, 44100, 48000, }; static constexpr int outSampleRates[] = { 16000, 24000, }; for (auto quality : qualities) { for (int outSampleRate : outSampleRates) { for (int inSampleRate : inSampleRates) { testFilterResponse(2 /* channels */, inSampleRate, outSampleRate, quality); } } } } // General responses for typical output device scenarios - 44.1, 48, 96 kHz // (48, 96 are part of the same resampler generation family). TEST(audioflinger_resampler, generalresponse) { static constexpr int inSampleRates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000, }; static constexpr int outSampleRates[] = { 44100, 48000, 96000, }; for (int outSampleRate : outSampleRates) { for (int inSampleRate : inSampleRates) { testFilterResponse(2 /* channels */, inSampleRate, outSampleRate); } } }