• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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