| /* |
| * Copyright 2022 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. |
| */ |
| |
| /* |
| * Test FlowGraph |
| * |
| * This file also tests a few different conversion techniques because |
| * sometimes that have caused compiler bugs. |
| */ |
| |
| #include <iostream> |
| |
| #include <gtest/gtest.h> |
| |
| #include "flowgraph/resampler/MultiChannelResampler.h" |
| |
| using namespace RESAMPLER_OUTER_NAMESPACE::resampler; |
| |
| // Measure zero crossings. |
| static int32_t countZeroCrossingsWithHysteresis(float *input, int32_t numSamples) { |
| const float kHysteresisLevel = 0.25f; |
| int zeroCrossingCount = 0; |
| int state = 0; // can be -1, 0, +1 |
| for (int i = 0; i < numSamples; i++) { |
| if (input[i] >= kHysteresisLevel) { |
| if (state < 0) { |
| zeroCrossingCount++; |
| } |
| state = 1; |
| } else if (input[i] <= -kHysteresisLevel) { |
| if (state > 0) { |
| zeroCrossingCount++; |
| } |
| state = -1; |
| } |
| } |
| return zeroCrossingCount; |
| } |
| |
| static constexpr int kChannelCount = 1; |
| |
| /** |
| * Convert a sine wave and then look for glitches. |
| * Glitches have a high value in the second derivative. |
| */ |
| static void checkResampler(int32_t sourceRate, int32_t sinkRate, |
| MultiChannelResampler::Quality quality) { |
| const int kNumOutputSamples = 10000; |
| const double framesPerCycle = 81.379; // target output period |
| |
| int numInputSamples = kNumOutputSamples * sourceRate / sinkRate; |
| |
| std::unique_ptr<float[]> inputBuffer = std::make_unique<float[]>(numInputSamples); |
| std::unique_ptr<float[]> outputBuffer = std::make_unique<float[]>(kNumOutputSamples); |
| |
| // Generate a sine wave for input. |
| const double kPhaseIncrement = 2.0 * sinkRate / (framesPerCycle * sourceRate); |
| double phase = 0.0; |
| for (int i = 0; i < numInputSamples; i++) { |
| inputBuffer[i] = sin(phase * M_PI); |
| phase += kPhaseIncrement; |
| while (phase > 1.0) { |
| phase -= 2.0; |
| } |
| } |
| int sourceZeroCrossingCount = countZeroCrossingsWithHysteresis( |
| inputBuffer.get(), numInputSamples); |
| |
| // Use a MultiChannelResampler to convert from the sourceRate to the sinkRate. |
| std::unique_ptr<MultiChannelResampler> mcResampler; |
| mcResampler.reset(MultiChannelResampler::make(kChannelCount, |
| sourceRate, |
| sinkRate, |
| quality)); |
| int inputFramesLeft = numInputSamples; |
| int numRead = 0; |
| float *input = inputBuffer.get(); // for iteration |
| float *output = outputBuffer.get(); |
| while (inputFramesLeft > 0) { |
| if (mcResampler->isWriteNeeded()) { |
| mcResampler->writeNextFrame(input); |
| input++; |
| inputFramesLeft--; |
| } else { |
| mcResampler->readNextFrame(output); |
| output++; |
| numRead++; |
| } |
| } |
| |
| ASSERT_LE(numRead, kNumOutputSamples); |
| // Some frames are lost priming the FIR filter. |
| const int kMaxAlgorithmicFrameLoss = 16; |
| EXPECT_GT(numRead, kNumOutputSamples - kMaxAlgorithmicFrameLoss); |
| |
| int sinkZeroCrossingCount = countZeroCrossingsWithHysteresis(outputBuffer.get(), numRead); |
| // Some cycles may get chopped off at the end. |
| const int kMaxZeroCrossingDelta = 3; |
| EXPECT_LE(abs(sourceZeroCrossingCount - sinkZeroCrossingCount), kMaxZeroCrossingDelta); |
| |
| // Detect glitches by looking for spikes in the second derivative. |
| output = outputBuffer.get(); |
| float previousValue = output[0]; |
| float previousSlope = output[1] - output[0]; |
| for (int i = 0; i < numRead; i++) { |
| float slope = output[i] - previousValue; |
| float slopeDelta = fabs(slope - previousSlope); |
| // Skip a few samples because there are often some steep slope changes at the beginning. |
| if (i > 10) { |
| EXPECT_LT(slopeDelta, 0.1); |
| } |
| previousValue = output[i]; |
| previousSlope = slope; |
| } |
| |
| #if 0 |
| // Save to disk for inspection. |
| FILE *fp = fopen( "/sdcard/Download/src_float_out.raw" , "wb" ); |
| fwrite(outputBuffer.get(), sizeof(float), numRead, fp ); |
| fclose(fp); |
| #endif |
| } |
| |
| |
| TEST(test_resampler, resampler_scan_all) { |
| // TODO Add 64000, 88200, 96000 when they work. Failing now. |
| const int rates[] = {8000, 11025, 22050, 32000, 44100, 48000}; |
| const MultiChannelResampler::Quality qualities[] = |
| { |
| MultiChannelResampler::Quality::Fastest, |
| MultiChannelResampler::Quality::Low, |
| MultiChannelResampler::Quality::Medium, |
| MultiChannelResampler::Quality::High, |
| MultiChannelResampler::Quality::Best |
| }; |
| for (int srcRate : rates) { |
| for (int destRate : rates) { |
| for (auto quality : qualities) { |
| if (srcRate != destRate) { |
| checkResampler(srcRate, destRate, quality); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(test_resampler, resampler_8000_11025_best) { |
| checkResampler(8000, 11025, MultiChannelResampler::Quality::Best); |
| } |
| TEST(test_resampler, resampler_8000_48000_best) { |
| checkResampler(8000, 48000, MultiChannelResampler::Quality::Best); |
| } |
| |
| TEST(test_resampler, resampler_8000_44100_best) { |
| checkResampler(8000, 44100, MultiChannelResampler::Quality::Best); |
| } |
| |
| TEST(test_resampler, resampler_11025_24000_best) { |
| checkResampler(11025, 24000, MultiChannelResampler::Quality::Best); |
| } |
| |
| TEST(test_resampler, resampler_11025_48000_fastest) { |
| checkResampler(11025, 48000, MultiChannelResampler::Quality::Fastest); |
| } |
| TEST(test_resampler, resampler_11025_48000_low) { |
| checkResampler(11025, 48000, MultiChannelResampler::Quality::Low); |
| } |
| TEST(test_resampler, resampler_11025_48000_medium) { |
| checkResampler(11025, 48000, MultiChannelResampler::Quality::Medium); |
| } |
| TEST(test_resampler, resampler_11025_48000_high) { |
| checkResampler(11025, 48000, MultiChannelResampler::Quality::High); |
| } |
| |
| TEST(test_resampler, resampler_11025_48000_best) { |
| checkResampler(11025, 48000, MultiChannelResampler::Quality::Best); |
| } |
| |
| TEST(test_resampler, resampler_11025_44100_best) { |
| checkResampler(11025, 44100, MultiChannelResampler::Quality::Best); |
| } |
| |
| // TODO This fails because the output is very low. |
| //TEST(test_resampler, resampler_11025_88200_best) { |
| // checkResampler(11025, 88200, MultiChannelResampler::Quality::Best); |
| //} |
| |
| TEST(test_resampler, resampler_16000_48000_best) { |
| checkResampler(16000, 48000, MultiChannelResampler::Quality::Best); |
| } |
| |
| TEST(test_resampler, resampler_44100_48000_low) { |
| checkResampler(44100, 48000, MultiChannelResampler::Quality::Low); |
| } |
| TEST(test_resampler, resampler_44100_48000_best) { |
| checkResampler(44100, 48000, MultiChannelResampler::Quality::Best); |
| } |
| |
| // Look for glitches when downsampling. |
| TEST(test_resampler, resampler_48000_11025_best) { |
| checkResampler(48000, 11025, MultiChannelResampler::Quality::Best); |
| } |
| TEST(test_resampler, resampler_48000_44100_best) { |
| checkResampler(48000, 44100, MultiChannelResampler::Quality::Best); |
| } |
| TEST(test_resampler, resampler_44100_11025_best) { |
| checkResampler(44100, 11025, MultiChannelResampler::Quality::Best); |
| } |