Switch away from hz to samples per channel for FrameCombiner et al

This simplifies the following steps:
* FrameCombiner infers the sample rate from channel size
* Sends the inferred sample rate to FixedDigitalLevelEstimator
  and Limiter.
* Those classes then convert the sample rate to channel size.
  Along the way perform checks that the derived channel size value
  is a legal value (which has already been done by FrameCombiner).

To:
* FrameCombiner sends channel size to FixedDigitalLevelEstimator and
  Limiter.

Bug: chromium:335805780
Change-Id: I6d2953ba5ee99771f3ff5bf4f4a049a8a29b5577
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/352581
Reviewed-by: Per Åhgren <peah@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42480}
This commit is contained in:
Tommi 2024-06-13 15:28:34 +02:00 committed by WebRTC LUCI CQ
parent da485a1b46
commit 093824c4d2
10 changed files with 95 additions and 65 deletions

View File

@ -106,10 +106,7 @@ void MixToFloatFrame(rtc::ArrayView<const AudioFrame* const> mix_list,
}
void RunLimiter(AudioFrameView<float> mixing_buffer_view, Limiter* limiter) {
const size_t sample_rate = mixing_buffer_view.samples_per_channel() * 1000 /
AudioMixerImpl::kFrameDurationInMs;
// TODO(alessiob): Avoid calling SetSampleRate every time.
limiter->SetSampleRate(sample_rate);
limiter->SetSamplesPerChannel(mixing_buffer_view.samples_per_channel());
limiter->Process(mixing_buffer_view);
}
@ -134,7 +131,7 @@ constexpr size_t FrameCombiner::kMaximumChannelSize;
FrameCombiner::FrameCombiner(bool use_limiter)
: data_dumper_(new ApmDataDumper(0)),
limiter_(static_cast<size_t>(48000), data_dumper_.get(), "AudioMixer"),
limiter_(data_dumper_.get(), kMaximumChannelSize, "AudioMixer"),
use_limiter_(use_limiter) {
static_assert(kMaximumChannelSize * kMaximumNumberOfChannels <=
AudioFrame::kMaxDataSizeSamples,

View File

@ -108,6 +108,7 @@ rtc_library("gain_controller2") {
":apm_logging",
":audio_buffer",
":audio_frame_view",
"../../api/audio:audio_frame_api",
"../../api/audio:audio_processing",
"../../common_audio",
"../../rtc_base:checks",

View File

@ -148,6 +148,7 @@ rtc_library("fixed_digital") {
"..:apm_logging",
"..:audio_frame_view",
"../../../api:array_view",
"../../../api/audio:audio_frame_api",
"../../../common_audio",
"../../../rtc_base:checks",
"../../../rtc_base:gtest_prod",
@ -388,6 +389,7 @@ rtc_library("fixed_digital_unittests") {
"..:apm_logging",
"..:audio_frame_view",
"../../../api:array_view",
"../../../api/audio:audio_frame_api",
"../../../common_audio",
"../../../rtc_base:checks",
"../../../rtc_base:gunit_helpers",

View File

@ -14,6 +14,7 @@
#include <cmath>
#include "api/array_view.h"
#include "api/audio/audio_frame.h"
#include "modules/audio_processing/logging/apm_data_dumper.h"
#include "rtc_base/checks.h"
@ -34,14 +35,17 @@ constexpr float kDecayFilterConstant = 0.9971259f;
} // namespace
FixedDigitalLevelEstimator::FixedDigitalLevelEstimator(
int sample_rate_hz,
size_t samples_per_channel,
ApmDataDumper* apm_data_dumper)
: apm_data_dumper_(apm_data_dumper),
filter_state_level_(kInitialFilterStateLevel) {
SetSampleRate(sample_rate_hz);
SetSamplesPerChannel(samples_per_channel);
CheckParameterCombination();
RTC_DCHECK(apm_data_dumper_);
apm_data_dumper_->DumpRaw("agc2_level_estimator_samplerate", sample_rate_hz);
// Convert `samples_per_channel` to sample rate for
// `agc2_level_estimator_samplerate`.
apm_data_dumper_->DumpRaw("agc2_level_estimator_samplerate",
samples_per_channel * kDefaultAudioBuffersPerSec);
}
void FixedDigitalLevelEstimator::CheckParameterCombination() {
@ -106,9 +110,9 @@ std::array<float, kSubFramesInFrame> FixedDigitalLevelEstimator::ComputeLevel(
return envelope;
}
void FixedDigitalLevelEstimator::SetSampleRate(int sample_rate_hz) {
samples_in_frame_ =
rtc::CheckedDivExact(sample_rate_hz * kFrameDurationMs, 1000);
void FixedDigitalLevelEstimator::SetSamplesPerChannel(
size_t samples_per_channel) {
samples_in_frame_ = static_cast<int>(samples_per_channel);
samples_in_sub_frame_ =
rtc::CheckedDivExact(samples_in_frame_, kSubFramesInFrame);
CheckParameterCombination();

View File

@ -25,12 +25,16 @@ class ApmDataDumper;
// filtering.
class FixedDigitalLevelEstimator {
public:
// Sample rates are allowed if the number of samples in a frame
// (sample_rate_hz * kFrameDurationMs / 1000) is divisible by
// `samples_per_channel` is expected to be derived from this formula:
// sample_rate_hz * kFrameDurationMs / 1000
// or
// sample_rate_hz / 100
// I.e. the number of samples for 10ms of the given sample rate. The
// expectation is that samples per channel is divisible by
// kSubFramesInSample. For kFrameDurationMs=10 and
// kSubFramesInSample=20, this means that sample_rate_hz has to be
// divisible by 2000.
FixedDigitalLevelEstimator(int sample_rate_hz,
// kSubFramesInSample=20, this means that the original sample rate has to be
// divisible by 2000 and therefore `samples_per_channel` by 20.
FixedDigitalLevelEstimator(size_t samples_per_channel,
ApmDataDumper* apm_data_dumper);
FixedDigitalLevelEstimator(const FixedDigitalLevelEstimator&) = delete;
@ -46,7 +50,7 @@ class FixedDigitalLevelEstimator {
// Rate may be changed at any time (but not concurrently) from the
// value passed to the constructor. The class is not thread safe.
void SetSampleRate(int sample_rate_hz);
void SetSamplesPerChannel(size_t samples_per_channel);
// Resets the level estimator internal state.
void Reset();

View File

@ -12,6 +12,7 @@
#include <limits>
#include "api/audio/audio_frame.h"
#include "common_audio/include/audio_util.h"
#include "modules/audio_processing/agc2/agc2_common.h"
#include "modules/audio_processing/agc2/agc2_testing_common.h"
@ -26,17 +27,17 @@ constexpr float kInputLevel = 10000.f;
// Run audio at specified settings through the level estimator, and
// verify that the output level falls within the bounds.
void TestLevelEstimator(int sample_rate_hz,
void TestLevelEstimator(size_t samples_per_channel,
int num_channels,
float input_level_linear_scale,
float expected_min,
float expected_max) {
ApmDataDumper apm_data_dumper(0);
FixedDigitalLevelEstimator level_estimator(sample_rate_hz, &apm_data_dumper);
FixedDigitalLevelEstimator level_estimator(samples_per_channel,
&apm_data_dumper);
const VectorFloatFrame vectors_with_float_frame(
num_channels, rtc::CheckedDivExact(sample_rate_hz, 100),
input_level_linear_scale);
num_channels, samples_per_channel, input_level_linear_scale);
for (int i = 0; i < 500; ++i) {
const auto level = level_estimator.ComputeLevel(
@ -56,7 +57,7 @@ void TestLevelEstimator(int sample_rate_hz,
// Returns time it takes for the level estimator to decrease its level
// estimate by 'level_reduction_db'.
float TimeMsToDecreaseLevel(int sample_rate_hz,
float TimeMsToDecreaseLevel(size_t samples_per_channel,
int num_channels,
float input_level_db,
float level_reduction_db) {
@ -64,10 +65,11 @@ float TimeMsToDecreaseLevel(int sample_rate_hz,
RTC_DCHECK_GT(level_reduction_db, 0);
const VectorFloatFrame vectors_with_float_frame(
num_channels, rtc::CheckedDivExact(sample_rate_hz, 100), input_level);
num_channels, samples_per_channel, input_level);
ApmDataDumper apm_data_dumper(0);
FixedDigitalLevelEstimator level_estimator(sample_rate_hz, &apm_data_dumper);
FixedDigitalLevelEstimator level_estimator(samples_per_channel,
&apm_data_dumper);
// Give the LevelEstimator plenty of time to ramp up and stabilize
float last_level = 0.f;
@ -78,8 +80,8 @@ float TimeMsToDecreaseLevel(int sample_rate_hz,
}
// Set input to 0.
VectorFloatFrame vectors_with_zero_float_frame(
num_channels, rtc::CheckedDivExact(sample_rate_hz, 100), 0);
VectorFloatFrame vectors_with_zero_float_frame(num_channels,
samples_per_channel, 0);
const float reduced_level_linear =
DbfsToFloatS16(input_level_db - level_reduction_db);
@ -102,21 +104,22 @@ float TimeMsToDecreaseLevel(int sample_rate_hz,
} // namespace
TEST(GainController2FixedDigitalLevelEstimator, EstimatorShouldNotCrash) {
TestLevelEstimator(8000, 1, 0, std::numeric_limits<float>::lowest(),
TestLevelEstimator(SampleRateToDefaultChannelSize(8000u), 1, 0,
std::numeric_limits<float>::lowest(),
std::numeric_limits<float>::max());
}
TEST(GainController2FixedDigitalLevelEstimator,
EstimatorShouldEstimateConstantLevel) {
TestLevelEstimator(10000, 1, kInputLevel, kInputLevel * 0.99,
kInputLevel * 1.01);
TestLevelEstimator(SampleRateToDefaultChannelSize(10000u), 1, kInputLevel,
kInputLevel * 0.99, kInputLevel * 1.01);
}
TEST(GainController2FixedDigitalLevelEstimator,
EstimatorShouldEstimateConstantLevelForManyChannels) {
constexpr size_t num_channels = 10;
TestLevelEstimator(20000, num_channels, kInputLevel, kInputLevel * 0.99,
kInputLevel * 1.01);
TestLevelEstimator(SampleRateToDefaultChannelSize(20000u), num_channels,
kInputLevel, kInputLevel * 0.99, kInputLevel * 1.01);
}
TEST(GainController2FixedDigitalLevelEstimator, TimeToDecreaseForLowLevel) {
@ -125,7 +128,8 @@ TEST(GainController2FixedDigitalLevelEstimator, TimeToDecreaseForLowLevel) {
constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
const float time_to_decrease =
TimeMsToDecreaseLevel(22000, 1, kInitialLowLevel, kLevelReductionDb);
TimeMsToDecreaseLevel(SampleRateToDefaultChannelSize(22000u), 1,
kInitialLowLevel, kLevelReductionDb);
EXPECT_LE(kExpectedTime * 0.9, time_to_decrease);
EXPECT_LE(time_to_decrease, kExpectedTime * 1.1);
@ -136,8 +140,8 @@ TEST(GainController2FixedDigitalLevelEstimator,
constexpr float kLevelReductionDb = 25;
constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
const float time_to_decrease =
TimeMsToDecreaseLevel(26000, 1, 0, kLevelReductionDb);
const float time_to_decrease = TimeMsToDecreaseLevel(
SampleRateToDefaultChannelSize(26000u), 1, 0, kLevelReductionDb);
EXPECT_LE(kExpectedTime * 0.9, time_to_decrease);
EXPECT_LE(time_to_decrease, kExpectedTime * 1.1);
@ -150,7 +154,8 @@ TEST(GainController2FixedDigitalLevelEstimator,
constexpr size_t kNumChannels = 10;
const float time_to_decrease =
TimeMsToDecreaseLevel(28000, kNumChannels, 0, kLevelReductionDb);
TimeMsToDecreaseLevel(SampleRateToDefaultChannelSize(28000u),
kNumChannels, 0, kLevelReductionDb);
EXPECT_LE(kExpectedTime * 0.9, time_to_decrease);
EXPECT_LE(time_to_decrease, kExpectedTime * 1.1);

View File

@ -86,24 +86,25 @@ void ScaleSamples(MonoView<const float> per_sample_scaling_factors,
}
}
}
void CheckLimiterSampleRate(int sample_rate_hz) {
// Check that per_sample_scaling_factors_ is large enough.
RTC_DCHECK_LE(sample_rate_hz,
kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs);
}
} // namespace
Limiter::Limiter(int sample_rate_hz,
ApmDataDumper* apm_data_dumper,
Limiter::Limiter(ApmDataDumper* apm_data_dumper,
size_t samples_per_channel,
absl::string_view histogram_name)
: interp_gain_curve_(apm_data_dumper, histogram_name),
level_estimator_(sample_rate_hz, apm_data_dumper),
level_estimator_(samples_per_channel, apm_data_dumper),
apm_data_dumper_(apm_data_dumper) {
CheckLimiterSampleRate(sample_rate_hz);
RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel);
}
// Deprecated ctor.
Limiter::Limiter(int sample_rate_hz,
ApmDataDumper* apm_data_dumper,
absl::string_view histogram_name_prefix)
: Limiter(apm_data_dumper,
SampleRateToDefaultChannelSize(sample_rate_hz),
histogram_name_prefix) {}
Limiter::~Limiter() = default;
void Limiter::Process(AudioFrameView<float> signal) {
@ -140,9 +141,9 @@ InterpolatedGainCurve::Stats Limiter::GetGainCurveStats() const {
return interp_gain_curve_.get_stats();
}
void Limiter::SetSampleRate(int sample_rate_hz) {
CheckLimiterSampleRate(sample_rate_hz);
level_estimator_.SetSampleRate(sample_rate_hz);
void Limiter::SetSamplesPerChannel(size_t samples_per_channel) {
RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel);
level_estimator_.SetSamplesPerChannel(samples_per_channel);
}
void Limiter::Reset() {

View File

@ -14,6 +14,7 @@
#include <vector>
#include "absl/strings/string_view.h"
#include "api/audio/audio_frame.h"
#include "modules/audio_processing/agc2/fixed_digital_level_estimator.h"
#include "modules/audio_processing/agc2/interpolated_gain_curve.h"
#include "modules/audio_processing/include/audio_frame_view.h"
@ -23,9 +24,16 @@ class ApmDataDumper;
class Limiter {
public:
Limiter(int sample_rate_hz,
ApmDataDumper* apm_data_dumper,
// See `SetSamplesPerChannel()` for valid values for `samples_per_channel`.
Limiter(ApmDataDumper* apm_data_dumper,
size_t samples_per_channel,
absl::string_view histogram_name_prefix);
[[deprecated("Use constructor that accepts samples_per_channel")]] Limiter(
int sample_rate_hz,
ApmDataDumper* apm_data_dumper,
absl::string_view histogram_name_prefix);
Limiter(const Limiter& limiter) = delete;
Limiter& operator=(const Limiter& limiter) = delete;
~Limiter();
@ -34,12 +42,16 @@ class Limiter {
void Process(AudioFrameView<float> signal);
InterpolatedGainCurve::Stats GetGainCurveStats() const;
// Supported rates must be
// * supported by FixedDigitalLevelEstimator
// * below kMaximalNumberOfSamplesPerChannel*1000/kFrameDurationMs
// so that samples_per_channel fit in the
// per_sample_scaling_factors_ array.
void SetSampleRate(int sample_rate_hz);
// Supported values must be
// * Supported by FixedDigitalLevelEstimator
// * Below or equal to kMaximalNumberOfSamplesPerChannel so that samples
// fit in the per_sample_scaling_factors_ array.
void SetSamplesPerChannel(size_t samples_per_channel);
[[deprecated("Use SetSamplesPerChannel")]] void SetSampleRate(
int sample_rate_hz) {
SetSamplesPerChannel(SampleRateToDefaultChannelSize(sample_rate_hz));
}
// Resets the internal state.
void Reset();

View File

@ -10,6 +10,7 @@
#include "modules/audio_processing/agc2/limiter.h"
#include "api/audio/audio_frame.h"
#include "common_audio/include/audio_util.h"
#include "modules/audio_processing/agc2/agc2_common.h"
#include "modules/audio_processing/agc2/agc2_testing_common.h"
@ -20,33 +21,33 @@
namespace webrtc {
TEST(Limiter, LimiterShouldConstructAndRun) {
const int sample_rate_hz = 48000;
const size_t samples_per_channel = SampleRateToDefaultChannelSize(48000);
ApmDataDumper apm_data_dumper(0);
Limiter limiter(sample_rate_hz, &apm_data_dumper, "");
Limiter limiter(&apm_data_dumper, samples_per_channel, "");
VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100,
VectorFloatFrame vectors_with_float_frame(1, samples_per_channel,
kMaxAbsFloatS16Value);
limiter.Process(vectors_with_float_frame.float_frame_view());
}
TEST(Limiter, OutputVolumeAboveThreshold) {
const int sample_rate_hz = 48000;
const size_t samples_per_channel = SampleRateToDefaultChannelSize(48000);
const float input_level =
(kMaxAbsFloatS16Value + DbfsToFloatS16(test::kLimiterMaxInputLevelDbFs)) /
2.f;
ApmDataDumper apm_data_dumper(0);
Limiter limiter(sample_rate_hz, &apm_data_dumper, "");
Limiter limiter(&apm_data_dumper, samples_per_channel, "");
// Give the level estimator time to adapt.
for (int i = 0; i < 5; ++i) {
VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100,
VectorFloatFrame vectors_with_float_frame(1, samples_per_channel,
input_level);
limiter.Process(vectors_with_float_frame.float_frame_view());
}
VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100,
VectorFloatFrame vectors_with_float_frame(1, samples_per_channel,
input_level);
limiter.Process(vectors_with_float_frame.float_frame_view());
rtc::ArrayView<const float> channel =

View File

@ -13,6 +13,7 @@
#include <memory>
#include <utility>
#include "api/audio/audio_frame.h"
#include "common_audio/include/audio_util.h"
#include "modules/audio_processing/agc2/agc2_common.h"
#include "modules/audio_processing/agc2/cpu_features.h"
@ -94,7 +95,9 @@ GainController2::GainController2(
fixed_gain_applier_(
/*hard_clip_samples=*/false,
/*initial_gain_factor=*/DbToRatio(config.fixed_digital.gain_db)),
limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"),
limiter_(&data_dumper_,
SampleRateToDefaultChannelSize(sample_rate_hz),
/*histogram_name_prefix=*/"Agc2"),
calls_since_last_limiter_log_(0) {
RTC_DCHECK(Validate(config));
data_dumper_.InitiateNewSetOfRecordings();