1 /*
2 * Copyright 2020 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 #include <array>
18 #include <random>
19 #include <vector>
20
21 #include <gmock/gmock.h>
22 #include <gtest/gtest.h>
23
24 #include <audio_utils/BiquadFilter.h>
25
26 using ::testing::Pointwise;
27 using ::testing::FloatNear;
28 using namespace android::audio_utils;
29
30 /************************************************************************************
31 * Reference data, must not change.
32 * The reference output data is from running in matlab or octave y = filter(b, a, x), where
33 * b = [2.0 3.0 4.0]
34 * a = [1.0 0.2 0.3]
35 * x = [-0.1 -0.2 -0.3 -0.4 -0.5 0.1 0.2 0.3 0.4 0.5]
36 * filter(b, a, x)
37 *
38 * The output y = [-0.2 -0.66 -1.4080 -2.0204 -2.5735 -1.7792 -0.1721 2.1682 2.1180 2.3259].
39 * The reference data construct the input and output as 2D array so that it can be
40 * use to practice calling BiquadFilter::process multiple times.
41 ************************************************************************************/
42 constexpr size_t FRAME_COUNT = 5;
43 constexpr size_t PERIOD = 2;
44 constexpr float INPUT[PERIOD][FRAME_COUNT] = {
45 {-0.1f, -0.2f, -0.3f, -0.4f, -0.5f},
46 {0.1f, 0.2f, 0.3f, 0.4f, 0.5f}};
47 // COEFS in order of [ b0 b1 b2 a1 a2 ], normalized form where a0 = 1.
48 constexpr std::array<float, kBiquadNumCoefs> COEFS = {
49 2.0f, 3.0f, 4.0f, 0.2f, 0.3f };
50 constexpr float OUTPUT[PERIOD][FRAME_COUNT] = {
51 {-0.2f, -0.66f, -1.4080f, -2.0204f, -2.5735f},
52 {-1.7792f, -0.1721f, 2.1682f, 2.1180f, 2.3259f}};
53 constexpr float EPS = 1e-4f;
54
55 template <typename S, typename D>
populateBuffer(const S * singleChannelBuffer,size_t frameCount,size_t channelCount,size_t zeroChannels,D * buffer)56 static void populateBuffer(const S *singleChannelBuffer, size_t frameCount,
57 size_t channelCount, size_t zeroChannels, D *buffer) {
58 const size_t stride = channelCount + zeroChannels;
59 for (size_t i = 0; i < frameCount; ++i) {
60 size_t j = 0;
61 for (; j < channelCount; ++j) {
62 buffer[i * stride + j] = singleChannelBuffer[i];
63 }
64 for (; j < stride; ++j) {
65 buffer[i * stride + j] = D{};
66 }
67 }
68 }
69
70 template <typename D>
randomBuffer(D * buffer,size_t frameCount,size_t channelCount)71 static void randomBuffer(D *buffer, size_t frameCount, size_t channelCount) {
72 static std::minstd_rand gen(42);
73 constexpr float amplitude = 1.0f;
74 std::uniform_real_distribution<> dis(-amplitude, amplitude);
75 for (size_t i = 0; i < frameCount * channelCount; ++i) {
76 buffer[i] = dis(gen);
77 }
78 }
79
80 template <typename D>
randomFilter()81 static std::array<D, 5> randomFilter() {
82 static std::minstd_rand gen(42);
83 constexpr float amplitude = 0.9f;
84 std::uniform_real_distribution<> dis(-amplitude, amplitude);
85 const D p1 = (D)dis(gen);
86 const D p2 = (D)dis(gen);
87 return {(D)dis(gen), (D)dis(gen), (D)dis(gen), -(p1 + p2), p1 * p2};
88 }
89
90 template <typename D>
randomUnstableFilter()91 static std::array<D, 5> randomUnstableFilter() {
92 static std::minstd_rand gen(42);
93 constexpr float amplitude = 3.;
94 std::uniform_real_distribution<> dis(-amplitude, amplitude);
95 // symmetric in p1 and p2.
96 const D p1 = (D)dis(gen);
97 D p2;
98 while (true) {
99 p2 = (D)dis(gen);
100 if (fabs(p2) > 1.1) break;
101 }
102 return {(D)dis(gen), (D)dis(gen), (D)dis(gen), -(p1 + p2), p1 * p2};
103 }
104
105 // The BiquadFilterTest is parameterized on channel count.
106 class BiquadFilterTest : public ::testing::TestWithParam<size_t> {
107 protected:
108 template <typename ConstOptions, typename T>
testProcess(size_t zeroChannels=0)109 static void testProcess(size_t zeroChannels = 0) {
110 const size_t channelCount = static_cast<size_t>(GetParam());
111 const size_t stride = channelCount + zeroChannels;
112 const size_t sampleCount = FRAME_COUNT * stride;
113 T inputBuffer[PERIOD][sampleCount];
114 T outputBuffer[sampleCount];
115 T expectedOutputBuffer[PERIOD][sampleCount];
116 for (size_t i = 0; i < PERIOD; ++i) {
117 populateBuffer(INPUT[i], FRAME_COUNT, channelCount, zeroChannels, inputBuffer[i]);
118 populateBuffer(
119 OUTPUT[i], FRAME_COUNT, channelCount, zeroChannels, expectedOutputBuffer[i]);
120 }
121 BiquadFilter<T, true /* SAME_COEF_PER_CHANNEL */, ConstOptions>
122 filter(channelCount, COEFS);
123
124 for (size_t i = 0; i < PERIOD; ++i) {
125 filter.process(outputBuffer, inputBuffer[i], FRAME_COUNT, stride);
126 EXPECT_THAT(std::vector<float>(outputBuffer, outputBuffer + sampleCount),
127 Pointwise(FloatNear(EPS), std::vector<float>(
128 expectedOutputBuffer[i], expectedOutputBuffer[i] + sampleCount)));
129 }
130
131 // After clear, the previous delays should be cleared.
132 filter.clear();
133 filter.process(outputBuffer, inputBuffer[0], FRAME_COUNT, stride);
134 EXPECT_THAT(std::vector<float>(outputBuffer, outputBuffer + sampleCount),
135 Pointwise(FloatNear(EPS), std::vector<float>(
136 expectedOutputBuffer[0], expectedOutputBuffer[0] + sampleCount)));
137 }
138 };
139
140 struct StateSpaceOptions {
141 template <typename T, typename F>
142 using FilterType = BiquadStateSpace<T, F>;
143 };
144
145 struct StateSpaceChannelOptimizedOptions {
146 template <typename T, typename F>
147 using FilterType = BiquadStateSpace<T, F, true /* SEPARATE_CHANNEL_OPTIMIZATION */>;
148 };
149
150 struct Direct2TransposeOptions {
151 template <typename T, typename F>
152 using FilterType = BiquadDirect2Transpose<T, F>;
153 };
154
TEST_P(BiquadFilterTest,ConstructAndProcessSSFilterFloat)155 TEST_P(BiquadFilterTest, ConstructAndProcessSSFilterFloat) {
156 testProcess<StateSpaceOptions, float>();
157 }
158
TEST_P(BiquadFilterTest,ConstructAndProcessSSFilterDouble)159 TEST_P(BiquadFilterTest, ConstructAndProcessSSFilterDouble) {
160 testProcess<StateSpaceOptions, double>();
161 }
162
TEST_P(BiquadFilterTest,ConstructAndProcessSSFilterFloatZero3)163 TEST_P(BiquadFilterTest, ConstructAndProcessSSFilterFloatZero3) {
164 testProcess<StateSpaceOptions, float>(3 /* zeroChannels */);
165 }
166
TEST_P(BiquadFilterTest,ConstructAndProcessSSFilterDoubleZero5)167 TEST_P(BiquadFilterTest, ConstructAndProcessSSFilterDoubleZero5) {
168 testProcess<StateSpaceOptions, double>(5 /* zeroChannels */);
169 }
170
TEST_P(BiquadFilterTest,ConstructAndProcessSSChanelOptimizedFilterFloat)171 TEST_P(BiquadFilterTest, ConstructAndProcessSSChanelOptimizedFilterFloat) {
172 testProcess<StateSpaceChannelOptimizedOptions, float>();
173 }
174
TEST_P(BiquadFilterTest,ConstructAndProcessSSChannelOptimizedFilterDouble)175 TEST_P(BiquadFilterTest, ConstructAndProcessSSChannelOptimizedFilterDouble) {
176 testProcess<StateSpaceChannelOptimizedOptions, double>();
177 }
178
TEST_P(BiquadFilterTest,ConstructAndProcessDT2FilterFloat)179 TEST_P(BiquadFilterTest, ConstructAndProcessDT2FilterFloat) {
180 testProcess<Direct2TransposeOptions, float>();
181 }
182
TEST_P(BiquadFilterTest,ConstructAndProcessDT2FilterDouble)183 TEST_P(BiquadFilterTest, ConstructAndProcessDT2FilterDouble) {
184 testProcess<Direct2TransposeOptions, double>();
185 }
186
187 INSTANTIATE_TEST_CASE_P(
188 CstrAndRunBiquadFilter,
189 BiquadFilterTest,
190 ::testing::Values(1, 2, 3, 4, 5, 6, 7, 8,
191 9, 10, 11, 12, 13, 14, 15, 16,
192 17, 18, 19, 20, 21, 22, 23, 24)
193 );
194
195 // Test the experimental 1D mode.
TEST(BiquadBasicTest,OneDee)196 TEST(BiquadBasicTest, OneDee) {
197 using D = float;
198 constexpr size_t TEST_LENGTH = 1024;
199 constexpr size_t FILTERS = 3;
200 std::vector<D> reference(TEST_LENGTH);
201 randomBuffer(reference.data(), TEST_LENGTH, 1 /* channelCount */);
202
203 BiquadFilter<D, true> parallel(FILTERS, COEFS);
204 std::vector<std::unique_ptr<BiquadFilter<D>>> biquads(FILTERS);
205 for (auto& biquad : biquads) {
206 biquad.reset(new BiquadFilter<D>(1, COEFS));
207 }
208
209 auto test1 = reference;
210 parallel.process1D(test1.data(), TEST_LENGTH);
211
212 auto test2 = reference;
213 for (auto& biquad : biquads) {
214 biquad->process(test2.data(), test2.data(), TEST_LENGTH);
215 }
216 EXPECT_THAT(test1, Pointwise(FloatNear(EPS), test2));
217 }
218
219 // The BiquadBasicTest is parameterized on floating point type (float or double).
220 template <typename D>
221 class BiquadBasicTest : public ::testing::Test {
222 protected:
223
224 // Multichannel biquad test where each channel has different filter coefficients.
testDifferentFiltersPerChannel()225 static void testDifferentFiltersPerChannel() {
226 constexpr size_t FILTERS = 3;
227 constexpr size_t TEST_LENGTH = 1024;
228 std::vector<D> reference(TEST_LENGTH * FILTERS);
229 randomBuffer(reference.data(), TEST_LENGTH, FILTERS);
230
231 std::array<std::array<D, 5>, FILTERS> filters;
232 for (auto &filter : filters) {
233 filter = randomFilter<D>();
234 }
235
236 BiquadFilter<D, false> multichannel(FILTERS);
237 std::vector<std::unique_ptr<BiquadFilter<D>>> biquads(FILTERS);
238 for (size_t i = 0; i < filters.size(); ++i) {
239 ASSERT_TRUE(multichannel.setCoefficients(filters[i], i));
240 biquads[i].reset(new BiquadFilter<D>(1 /* channels */, filters[i]));
241 }
242
243 // Single multichannel Biquad with different filters per channel.
244 auto test1 = reference;
245 multichannel.process(test1.data(), test1.data(), TEST_LENGTH);
246
247 // Multiple different single channel Biquads applied to the test data, with a stride.
248 auto test2 = reference;
249 for (size_t i = 0; i < biquads.size(); ++i) {
250 biquads[i]->process(test2.data() + i, test2.data() + i, TEST_LENGTH, FILTERS);
251 }
252
253 // Must be equivalent.
254 EXPECT_THAT(test1, Pointwise(FloatNear(EPS), test2));
255 }
256
257 // Test zero fill with coefficients all zero.
testZeroFill()258 static void testZeroFill() {
259 constexpr size_t TEST_LENGTH = 1024;
260
261 // Randomize input and output.
262 std::vector<D> reference(TEST_LENGTH);
263 randomBuffer(reference.data(), TEST_LENGTH, 1);
264 std::vector<D> output(TEST_LENGTH);
265 randomBuffer(output.data(), TEST_LENGTH, 1);
266
267 // Single channel Biquad
268 BiquadFilter<D> bqf(1 /* channelCount */, {} /* coefs */);
269
270
271 bqf.process(output.data(), reference.data(), TEST_LENGTH);
272
273 // Result is zero.
274 const std::vector<D> zero(TEST_LENGTH);
275 ASSERT_EQ(zero, output);
276 ASSERT_NE(zero, reference);
277 }
278
279 // Stability check
testStability()280 static void testStability() {
281 BiquadFilter<D> bqf(1 /* channels */);
282 constexpr size_t TRIALS = 1000;
283 for (size_t i = 0; i < TRIALS; ++i) {
284 ASSERT_TRUE(bqf.setCoefficients(randomFilter<D>()));
285 ASSERT_FALSE(bqf.setCoefficients(randomUnstableFilter<D>()));
286 }
287 }
288
289 // Constructor, assignment equivalence check
testEquivalence()290 static void testEquivalence() {
291 for (size_t channelCount = 1; channelCount < 3; ++channelCount) {
292 BiquadFilter<D> bqf1(channelCount);
293 BiquadFilter<D> bqf2(channelCount);
294 ASSERT_TRUE(bqf1.setCoefficients(randomFilter<D>()));
295 ASSERT_FALSE(bqf2.setCoefficients(randomUnstableFilter<D>()));
296 ASSERT_NE(bqf1, bqf2); // one is stable one isn't, can't be the same.
297 constexpr size_t TRIALS = 10; // try a few different filters, just to be sure.
298 for (size_t i = 0; i < TRIALS; ++i) {
299 ASSERT_TRUE(bqf1.setCoefficients(randomFilter<D>()));
300 // Copy construction/assignment is equivalent.
301 const auto bqf3 = bqf1;
302 ASSERT_EQ(bqf1, bqf3);
303 const auto bqf4(bqf1);
304 ASSERT_EQ(bqf1, bqf4);
305
306 BiquadFilter<D> bqf5(channelCount);
307 bqf5.setCoefficients(bqf1.getCoefficients());
308 ASSERT_EQ(bqf1, bqf5);
309 }
310 }
311 }
312
313 // Test that 6 coefficient definition reduces to same 5 coefficient definition
testCoefReductionEquivalence()314 static void testCoefReductionEquivalence() {
315 std::array<D, 5> coef5 = randomFilter<D>();
316 // The 6 coefficient version has a0.
317 // This should be a power of 2 to be exact for IEEE binary float
318 for (size_t shift = 0; shift < 4; ++shift) {
319 const D a0 = 1 << shift;
320 std::array<D, 6> coef6 = { coef5[0] * a0, coef5[1] * a0, coef5[2] * a0,
321 a0, coef5[3] * a0, coef5[4] * a0
322 };
323 for (size_t channelCount = 1; channelCount < 2; ++channelCount) {
324 BiquadFilter<D> bqf1(channelCount, coef5);
325 BiquadFilter<D> bqf2(channelCount, coef6);
326 ASSERT_EQ(bqf1, bqf2);
327 }
328 }
329 }
330 };
331
332 using FloatTypes = ::testing::Types<float, double>;
333 TYPED_TEST_CASE(BiquadBasicTest, FloatTypes);
334
TYPED_TEST(BiquadBasicTest,DifferentFiltersPerChannel)335 TYPED_TEST(BiquadBasicTest, DifferentFiltersPerChannel) {
336 this->testDifferentFiltersPerChannel();
337 }
338
TYPED_TEST(BiquadBasicTest,ZeroFill)339 TYPED_TEST(BiquadBasicTest, ZeroFill) {
340 this->testZeroFill();
341 }
342
TYPED_TEST(BiquadBasicTest,Stability)343 TYPED_TEST(BiquadBasicTest, Stability) {
344 this->testStability();
345 }
346
TYPED_TEST(BiquadBasicTest,Equivalence)347 TYPED_TEST(BiquadBasicTest, Equivalence) {
348 this->testEquivalence();
349 }
350
TYPED_TEST(BiquadBasicTest,CoefReductionEquivalence)351 TYPED_TEST(BiquadBasicTest, CoefReductionEquivalence) {
352 this->testCoefReductionEquivalence();
353 }
354