1 /*
2 * Copyright (C) 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 * Unless required by applicable law or agreed to in writing, software
10 *
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 #ifndef ANDROID_AUDIO_UTILS_BIQUAD_FILTER_H
18 #define ANDROID_AUDIO_UTILS_BIQUAD_FILTER_H
19
20 #include <array>
21 #include <cmath>
22 #include <functional>
23 #include <utility>
24 #include <vector>
25
26 #include <assert.h>
27
28 namespace android::audio_utils {
29
30 static constexpr size_t kBiquadNumCoefs = 5;
31 static constexpr size_t kBiquadNumDelays = 2;
32
33 namespace details {
34 // Helper methods for constructing a constexpr array of function pointers.
35 // As function pointers are efficient and have no constructor/destructor
36 // this is preferred over std::function.
37
38 template <template <size_t, typename ...> typename F, size_t... Is>
make_functional_array_from_index_sequence(std::index_sequence<Is...>)39 static inline constexpr auto make_functional_array_from_index_sequence(std::index_sequence<Is...>) {
40 using first_t = decltype(&F<0>::func); // type from function
41 using result_t = std::array<first_t, sizeof...(Is)>; // type of array
42 return result_t{{F<Is>::func...}}; // initialize with functions.
43 }
44
45 template <template <size_t, typename ...> typename F, size_t M>
make_functional_array()46 static inline constexpr auto make_functional_array() {
47 return make_functional_array_from_index_sequence<F>(std::make_index_sequence<M>());
48 }
49
50 // For biquad_filter_fast, we template based on whether coef[i] is nonzero - this should be
51 // determined in a constexpr fashion for optimization.
52 template <size_t OCCUPANCY>
biquad_filter_fast(const float * coefs,const float * in,size_t channelCount,size_t frames,float * delays,float * out)53 void biquad_filter_fast(const float *coefs, const float *in, size_t channelCount,
54 size_t frames, float *delays, float *out) {
55 const float b0 = coefs[0];
56 const float b1 = coefs[1];
57 const float b2 = coefs[2];
58 const float negativeA1 = -coefs[3];
59 const float negativeA2 = -coefs[4];
60 #if defined(__i386__) || defined(__x86_x64__)
61 float delta = std::numeric_limits<float>::min() * (1 << 24);
62 #endif
63 for (size_t i = 0; i < channelCount; ++i) {
64 float s1n1 = delays[i * kBiquadNumDelays];
65 float s2n1 = delays[i * kBiquadNumDelays + 1];
66 const float *input = &in[i];
67 float *output = &out[i];
68 for (size_t j = frames; j > 0; --j) {
69 // Adding a delta to avoid high latency with subnormal data. The high latency is not
70 // significant with ARM platform, but on x86 platform. The delta will not affect the
71 // precision of the result.
72 #if defined(__i386__) || defined(__x86_x64__)
73 const float xn = *input + delta;
74 #else
75 const float xn = *input;
76 #endif
77 float yn = (OCCUPANCY >> 0 & 1) * b0 * xn + s1n1;
78 s1n1 = (OCCUPANCY >> 1 & 1) * b1 * xn + (OCCUPANCY >> 3 & 1) * negativeA1 * yn + s2n1;
79 s2n1 = (OCCUPANCY >> 2 & 1) * b2 * xn + (OCCUPANCY >> 4 & 1) * negativeA2 * yn;
80
81 input += channelCount;
82
83 *output = yn;
84 output += channelCount;
85
86 #if defined(__i386__) || defined(__x86_x64__)
87 delta = -delta;
88 #endif
89 }
90 delays[i * kBiquadNumDelays] = s1n1;
91 delays[i * kBiquadNumDelays + 1] = s2n1;
92 }
93 }
94
95 } // namespace details
96
97 // When using BiquadFilter, `-ffast-math` is needed to get non-zero optimization.
98 // TODO(b/159373530): Use compound statement scoped pragmas so that the library
99 // users not need to add `-ffast-math`.
100 class BiquadFilter {
101 public:
102 template <typename T = std::array<float,kBiquadNumCoefs>>
103 explicit BiquadFilter(const size_t channelCount, const T& coefs = {})
mChannelCount(channelCount)104 : mChannelCount(channelCount), mDelays(channelCount * kBiquadNumDelays) {
105 setCoefficients(coefs);
106 }
107
108 /**
109 * \brief Sets filter coefficients
110 *
111 * \param coefs pointer to the filter coefficients array
112 * \return true if the BiquadFilter is stable. Otherwise, return false.
113 *
114 * The coefficients are interpreted in the following manner:
115 * coefs[0] is b0, coefs[1] is b1,
116 * coefs[2] is b2, coefs[3] is a1,
117 * coefs[4] is a2.
118 *
119 * coefs are used in Biquad filter equation as follows:
120 * y[n] = b0 * x[n] + s1[n - 1]
121 * s1[n] = s2[n - 1] + b1 * x[n] - a1 * y[n]
122 * s2[n] = b2 * x[n] - a2 * y[n]
123 */
124 template <typename T = std::array<float,kBiquadNumCoefs>>
setCoefficients(const T & coefs)125 bool setCoefficients(const T& coefs) {
126 assert(coefs.size() == kBiquadNumCoefs);
127 bool stable = (fabs(coefs[4]) < 1.0f && fabs(coefs[3]) < 1.0f + coefs[4]);
128 // Determine which coefficients are nonzero.
129 size_t category = 0;
130 for (size_t i = 0; i < kBiquadNumCoefs; ++i) {
131 category |= (coefs[i] != 0) << i;
132 }
133
134 // Select the proper filtering function from our array.
135 mFunc = mArray[category];
136 std::copy(coefs.begin(), coefs.end(), mCoefs.begin());
137 return stable;
138 }
139
140 /**
141 * \brief Filters the input data
142 *
143 * Carries out filtering on the input data.
144 *
145 * \param out pointer to the output data
146 * \param in pointer to the input data
147 * \param frames number of audio frames to be processed
148 */
process(float * out,const float * in,const size_t frames)149 void process(float* out, const float *in, const size_t frames) {
150 mFunc(mCoefs.data(), in, mChannelCount, frames, mDelays.data(), out);
151 }
152
153 /**
154 * \brief Clears the input and output data
155 *
156 * This function clears the input and output delay elements
157 * stored in delay buffer.
158 */
clear()159 void clear() {
160 std::fill(mDelays.begin(), mDelays.end(), 0.f);
161 }
162
163 /**
164 * \brief Sets mDelays with the values passed.
165 *
166 * \param delays reference to vector containing delays.
167 */
setDelays(std::vector<float> & delays)168 void setDelays(std::vector<float>& delays) {
169 assert(delays.size() == mDelays.size());
170 mDelays = std::move(delays);
171 }
172
173 /**
174 * \brief Gets delays vector with delay values at mDelays vector.
175 *
176 * \return a const vector reference of delays.
177 */
getDelays()178 const std::vector<float>& getDelays() {
179 return mDelays;
180 }
181
182 private:
183 const size_t mChannelCount;
184
185 /*
186 * \var float mCoefs
187 * \brief Stores the filter coefficients
188 */
189 std::array<float, kBiquadNumCoefs> mCoefs;
190
191 /**
192 * \var float mDelays
193 * \brief Stores the input and outpt delays.
194 *
195 * The delays are stored in the following manner,
196 * mDelays[2 * i + 0] is s1(n-1) of i-th channel
197 * mDelays[2 * i + 1] is s2(n-1) of i-th channel
198 * index i runs from 0 to (mChannelCount - 1).
199 */
200 std::vector<float> mDelays;
201
202 // Declare a float filter function.
203 using float_filter_func = decltype(details::biquad_filter_fast<0>);
204
205 float_filter_func *mFunc;
206
207 // Create a functional wrapper to feed "biquad_filter_fast" to
208 // make_functional_array() to populate the array.
209 template <size_t OCCUPANCY>
210 struct FuncWrap {
funcFuncWrap211 static void func(const float *coef, const float *in, const size_t channelCount,
212 const size_t frames, float *delays, float* out) {
213 details::biquad_filter_fast<OCCUPANCY>(coef, in, channelCount, frames, delays, out);
214 }
215 };
216
217 // Create the std::array of functions.
218 // static inline constexpr std::array<float_filter_func*, M> mArray = {
219 // biquad_filter_fast<0>,
220 // biquad_filter_fast<1>,
221 // biquad_filter_fast<2>,
222 // ...
223 // biquad_filter_fast<(1 << kBiquadNumCoefs) - 1>,
224 // };
225 // The actual processing function will be picked based on the coefficients.
226 static inline constexpr auto mArray =
227 details::make_functional_array<FuncWrap, 1 << kBiquadNumCoefs>();
228 };
229
230 } // namespace android::audio_utils
231
232 #endif // !ANDROID_AUDIO_UTILS_BIQUAD_FILTER_H
233