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