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