diff --git a/modules/audio_processing/agc2/biquad_filter.cc b/modules/audio_processing/agc2/biquad_filter.cc index ccb7807e6b..453125fde7 100644 --- a/modules/audio_processing/agc2/biquad_filter.cc +++ b/modules/audio_processing/agc2/biquad_filter.cc @@ -10,26 +10,37 @@ #include "modules/audio_processing/agc2/biquad_filter.h" -#include +#include "rtc_base/arraysize.h" namespace webrtc { -// Transposed direct form I implementation of a bi-quad filter applied to an -// input signal `x` to produce an output signal `y`. +BiQuadFilter::BiQuadFilter(const Config& config) + : config_(config), state_({}) {} + +BiQuadFilter::~BiQuadFilter() = default; + +void BiQuadFilter::SetConfig(const Config& config) { + config_ = config; + state_ = {}; +} + +void BiQuadFilter::Reset() { + state_ = {}; +} + void BiQuadFilter::Process(rtc::ArrayView x, rtc::ArrayView y) { + RTC_DCHECK_EQ(x.size(), y.size()); for (size_t k = 0; k < x.size(); ++k) { - // Use temporary variable for x[k] to allow in-place function call - // (that x and y refer to the same array). + // Use a temporary variable for `x[k]` to allow in-place processing. const float tmp = x[k]; - y[k] = coefficients_.b[0] * tmp + coefficients_.b[1] * biquad_state_.b[0] + - coefficients_.b[2] * biquad_state_.b[1] - - coefficients_.a[0] * biquad_state_.a[0] - - coefficients_.a[1] * biquad_state_.a[1]; - biquad_state_.b[1] = biquad_state_.b[0]; - biquad_state_.b[0] = tmp; - biquad_state_.a[1] = biquad_state_.a[0]; - biquad_state_.a[0] = y[k]; + y[k] = config_.b[0] * tmp + config_.b[1] * state_.b[0] + + config_.b[2] * state_.b[1] - config_.a[0] * state_.a[0] - + config_.a[1] * state_.a[1]; + state_.b[1] = state_.b[0]; + state_.b[0] = tmp; + state_.a[1] = state_.a[0]; + state_.a[0] = y[k]; } } diff --git a/modules/audio_processing/agc2/biquad_filter.h b/modules/audio_processing/agc2/biquad_filter.h index 7bf3301e4b..5273ff9386 100644 --- a/modules/audio_processing/agc2/biquad_filter.h +++ b/modules/audio_processing/agc2/biquad_filter.h @@ -11,54 +11,44 @@ #ifndef MODULES_AUDIO_PROCESSING_AGC2_BIQUAD_FILTER_H_ #define MODULES_AUDIO_PROCESSING_AGC2_BIQUAD_FILTER_H_ -#include - #include "api/array_view.h" -#include "rtc_base/arraysize.h" -#include "rtc_base/constructor_magic.h" namespace webrtc { +// Transposed direct form I implementation of a bi-quad filter. +// b[0] + b[1] • z^(-1) + b[2] • z^(-2) +// H(z) = ------------------------------------ +// 1 + a[1] • z^(-1) + a[2] • z^(-2) class BiQuadFilter { public: // Normalized filter coefficients. - // b_0 + b_1 • z^(-1) + b_2 • z^(-2) - // H(z) = --------------------------------- - // 1 + a_1 • z^(-1) + a_2 • z^(-2) - struct BiQuadCoefficients { - float b[3]; - float a[2]; + // Computed as `[b, a] = scipy.signal.butter(N=2, Wn, btype)`. + struct Config { + float b[3]; // b[0], b[1], b[2]. + float a[2]; // a[1], a[2]. }; - BiQuadFilter() = default; + explicit BiQuadFilter(const Config& config); + BiQuadFilter(const BiQuadFilter&) = delete; + BiQuadFilter& operator=(const BiQuadFilter&) = delete; + ~BiQuadFilter(); - void Initialize(const BiQuadCoefficients& coefficients) { - coefficients_ = coefficients; - } + // Sets the filter configuration and resets the internal state. + void SetConfig(const Config& config); - void Reset() { biquad_state_.Reset(); } + // Zeroes the filter state. + void Reset(); - // Produces a filtered output y of the input x. Both x and y need to - // have the same length. In-place modification is allowed. + // Filters `x` and writes the output in `y`, which must have the same length + // of `x`. In-place processing is supported. void Process(rtc::ArrayView x, rtc::ArrayView y); private: - struct BiQuadState { - BiQuadState() { Reset(); } - - void Reset() { - std::fill(b, b + arraysize(b), 0.f); - std::fill(a, a + arraysize(a), 0.f); - } - + Config config_; + struct State { float b[2]; float a[2]; - }; - - BiQuadState biquad_state_; - BiQuadCoefficients coefficients_; - - RTC_DISALLOW_COPY_AND_ASSIGN(BiQuadFilter); + } state_; }; } // namespace webrtc diff --git a/modules/audio_processing/agc2/biquad_filter_unittest.cc b/modules/audio_processing/agc2/biquad_filter_unittest.cc index 55ca1a5acc..a53036b08e 100644 --- a/modules/audio_processing/agc2/biquad_filter_unittest.cc +++ b/modules/audio_processing/agc2/biquad_filter_unittest.cc @@ -19,11 +19,10 @@ #include "rtc_base/gunit.h" namespace webrtc { -namespace test { namespace { -constexpr size_t kFrameSize = 8; -constexpr size_t kNumFrames = 4; +constexpr int kFrameSize = 8; +constexpr int kNumFrames = 4; using FloatArraySequence = std::array, kNumFrames>; @@ -37,8 +36,8 @@ constexpr FloatArraySequence kBiQuadInputSeq = { {{22.645832f, -64.597153f, 55.462521f, -109.393188f, 10.117825f, -40.019642f, -98.612228f, -8.330326f}}}}; -// Generated via "B, A = scipy.signal.butter(2, 30/12000, btype='highpass')" -const BiQuadFilter::BiQuadCoefficients kBiQuadConfig = { +// Computed as `scipy.signal.butter(N=2, Wn=60/24000, btype='highpass')`. +constexpr BiQuadFilter::Config kBiQuadConfig{ {0.99446179f, -1.98892358f, 0.99446179f}, {-1.98889291f, 0.98895425f}}; @@ -57,22 +56,23 @@ constexpr FloatArraySequence kBiQuadOutputSeq = { {{24.84286614f, -62.18094158f, 57.91488056f, -106.65685933f, 13.38760103f, -36.60367134f, -94.44880104f, -3.59920354f}}}}; -// Fail for every pair from two equally sized rtc::ArrayView views such +// Fails for every pair from two equally sized rtc::ArrayView views such // that their relative error is above a given threshold. If the expected value -// of a pair is 0, the tolerance is used to check the absolute error. +// of a pair is 0, `tolerance` is used to check the absolute error. void ExpectNearRelative(rtc::ArrayView expected, rtc::ArrayView computed, const float tolerance) { // The relative error is undefined when the expected value is 0. // When that happens, check the absolute error instead. `safe_den` is used // below to implement such logic. - auto safe_den = [](float x) { return (x == 0.f) ? 1.f : std::fabs(x); }; + auto safe_den = [](float x) { return (x == 0.0f) ? 1.0f : std::fabs(x); }; ASSERT_EQ(expected.size(), computed.size()); for (size_t i = 0; i < expected.size(); ++i) { const float abs_diff = std::fabs(expected[i] - computed[i]); // No failure when the values are equal. - if (abs_diff == 0.f) + if (abs_diff == 0.0f) { continue; + } SCOPED_TRACE(i); SCOPED_TRACE(expected[i]); SCOPED_TRACE(computed[i]); @@ -80,32 +80,32 @@ void ExpectNearRelative(rtc::ArrayView expected, } } -} // namespace - +// Checks that filtering works when different containers are used both as input +// and as output. TEST(BiQuadFilterTest, FilterNotInPlace) { - BiQuadFilter filter; - filter.Initialize(kBiQuadConfig); + BiQuadFilter filter(kBiQuadConfig); std::array samples; // TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed. // FloatingPointExceptionObserver fpe_observer; - for (size_t i = 0; i < kNumFrames; ++i) { + for (int i = 0; i < kNumFrames; ++i) { SCOPED_TRACE(i); filter.Process(kBiQuadInputSeq[i], samples); ExpectNearRelative(kBiQuadOutputSeq[i], samples, 2e-4f); } } +// Checks that filtering works when the same container is used both as input and +// as output. TEST(BiQuadFilterTest, FilterInPlace) { - BiQuadFilter filter; - filter.Initialize(kBiQuadConfig); + BiQuadFilter filter(kBiQuadConfig); std::array samples; // TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed. // FloatingPointExceptionObserver fpe_observer; - for (size_t i = 0; i < kNumFrames; ++i) { + for (int i = 0; i < kNumFrames; ++i) { SCOPED_TRACE(i); std::copy(kBiQuadInputSeq[i].begin(), kBiQuadInputSeq[i].end(), samples.begin()); @@ -114,23 +114,62 @@ TEST(BiQuadFilterTest, FilterInPlace) { } } -TEST(BiQuadFilterTest, Reset) { - BiQuadFilter filter; - filter.Initialize(kBiQuadConfig); +// Checks that different configurations produce different outputs. +TEST(BiQuadFilterTest, SetConfigDifferentOutput) { + BiQuadFilter filter(/*config=*/{{0.97803048f, -1.95606096f, 0.97803048f}, + {-1.95557824f, 0.95654368f}}); std::array samples1; - for (size_t i = 0; i < kNumFrames; ++i) { + for (int i = 0; i < kNumFrames; ++i) { filter.Process(kBiQuadInputSeq[i], samples1); } - filter.Reset(); + filter.SetConfig( + {{0.09763107f, 0.19526215f, 0.09763107f}, {-0.94280904f, 0.33333333f}}); std::array samples2; - for (size_t i = 0; i < kNumFrames; ++i) { + for (int i = 0; i < kNumFrames; ++i) { + filter.Process(kBiQuadInputSeq[i], samples2); + } + + EXPECT_NE(samples1, samples2); +} + +// Checks that when `SetConfig()` is called but the filter coefficients are the +// same the filter state is reset. +TEST(BiQuadFilterTest, SetConfigResetsState) { + BiQuadFilter filter(kBiQuadConfig); + + std::array samples1; + for (int i = 0; i < kNumFrames; ++i) { + filter.Process(kBiQuadInputSeq[i], samples1); + } + + filter.SetConfig(kBiQuadConfig); + std::array samples2; + for (int i = 0; i < kNumFrames; ++i) { filter.Process(kBiQuadInputSeq[i], samples2); } EXPECT_EQ(samples1, samples2); } -} // namespace test +// Checks that when `Reset()` is called the filter state is reset. +TEST(BiQuadFilterTest, Reset) { + BiQuadFilter filter(kBiQuadConfig); + + std::array samples1; + for (int i = 0; i < kNumFrames; ++i) { + filter.Process(kBiQuadInputSeq[i], samples1); + } + + filter.Reset(); + std::array samples2; + for (int i = 0; i < kNumFrames; ++i) { + filter.Process(kBiQuadInputSeq[i], samples2); + } + + EXPECT_EQ(samples1, samples2); +} + +} // namespace } // namespace webrtc diff --git a/modules/audio_processing/agc2/rnn_vad/features_extraction.cc b/modules/audio_processing/agc2/rnn_vad/features_extraction.cc index 5c276c87a9..502023428d 100644 --- a/modules/audio_processing/agc2/rnn_vad/features_extraction.cc +++ b/modules/audio_processing/agc2/rnn_vad/features_extraction.cc @@ -19,8 +19,8 @@ namespace webrtc { namespace rnn_vad { namespace { -// Generated via "B, A = scipy.signal.butter(2, 30/12000, btype='highpass')" -const BiQuadFilter::BiQuadCoefficients kHpfConfig24k = { +// Computed as `scipy.signal.butter(N=2, Wn=60/24000, btype='highpass')`. +constexpr BiQuadFilter::Config kHpfConfig24k{ {0.99446179f, -1.98892358f, 0.99446179f}, {-1.98889291f, 0.98895425f}}; @@ -28,6 +28,7 @@ const BiQuadFilter::BiQuadCoefficients kHpfConfig24k = { FeaturesExtractor::FeaturesExtractor(const AvailableCpuFeatures& cpu_features) : use_high_pass_filter_(false), + hpf_(kHpfConfig24k), pitch_buf_24kHz_(), pitch_buf_24kHz_view_(pitch_buf_24kHz_.GetBufferView()), lp_residual_(kBufSize24kHz), @@ -35,7 +36,6 @@ FeaturesExtractor::FeaturesExtractor(const AvailableCpuFeatures& cpu_features) pitch_estimator_(cpu_features), reference_frame_view_(pitch_buf_24kHz_.GetMostRecentValuesView()) { RTC_DCHECK_EQ(kBufSize24kHz, lp_residual_.size()); - hpf_.Initialize(kHpfConfig24k); Reset(); } @@ -44,8 +44,9 @@ FeaturesExtractor::~FeaturesExtractor() = default; void FeaturesExtractor::Reset() { pitch_buf_24kHz_.Reset(); spectral_features_extractor_.Reset(); - if (use_high_pass_filter_) + if (use_high_pass_filter_) { hpf_.Reset(); + } } bool FeaturesExtractor::CheckSilenceComputeFeatures(