Robert Wu | 9757d3c | 2022-12-14 21:35:20 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2022 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | /* |
| 18 | * Test FlowGraph |
| 19 | * |
| 20 | * This file also tests a few different conversion techniques because |
| 21 | * sometimes that have caused compiler bugs. |
| 22 | */ |
| 23 | |
| 24 | #include <iostream> |
| 25 | |
| 26 | #include <gtest/gtest.h> |
| 27 | |
| 28 | #include "flowgraph/resampler/MultiChannelResampler.h" |
| 29 | |
| 30 | using namespace RESAMPLER_OUTER_NAMESPACE::resampler; |
| 31 | |
| 32 | // Measure zero crossings. |
| 33 | static int32_t countZeroCrossingsWithHysteresis(float *input, int32_t numSamples) { |
| 34 | const float kHysteresisLevel = 0.25f; |
| 35 | int zeroCrossingCount = 0; |
| 36 | int state = 0; // can be -1, 0, +1 |
| 37 | for (int i = 0; i < numSamples; i++) { |
| 38 | if (input[i] >= kHysteresisLevel) { |
| 39 | if (state < 0) { |
| 40 | zeroCrossingCount++; |
| 41 | } |
| 42 | state = 1; |
| 43 | } else if (input[i] <= -kHysteresisLevel) { |
| 44 | if (state > 0) { |
| 45 | zeroCrossingCount++; |
| 46 | } |
| 47 | state = -1; |
| 48 | } |
| 49 | } |
| 50 | return zeroCrossingCount; |
| 51 | } |
| 52 | |
| 53 | static constexpr int kChannelCount = 1; |
| 54 | |
| 55 | /** |
| 56 | * Convert a sine wave and then look for glitches. |
| 57 | * Glitches have a high value in the second derivative. |
| 58 | */ |
| 59 | static void checkResampler(int32_t sourceRate, int32_t sinkRate, |
| 60 | MultiChannelResampler::Quality quality) { |
| 61 | const int kNumOutputSamples = 10000; |
| 62 | const double framesPerCycle = 81.379; // target output period |
| 63 | |
| 64 | int numInputSamples = kNumOutputSamples * sourceRate / sinkRate; |
| 65 | |
| 66 | std::unique_ptr<float[]> inputBuffer = std::make_unique<float[]>(numInputSamples); |
| 67 | std::unique_ptr<float[]> outputBuffer = std::make_unique<float[]>(kNumOutputSamples); |
| 68 | |
| 69 | // Generate a sine wave for input. |
| 70 | const double kPhaseIncrement = 2.0 * sinkRate / (framesPerCycle * sourceRate); |
| 71 | double phase = 0.0; |
| 72 | for (int i = 0; i < numInputSamples; i++) { |
| 73 | inputBuffer[i] = sin(phase * M_PI); |
| 74 | phase += kPhaseIncrement; |
| 75 | while (phase > 1.0) { |
| 76 | phase -= 2.0; |
| 77 | } |
| 78 | } |
| 79 | int sourceZeroCrossingCount = countZeroCrossingsWithHysteresis( |
| 80 | inputBuffer.get(), numInputSamples); |
| 81 | |
| 82 | // Use a MultiChannelResampler to convert from the sourceRate to the sinkRate. |
| 83 | std::unique_ptr<MultiChannelResampler> mcResampler; |
| 84 | mcResampler.reset(MultiChannelResampler::make(kChannelCount, |
| 85 | sourceRate, |
| 86 | sinkRate, |
| 87 | quality)); |
| 88 | int inputFramesLeft = numInputSamples; |
| 89 | int numRead = 0; |
| 90 | float *input = inputBuffer.get(); // for iteration |
| 91 | float *output = outputBuffer.get(); |
| 92 | while (inputFramesLeft > 0) { |
| 93 | if (mcResampler->isWriteNeeded()) { |
| 94 | mcResampler->writeNextFrame(input); |
| 95 | input++; |
| 96 | inputFramesLeft--; |
| 97 | } else { |
| 98 | mcResampler->readNextFrame(output); |
| 99 | output++; |
| 100 | numRead++; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | ASSERT_LE(numRead, kNumOutputSamples); |
| 105 | // Some frames are lost priming the FIR filter. |
| 106 | const int kMaxAlgorithmicFrameLoss = 16; |
| 107 | EXPECT_GT(numRead, kNumOutputSamples - kMaxAlgorithmicFrameLoss); |
| 108 | |
| 109 | int sinkZeroCrossingCount = countZeroCrossingsWithHysteresis(outputBuffer.get(), numRead); |
| 110 | // Some cycles may get chopped off at the end. |
| 111 | const int kMaxZeroCrossingDelta = 3; |
| 112 | EXPECT_LE(abs(sourceZeroCrossingCount - sinkZeroCrossingCount), kMaxZeroCrossingDelta); |
| 113 | |
| 114 | // Detect glitches by looking for spikes in the second derivative. |
| 115 | output = outputBuffer.get(); |
| 116 | float previousValue = output[0]; |
| 117 | float previousSlope = output[1] - output[0]; |
| 118 | for (int i = 0; i < numRead; i++) { |
| 119 | float slope = output[i] - previousValue; |
| 120 | float slopeDelta = fabs(slope - previousSlope); |
| 121 | // Skip a few samples because there are often some steep slope changes at the beginning. |
| 122 | if (i > 10) { |
| 123 | EXPECT_LT(slopeDelta, 0.1); |
| 124 | } |
| 125 | previousValue = output[i]; |
| 126 | previousSlope = slope; |
| 127 | } |
| 128 | |
| 129 | #if 0 |
| 130 | // Save to disk for inspection. |
| 131 | FILE *fp = fopen( "/sdcard/Download/src_float_out.raw" , "wb" ); |
| 132 | fwrite(outputBuffer.get(), sizeof(float), numRead, fp ); |
| 133 | fclose(fp); |
| 134 | #endif |
| 135 | } |
| 136 | |
| 137 | |
| 138 | TEST(test_resampler, resampler_scan_all) { |
| 139 | // TODO Add 64000, 88200, 96000 when they work. Failing now. |
| 140 | const int rates[] = {8000, 11025, 22050, 32000, 44100, 48000}; |
| 141 | const MultiChannelResampler::Quality qualities[] = |
| 142 | { |
| 143 | MultiChannelResampler::Quality::Fastest, |
| 144 | MultiChannelResampler::Quality::Low, |
| 145 | MultiChannelResampler::Quality::Medium, |
| 146 | MultiChannelResampler::Quality::High, |
| 147 | MultiChannelResampler::Quality::Best |
| 148 | }; |
| 149 | for (int srcRate : rates) { |
| 150 | for (int destRate : rates) { |
| 151 | for (auto quality : qualities) { |
| 152 | if (srcRate != destRate) { |
| 153 | checkResampler(srcRate, destRate, quality); |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | TEST(test_resampler, resampler_8000_11025_best) { |
| 161 | checkResampler(8000, 11025, MultiChannelResampler::Quality::Best); |
| 162 | } |
| 163 | TEST(test_resampler, resampler_8000_48000_best) { |
| 164 | checkResampler(8000, 48000, MultiChannelResampler::Quality::Best); |
| 165 | } |
| 166 | |
| 167 | TEST(test_resampler, resampler_8000_44100_best) { |
| 168 | checkResampler(8000, 44100, MultiChannelResampler::Quality::Best); |
| 169 | } |
| 170 | |
| 171 | TEST(test_resampler, resampler_11025_24000_best) { |
| 172 | checkResampler(11025, 24000, MultiChannelResampler::Quality::Best); |
| 173 | } |
| 174 | |
| 175 | TEST(test_resampler, resampler_11025_48000_fastest) { |
| 176 | checkResampler(11025, 48000, MultiChannelResampler::Quality::Fastest); |
| 177 | } |
| 178 | TEST(test_resampler, resampler_11025_48000_low) { |
| 179 | checkResampler(11025, 48000, MultiChannelResampler::Quality::Low); |
| 180 | } |
| 181 | TEST(test_resampler, resampler_11025_48000_medium) { |
| 182 | checkResampler(11025, 48000, MultiChannelResampler::Quality::Medium); |
| 183 | } |
| 184 | TEST(test_resampler, resampler_11025_48000_high) { |
| 185 | checkResampler(11025, 48000, MultiChannelResampler::Quality::High); |
| 186 | } |
| 187 | |
| 188 | TEST(test_resampler, resampler_11025_48000_best) { |
| 189 | checkResampler(11025, 48000, MultiChannelResampler::Quality::Best); |
| 190 | } |
| 191 | |
| 192 | TEST(test_resampler, resampler_11025_44100_best) { |
| 193 | checkResampler(11025, 44100, MultiChannelResampler::Quality::Best); |
| 194 | } |
| 195 | |
| 196 | // TODO This fails because the output is very low. |
| 197 | //TEST(test_resampler, resampler_11025_88200_best) { |
| 198 | // checkResampler(11025, 88200, MultiChannelResampler::Quality::Best); |
| 199 | //} |
| 200 | |
| 201 | TEST(test_resampler, resampler_16000_48000_best) { |
| 202 | checkResampler(16000, 48000, MultiChannelResampler::Quality::Best); |
| 203 | } |
| 204 | |
| 205 | TEST(test_resampler, resampler_44100_48000_low) { |
| 206 | checkResampler(44100, 48000, MultiChannelResampler::Quality::Low); |
| 207 | } |
| 208 | TEST(test_resampler, resampler_44100_48000_best) { |
| 209 | checkResampler(44100, 48000, MultiChannelResampler::Quality::Best); |
| 210 | } |
| 211 | |
| 212 | // Look for glitches when downsampling. |
| 213 | TEST(test_resampler, resampler_48000_11025_best) { |
| 214 | checkResampler(48000, 11025, MultiChannelResampler::Quality::Best); |
| 215 | } |
| 216 | TEST(test_resampler, resampler_48000_44100_best) { |
| 217 | checkResampler(48000, 44100, MultiChannelResampler::Quality::Best); |
| 218 | } |
| 219 | TEST(test_resampler, resampler_44100_11025_best) { |
| 220 | checkResampler(44100, 11025, MultiChannelResampler::Quality::Best); |
| 221 | } |