BiQuadFilter: API improvements
Bug: webrtc:7494 Change-Id: If0270cddeb46fa53c0fbb385c85e48f28f9e1a5c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/236342 Reviewed-by: Hanna Silen <silen@webrtc.org> Commit-Queue: Alessio Bazzica <alessiob@webrtc.org> Cr-Commit-Position: refs/heads/main@{#35274}
This commit is contained in:
parent
32c4ecb3db
commit
2bf6d45f14
@ -10,26 +10,37 @@
|
|||||||
|
|
||||||
#include "modules/audio_processing/agc2/biquad_filter.h"
|
#include "modules/audio_processing/agc2/biquad_filter.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include "rtc_base/arraysize.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
// Transposed direct form I implementation of a bi-quad filter applied to an
|
BiQuadFilter::BiQuadFilter(const Config& config)
|
||||||
// input signal `x` to produce an output signal `y`.
|
: config_(config), state_({}) {}
|
||||||
|
|
||||||
|
BiQuadFilter::~BiQuadFilter() = default;
|
||||||
|
|
||||||
|
void BiQuadFilter::SetConfig(const Config& config) {
|
||||||
|
config_ = config;
|
||||||
|
state_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void BiQuadFilter::Reset() {
|
||||||
|
state_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
void BiQuadFilter::Process(rtc::ArrayView<const float> x,
|
void BiQuadFilter::Process(rtc::ArrayView<const float> x,
|
||||||
rtc::ArrayView<float> y) {
|
rtc::ArrayView<float> y) {
|
||||||
|
RTC_DCHECK_EQ(x.size(), y.size());
|
||||||
for (size_t k = 0; k < x.size(); ++k) {
|
for (size_t k = 0; k < x.size(); ++k) {
|
||||||
// Use temporary variable for x[k] to allow in-place function call
|
// Use a temporary variable for `x[k]` to allow in-place processing.
|
||||||
// (that x and y refer to the same array).
|
|
||||||
const float tmp = x[k];
|
const float tmp = x[k];
|
||||||
y[k] = coefficients_.b[0] * tmp + coefficients_.b[1] * biquad_state_.b[0] +
|
y[k] = config_.b[0] * tmp + config_.b[1] * state_.b[0] +
|
||||||
coefficients_.b[2] * biquad_state_.b[1] -
|
config_.b[2] * state_.b[1] - config_.a[0] * state_.a[0] -
|
||||||
coefficients_.a[0] * biquad_state_.a[0] -
|
config_.a[1] * state_.a[1];
|
||||||
coefficients_.a[1] * biquad_state_.a[1];
|
state_.b[1] = state_.b[0];
|
||||||
biquad_state_.b[1] = biquad_state_.b[0];
|
state_.b[0] = tmp;
|
||||||
biquad_state_.b[0] = tmp;
|
state_.a[1] = state_.a[0];
|
||||||
biquad_state_.a[1] = biquad_state_.a[0];
|
state_.a[0] = y[k];
|
||||||
biquad_state_.a[0] = y[k];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,54 +11,44 @@
|
|||||||
#ifndef MODULES_AUDIO_PROCESSING_AGC2_BIQUAD_FILTER_H_
|
#ifndef MODULES_AUDIO_PROCESSING_AGC2_BIQUAD_FILTER_H_
|
||||||
#define MODULES_AUDIO_PROCESSING_AGC2_BIQUAD_FILTER_H_
|
#define MODULES_AUDIO_PROCESSING_AGC2_BIQUAD_FILTER_H_
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "api/array_view.h"
|
#include "api/array_view.h"
|
||||||
#include "rtc_base/arraysize.h"
|
|
||||||
#include "rtc_base/constructor_magic.h"
|
|
||||||
|
|
||||||
namespace webrtc {
|
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 {
|
class BiQuadFilter {
|
||||||
public:
|
public:
|
||||||
// Normalized filter coefficients.
|
// Normalized filter coefficients.
|
||||||
// b_0 + b_1 • z^(-1) + b_2 • z^(-2)
|
// Computed as `[b, a] = scipy.signal.butter(N=2, Wn, btype)`.
|
||||||
// H(z) = ---------------------------------
|
struct Config {
|
||||||
// 1 + a_1 • z^(-1) + a_2 • z^(-2)
|
float b[3]; // b[0], b[1], b[2].
|
||||||
struct BiQuadCoefficients {
|
float a[2]; // a[1], a[2].
|
||||||
float b[3];
|
|
||||||
float a[2];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
BiQuadFilter() = default;
|
explicit BiQuadFilter(const Config& config);
|
||||||
|
BiQuadFilter(const BiQuadFilter&) = delete;
|
||||||
|
BiQuadFilter& operator=(const BiQuadFilter&) = delete;
|
||||||
|
~BiQuadFilter();
|
||||||
|
|
||||||
void Initialize(const BiQuadCoefficients& coefficients) {
|
// Sets the filter configuration and resets the internal state.
|
||||||
coefficients_ = coefficients;
|
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
|
// Filters `x` and writes the output in `y`, which must have the same length
|
||||||
// have the same length. In-place modification is allowed.
|
// of `x`. In-place processing is supported.
|
||||||
void Process(rtc::ArrayView<const float> x, rtc::ArrayView<float> y);
|
void Process(rtc::ArrayView<const float> x, rtc::ArrayView<float> y);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct BiQuadState {
|
Config config_;
|
||||||
BiQuadState() { Reset(); }
|
struct State {
|
||||||
|
|
||||||
void Reset() {
|
|
||||||
std::fill(b, b + arraysize(b), 0.f);
|
|
||||||
std::fill(a, a + arraysize(a), 0.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
float b[2];
|
float b[2];
|
||||||
float a[2];
|
float a[2];
|
||||||
};
|
} state_;
|
||||||
|
|
||||||
BiQuadState biquad_state_;
|
|
||||||
BiQuadCoefficients coefficients_;
|
|
||||||
|
|
||||||
RTC_DISALLOW_COPY_AND_ASSIGN(BiQuadFilter);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -19,11 +19,10 @@
|
|||||||
#include "rtc_base/gunit.h"
|
#include "rtc_base/gunit.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr size_t kFrameSize = 8;
|
constexpr int kFrameSize = 8;
|
||||||
constexpr size_t kNumFrames = 4;
|
constexpr int kNumFrames = 4;
|
||||||
using FloatArraySequence =
|
using FloatArraySequence =
|
||||||
std::array<std::array<float, kFrameSize>, kNumFrames>;
|
std::array<std::array<float, kFrameSize>, kNumFrames>;
|
||||||
|
|
||||||
@ -37,8 +36,8 @@ constexpr FloatArraySequence kBiQuadInputSeq = {
|
|||||||
{{22.645832f, -64.597153f, 55.462521f, -109.393188f, 10.117825f,
|
{{22.645832f, -64.597153f, 55.462521f, -109.393188f, 10.117825f,
|
||||||
-40.019642f, -98.612228f, -8.330326f}}}};
|
-40.019642f, -98.612228f, -8.330326f}}}};
|
||||||
|
|
||||||
// Generated via "B, A = scipy.signal.butter(2, 30/12000, btype='highpass')"
|
// Computed as `scipy.signal.butter(N=2, Wn=60/24000, btype='highpass')`.
|
||||||
const BiQuadFilter::BiQuadCoefficients kBiQuadConfig = {
|
constexpr BiQuadFilter::Config kBiQuadConfig{
|
||||||
{0.99446179f, -1.98892358f, 0.99446179f},
|
{0.99446179f, -1.98892358f, 0.99446179f},
|
||||||
{-1.98889291f, 0.98895425f}};
|
{-1.98889291f, 0.98895425f}};
|
||||||
|
|
||||||
@ -57,22 +56,23 @@ constexpr FloatArraySequence kBiQuadOutputSeq = {
|
|||||||
{{24.84286614f, -62.18094158f, 57.91488056f, -106.65685933f, 13.38760103f,
|
{{24.84286614f, -62.18094158f, 57.91488056f, -106.65685933f, 13.38760103f,
|
||||||
-36.60367134f, -94.44880104f, -3.59920354f}}}};
|
-36.60367134f, -94.44880104f, -3.59920354f}}}};
|
||||||
|
|
||||||
// Fail for every pair from two equally sized rtc::ArrayView<float> views such
|
// Fails for every pair from two equally sized rtc::ArrayView<float> views such
|
||||||
// that their relative error is above a given threshold. If the expected value
|
// 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<const float> expected,
|
void ExpectNearRelative(rtc::ArrayView<const float> expected,
|
||||||
rtc::ArrayView<const float> computed,
|
rtc::ArrayView<const float> computed,
|
||||||
const float tolerance) {
|
const float tolerance) {
|
||||||
// The relative error is undefined when the expected value is 0.
|
// The relative error is undefined when the expected value is 0.
|
||||||
// When that happens, check the absolute error instead. `safe_den` is used
|
// When that happens, check the absolute error instead. `safe_den` is used
|
||||||
// below to implement such logic.
|
// 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());
|
ASSERT_EQ(expected.size(), computed.size());
|
||||||
for (size_t i = 0; i < expected.size(); ++i) {
|
for (size_t i = 0; i < expected.size(); ++i) {
|
||||||
const float abs_diff = std::fabs(expected[i] - computed[i]);
|
const float abs_diff = std::fabs(expected[i] - computed[i]);
|
||||||
// No failure when the values are equal.
|
// No failure when the values are equal.
|
||||||
if (abs_diff == 0.f)
|
if (abs_diff == 0.0f) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
SCOPED_TRACE(i);
|
SCOPED_TRACE(i);
|
||||||
SCOPED_TRACE(expected[i]);
|
SCOPED_TRACE(expected[i]);
|
||||||
SCOPED_TRACE(computed[i]);
|
SCOPED_TRACE(computed[i]);
|
||||||
@ -80,32 +80,32 @@ void ExpectNearRelative(rtc::ArrayView<const float> expected,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
// Checks that filtering works when different containers are used both as input
|
||||||
|
// and as output.
|
||||||
TEST(BiQuadFilterTest, FilterNotInPlace) {
|
TEST(BiQuadFilterTest, FilterNotInPlace) {
|
||||||
BiQuadFilter filter;
|
BiQuadFilter filter(kBiQuadConfig);
|
||||||
filter.Initialize(kBiQuadConfig);
|
|
||||||
std::array<float, kFrameSize> samples;
|
std::array<float, kFrameSize> samples;
|
||||||
|
|
||||||
// TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed.
|
// TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed.
|
||||||
// FloatingPointExceptionObserver fpe_observer;
|
// FloatingPointExceptionObserver fpe_observer;
|
||||||
|
|
||||||
for (size_t i = 0; i < kNumFrames; ++i) {
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
SCOPED_TRACE(i);
|
SCOPED_TRACE(i);
|
||||||
filter.Process(kBiQuadInputSeq[i], samples);
|
filter.Process(kBiQuadInputSeq[i], samples);
|
||||||
ExpectNearRelative(kBiQuadOutputSeq[i], samples, 2e-4f);
|
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) {
|
TEST(BiQuadFilterTest, FilterInPlace) {
|
||||||
BiQuadFilter filter;
|
BiQuadFilter filter(kBiQuadConfig);
|
||||||
filter.Initialize(kBiQuadConfig);
|
|
||||||
std::array<float, kFrameSize> samples;
|
std::array<float, kFrameSize> samples;
|
||||||
|
|
||||||
// TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed.
|
// TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed.
|
||||||
// FloatingPointExceptionObserver fpe_observer;
|
// FloatingPointExceptionObserver fpe_observer;
|
||||||
|
|
||||||
for (size_t i = 0; i < kNumFrames; ++i) {
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
SCOPED_TRACE(i);
|
SCOPED_TRACE(i);
|
||||||
std::copy(kBiQuadInputSeq[i].begin(), kBiQuadInputSeq[i].end(),
|
std::copy(kBiQuadInputSeq[i].begin(), kBiQuadInputSeq[i].end(),
|
||||||
samples.begin());
|
samples.begin());
|
||||||
@ -114,23 +114,62 @@ TEST(BiQuadFilterTest, FilterInPlace) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(BiQuadFilterTest, Reset) {
|
// Checks that different configurations produce different outputs.
|
||||||
BiQuadFilter filter;
|
TEST(BiQuadFilterTest, SetConfigDifferentOutput) {
|
||||||
filter.Initialize(kBiQuadConfig);
|
BiQuadFilter filter(/*config=*/{{0.97803048f, -1.95606096f, 0.97803048f},
|
||||||
|
{-1.95557824f, 0.95654368f}});
|
||||||
|
|
||||||
std::array<float, kFrameSize> samples1;
|
std::array<float, kFrameSize> samples1;
|
||||||
for (size_t i = 0; i < kNumFrames; ++i) {
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
filter.Process(kBiQuadInputSeq[i], samples1);
|
filter.Process(kBiQuadInputSeq[i], samples1);
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.Reset();
|
filter.SetConfig(
|
||||||
|
{{0.09763107f, 0.19526215f, 0.09763107f}, {-0.94280904f, 0.33333333f}});
|
||||||
std::array<float, kFrameSize> samples2;
|
std::array<float, kFrameSize> 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<float, kFrameSize> samples1;
|
||||||
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
|
filter.Process(kBiQuadInputSeq[i], samples1);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.SetConfig(kBiQuadConfig);
|
||||||
|
std::array<float, kFrameSize> samples2;
|
||||||
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
filter.Process(kBiQuadInputSeq[i], samples2);
|
filter.Process(kBiQuadInputSeq[i], samples2);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_EQ(samples1, 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<float, kFrameSize> samples1;
|
||||||
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
|
filter.Process(kBiQuadInputSeq[i], samples1);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.Reset();
|
||||||
|
std::array<float, kFrameSize> samples2;
|
||||||
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
|
filter.Process(kBiQuadInputSeq[i], samples2);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(samples1, samples2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -19,8 +19,8 @@ namespace webrtc {
|
|||||||
namespace rnn_vad {
|
namespace rnn_vad {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Generated via "B, A = scipy.signal.butter(2, 30/12000, btype='highpass')"
|
// Computed as `scipy.signal.butter(N=2, Wn=60/24000, btype='highpass')`.
|
||||||
const BiQuadFilter::BiQuadCoefficients kHpfConfig24k = {
|
constexpr BiQuadFilter::Config kHpfConfig24k{
|
||||||
{0.99446179f, -1.98892358f, 0.99446179f},
|
{0.99446179f, -1.98892358f, 0.99446179f},
|
||||||
{-1.98889291f, 0.98895425f}};
|
{-1.98889291f, 0.98895425f}};
|
||||||
|
|
||||||
@ -28,6 +28,7 @@ const BiQuadFilter::BiQuadCoefficients kHpfConfig24k = {
|
|||||||
|
|
||||||
FeaturesExtractor::FeaturesExtractor(const AvailableCpuFeatures& cpu_features)
|
FeaturesExtractor::FeaturesExtractor(const AvailableCpuFeatures& cpu_features)
|
||||||
: use_high_pass_filter_(false),
|
: use_high_pass_filter_(false),
|
||||||
|
hpf_(kHpfConfig24k),
|
||||||
pitch_buf_24kHz_(),
|
pitch_buf_24kHz_(),
|
||||||
pitch_buf_24kHz_view_(pitch_buf_24kHz_.GetBufferView()),
|
pitch_buf_24kHz_view_(pitch_buf_24kHz_.GetBufferView()),
|
||||||
lp_residual_(kBufSize24kHz),
|
lp_residual_(kBufSize24kHz),
|
||||||
@ -35,7 +36,6 @@ FeaturesExtractor::FeaturesExtractor(const AvailableCpuFeatures& cpu_features)
|
|||||||
pitch_estimator_(cpu_features),
|
pitch_estimator_(cpu_features),
|
||||||
reference_frame_view_(pitch_buf_24kHz_.GetMostRecentValuesView()) {
|
reference_frame_view_(pitch_buf_24kHz_.GetMostRecentValuesView()) {
|
||||||
RTC_DCHECK_EQ(kBufSize24kHz, lp_residual_.size());
|
RTC_DCHECK_EQ(kBufSize24kHz, lp_residual_.size());
|
||||||
hpf_.Initialize(kHpfConfig24k);
|
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +44,10 @@ FeaturesExtractor::~FeaturesExtractor() = default;
|
|||||||
void FeaturesExtractor::Reset() {
|
void FeaturesExtractor::Reset() {
|
||||||
pitch_buf_24kHz_.Reset();
|
pitch_buf_24kHz_.Reset();
|
||||||
spectral_features_extractor_.Reset();
|
spectral_features_extractor_.Reset();
|
||||||
if (use_high_pass_filter_)
|
if (use_high_pass_filter_) {
|
||||||
hpf_.Reset();
|
hpf_.Reset();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool FeaturesExtractor::CheckSilenceComputeFeatures(
|
bool FeaturesExtractor::CheckSilenceComputeFeatures(
|
||||||
rtc::ArrayView<const float, kFrameSize10ms24kHz> samples,
|
rtc::ArrayView<const float, kFrameSize10ms24kHz> samples,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user