Source code
Revision control
Copy as Markdown
Other Tools
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/audio_processing/agc/agc_manager_direct.h"
#include <fstream>
#include <limits>
#include <tuple>
#include <vector>
#include "modules/audio_processing/agc/gain_control.h"
#include "modules/audio_processing/agc/mock_agc.h"
#include "modules/audio_processing/include/mock_audio_processing.h"
#include "rtc_base/numerics/safe_minmax.h"
#include "rtc_base/strings/string_builder.h"
#include "test/field_trial.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace webrtc {
namespace {
constexpr int kSampleRateHz = 32000;
constexpr int kNumChannels = 1;
constexpr int kInitialInputVolume = 128;
constexpr int kClippedMin = 165; // Arbitrary, but different from the default.
constexpr float kAboveClippedThreshold = 0.2f;
constexpr int kMinMicLevel = 12;
constexpr int kClippedLevelStep = 15;
constexpr float kClippedRatioThreshold = 0.1f;
constexpr int kClippedWaitFrames = 300;
constexpr float kLowSpeechProbability = 0.1f;
constexpr float kHighSpeechProbability = 0.7f;
constexpr float kSpeechLevelDbfs = -25.0f;
constexpr float kMinSample = std::numeric_limits<int16_t>::min();
constexpr float kMaxSample = std::numeric_limits<int16_t>::max();
using AnalogAgcConfig =
AudioProcessing::Config::GainController1::AnalogGainController;
using ClippingPredictorConfig = AudioProcessing::Config::GainController1::
AnalogGainController::ClippingPredictor;
constexpr AnalogAgcConfig kDefaultAnalogConfig{};
class MockGainControl : public GainControl {
public:
virtual ~MockGainControl() {}
MOCK_METHOD(int, set_stream_analog_level, (int level), (override));
MOCK_METHOD(int, stream_analog_level, (), (const, override));
MOCK_METHOD(int, set_mode, (Mode mode), (override));
MOCK_METHOD(Mode, mode, (), (const, override));
MOCK_METHOD(int, set_target_level_dbfs, (int level), (override));
MOCK_METHOD(int, target_level_dbfs, (), (const, override));
MOCK_METHOD(int, set_compression_gain_db, (int gain), (override));
MOCK_METHOD(int, compression_gain_db, (), (const, override));
MOCK_METHOD(int, enable_limiter, (bool enable), (override));
MOCK_METHOD(bool, is_limiter_enabled, (), (const, override));
MOCK_METHOD(int,
set_analog_level_limits,
(int minimum, int maximum),
(override));
MOCK_METHOD(int, analog_level_minimum, (), (const, override));
MOCK_METHOD(int, analog_level_maximum, (), (const, override));
MOCK_METHOD(bool, stream_is_saturated, (), (const, override));
};
// TODO(bugs.webrtc.org/12874): Remove and use designated initializers once
// fixed.
std::unique_ptr<AgcManagerDirect> CreateAgcManagerDirect(
int startup_min_volume,
int clipped_level_step,
float clipped_ratio_threshold,
int clipped_wait_frames,
const ClippingPredictorConfig& clipping_predictor_config =
kDefaultAnalogConfig.clipping_predictor) {
AnalogAgcConfig config;
config.startup_min_volume = startup_min_volume;
config.clipped_level_min = kClippedMin;
config.enable_digital_adaptive = false;
config.clipped_level_step = clipped_level_step;
config.clipped_ratio_threshold = clipped_ratio_threshold;
config.clipped_wait_frames = clipped_wait_frames;
config.clipping_predictor = clipping_predictor_config;
return std::make_unique<AgcManagerDirect>(/*num_capture_channels=*/1, config);
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Delete this helper, use
// `AgcManagerDirectTestHelper::CallAgcSequence()` instead.
// Calls `AnalyzePreProcess()` on `manager` `num_calls` times. `peak_ratio` is a
// value in [0, 1] which determines the amplitude of the samples (1 maps to full
// scale). The first half of the calls is made on frames which are half filled
// with zeros in order to simulate a signal with different crest factors.
void CallPreProcessAudioBuffer(int num_calls,
float peak_ratio,
AgcManagerDirect& manager) {
RTC_DCHECK_LE(peak_ratio, 1.0f);
AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz,
kNumChannels, kSampleRateHz, kNumChannels);
const int num_channels = audio_buffer.num_channels();
const int num_frames = audio_buffer.num_frames();
// Make half of the calls with half zeroed frames.
for (int ch = 0; ch < num_channels; ++ch) {
// 50% of the samples in one frame are zero.
for (int i = 0; i < num_frames; i += 2) {
audio_buffer.channels()[ch][i] = peak_ratio * 32767.0f;
audio_buffer.channels()[ch][i + 1] = 0.0f;
}
}
for (int n = 0; n < num_calls / 2; ++n) {
manager.AnalyzePreProcess(audio_buffer);
}
// Make the remaining half of the calls with frames whose samples are all set.
for (int ch = 0; ch < num_channels; ++ch) {
for (int i = 0; i < num_frames; ++i) {
audio_buffer.channels()[ch][i] = peak_ratio * 32767.0f;
}
}
for (int n = 0; n < num_calls - num_calls / 2; ++n) {
manager.AnalyzePreProcess(audio_buffer);
}
}
constexpr char kMinMicLevelFieldTrial[] =
"WebRTC-Audio-2ndAgcMinMicLevelExperiment";
std::string GetAgcMinMicLevelExperimentFieldTrial(const std::string& value) {
char field_trial_buffer[64];
rtc::SimpleStringBuilder builder(field_trial_buffer);
builder << kMinMicLevelFieldTrial << "/" << value << "/";
return builder.str();
}
std::string GetAgcMinMicLevelExperimentFieldTrialEnabled(
int enabled_value,
const std::string& suffix = "") {
RTC_DCHECK_GE(enabled_value, 0);
RTC_DCHECK_LE(enabled_value, 255);
char field_trial_buffer[64];
rtc::SimpleStringBuilder builder(field_trial_buffer);
builder << kMinMicLevelFieldTrial << "/Enabled-" << enabled_value << suffix
<< "/";
return builder.str();
}
std::string GetAgcMinMicLevelExperimentFieldTrial(
std::optional<int> min_mic_level) {
if (min_mic_level.has_value()) {
return GetAgcMinMicLevelExperimentFieldTrialEnabled(*min_mic_level);
}
return GetAgcMinMicLevelExperimentFieldTrial("Disabled");
}
// (Over)writes `samples_value` for the samples in `audio_buffer`.
// When `clipped_ratio`, a value in [0, 1], is greater than 0, the corresponding
// fraction of the frame is set to a full scale value to simulate clipping.
void WriteAudioBufferSamples(float samples_value,
float clipped_ratio,
AudioBuffer& audio_buffer) {
RTC_DCHECK_GE(samples_value, kMinSample);
RTC_DCHECK_LE(samples_value, kMaxSample);
RTC_DCHECK_GE(clipped_ratio, 0.0f);
RTC_DCHECK_LE(clipped_ratio, 1.0f);
int num_channels = audio_buffer.num_channels();
int num_samples = audio_buffer.num_frames();
int num_clipping_samples = clipped_ratio * num_samples;
for (int ch = 0; ch < num_channels; ++ch) {
int i = 0;
for (; i < num_clipping_samples; ++i) {
audio_buffer.channels()[ch][i] = 32767.0f;
}
for (; i < num_samples; ++i) {
audio_buffer.channels()[ch][i] = samples_value;
}
}
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Delete this helper, use
// `AgcManagerDirectTestHelper::CallAgcSequence()` instead.
void CallPreProcessAndProcess(int num_calls,
const AudioBuffer& audio_buffer,
std::optional<float> speech_probability_override,
std::optional<float> speech_level_override,
AgcManagerDirect& manager) {
for (int n = 0; n < num_calls; ++n) {
manager.AnalyzePreProcess(audio_buffer);
manager.Process(audio_buffer, speech_probability_override,
speech_level_override);
}
}
// Reads a given number of 10 ms chunks from a PCM file and feeds them to
// `AgcManagerDirect`.
class SpeechSamplesReader {
private:
// Recording properties.
static constexpr int kPcmSampleRateHz = 16000;
static constexpr int kPcmNumChannels = 1;
static constexpr int kPcmBytesPerSamples = sizeof(int16_t);
public:
SpeechSamplesReader()
: is_(test::ResourcePath("audio_processing/agc/agc_audio", "pcm"),
std::ios::binary | std::ios::ate),
audio_buffer_(kPcmSampleRateHz,
kPcmNumChannels,
kPcmSampleRateHz,
kPcmNumChannels,
kPcmSampleRateHz,
kPcmNumChannels),
buffer_(audio_buffer_.num_frames()),
buffer_num_bytes_(buffer_.size() * kPcmBytesPerSamples) {
RTC_CHECK(is_);
}
// Reads `num_frames` 10 ms frames from the beginning of the PCM file, applies
// `gain_db` and feeds the frames into `agc` by calling `AnalyzePreProcess()`
// and `Process()` for each frame. Reads the number of 10 ms frames available
// in the PCM file if `num_frames` is too large - i.e., does not loop.
void Feed(int num_frames, int gain_db, AgcManagerDirect& agc) {
float gain = std::pow(10.0f, gain_db / 20.0f); // From dB to linear gain.
is_.seekg(0, is_.beg); // Start from the beginning of the PCM file.
// Read and feed frames.
for (int i = 0; i < num_frames; ++i) {
is_.read(reinterpret_cast<char*>(buffer_.data()), buffer_num_bytes_);
if (is_.gcount() < buffer_num_bytes_) {
// EOF reached. Stop.
break;
}
// Apply gain and copy samples into `audio_buffer_`.
std::transform(buffer_.begin(), buffer_.end(),
audio_buffer_.channels()[0], [gain](int16_t v) -> float {
return rtc::SafeClamp(static_cast<float>(v) * gain,
kMinSample, kMaxSample);
});
agc.AnalyzePreProcess(audio_buffer_);
agc.Process(audio_buffer_);
}
}
// Reads `num_frames` 10 ms frames from the beginning of the PCM file, applies
// `gain_db` and feeds the frames into `agc` by calling `AnalyzePreProcess()`
// and `Process()` for each frame. Reads the number of 10 ms frames available
// in the PCM file if `num_frames` is too large - i.e., does not loop.
// `speech_probability_override` and `speech_level_override` are passed to
// `Process()` where they are used to override the `agc` RMS error if they
// have a value.
void Feed(int num_frames,
int gain_db,
std::optional<float> speech_probability_override,
std::optional<float> speech_level_override,
AgcManagerDirect& agc) {
float gain = std::pow(10.0f, gain_db / 20.0f); // From dB to linear gain.
is_.seekg(0, is_.beg); // Start from the beginning of the PCM file.
// Read and feed frames.
for (int i = 0; i < num_frames; ++i) {
is_.read(reinterpret_cast<char*>(buffer_.data()), buffer_num_bytes_);
if (is_.gcount() < buffer_num_bytes_) {
// EOF reached. Stop.
break;
}
// Apply gain and copy samples into `audio_buffer_`.
std::transform(buffer_.begin(), buffer_.end(),
audio_buffer_.channels()[0], [gain](int16_t v) -> float {
return rtc::SafeClamp(static_cast<float>(v) * gain,
kMinSample, kMaxSample);
});
agc.AnalyzePreProcess(audio_buffer_);
agc.Process(audio_buffer_, speech_probability_override,
speech_level_override);
}
}
private:
std::ifstream is_;
AudioBuffer audio_buffer_;
std::vector<int16_t> buffer_;
const std::streamsize buffer_num_bytes_;
};
} // namespace
// TODO(bugs.webrtc.org/12874): Use constexpr struct with designated
// initializers once fixed.
constexpr AnalogAgcConfig GetAnalogAgcTestConfig() {
AnalogAgcConfig config;
config.enabled = true;
config.startup_min_volume = kInitialInputVolume;
config.clipped_level_min = kClippedMin;
config.enable_digital_adaptive = true;
config.clipped_level_step = kClippedLevelStep;
config.clipped_ratio_threshold = kClippedRatioThreshold;
config.clipped_wait_frames = kClippedWaitFrames;
config.clipping_predictor = kDefaultAnalogConfig.clipping_predictor;
return config;
}
constexpr AnalogAgcConfig GetDisabledAnalogAgcConfig() {
AnalogAgcConfig config = GetAnalogAgcTestConfig();
config.enabled = false;
return config;
}
// Helper class that provides an `AgcManagerDirect` instance with an injected
// `Agc` mock, an `AudioBuffer` instance and `CallAgcSequence()`, a helper
// method that runs the `AgcManagerDirect` instance on the `AudioBuffer` one by
// sticking to the API contract.
class AgcManagerDirectTestHelper {
public:
// Ctor. Initializes `audio_buffer` with zeros.
AgcManagerDirectTestHelper()
: audio_buffer(kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels),
mock_agc(new ::testing::NiceMock<MockAgc>()),
manager(GetAnalogAgcTestConfig(), mock_agc) {
manager.Initialize();
manager.SetupDigitalGainControl(mock_gain_control);
WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipped_ratio=*/0.0f,
audio_buffer);
}
// Calls the sequence of `AgcManagerDirect` methods according to the API
// contract, namely:
// - Sets the applied input volume;
// - Uses `audio_buffer` to call `AnalyzePreProcess()` and `Process()`;
// - Sets the digital compression gain, if specified, on the injected
// `mock_agc`. Returns the recommended input volume. The RMS error from
// AGC is replaced by an override value if `speech_probability_override`
// and `speech_level_override` have a value.
int CallAgcSequence(int applied_input_volume,
std::optional<float> speech_probability_override,
std::optional<float> speech_level_override) {
manager.set_stream_analog_level(applied_input_volume);
manager.AnalyzePreProcess(audio_buffer);
manager.Process(audio_buffer, speech_probability_override,
speech_level_override);
std::optional<int> digital_gain = manager.GetDigitalComressionGain();
if (digital_gain) {
mock_gain_control.set_compression_gain_db(*digital_gain);
}
return manager.recommended_analog_level();
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use
// `CallAgcSequence()`. The RMS error from AGC is replaced by an override
// value if `speech_probability_override` and `speech_level_override` have
// a value.
void CallProcess(int num_calls,
std::optional<float> speech_probability_override,
std::optional<float> speech_level_override) {
for (int i = 0; i < num_calls; ++i) {
EXPECT_CALL(*mock_agc, Process(_)).WillOnce(Return());
manager.Process(audio_buffer, speech_probability_override,
speech_level_override);
std::optional<int> new_digital_gain = manager.GetDigitalComressionGain();
if (new_digital_gain) {
mock_gain_control.set_compression_gain_db(*new_digital_gain);
}
}
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use
// `CallAgcSequence()`.
void CallPreProc(int num_calls, float clipped_ratio) {
RTC_DCHECK_GE(clipped_ratio, 0.0f);
RTC_DCHECK_LE(clipped_ratio, 1.0f);
WriteAudioBufferSamples(/*samples_value=*/0.0f, clipped_ratio,
audio_buffer);
for (int i = 0; i < num_calls; ++i) {
manager.AnalyzePreProcess(audio_buffer);
}
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use
// `CallAgcSequence()`.
void CallPreProcForChangingAudio(int num_calls, float peak_ratio) {
RTC_DCHECK_GE(peak_ratio, 0.0f);
RTC_DCHECK_LE(peak_ratio, 1.0f);
const float samples_value = peak_ratio * 32767.0f;
// Make half of the calls on a frame where the samples alternate
// `sample_values` and zeros.
WriteAudioBufferSamples(samples_value, /*clipped_ratio=*/0.0f,
audio_buffer);
for (size_t ch = 0; ch < audio_buffer.num_channels(); ++ch) {
for (size_t k = 1; k < audio_buffer.num_frames(); k += 2) {
audio_buffer.channels()[ch][k] = 0.0f;
}
}
for (int i = 0; i < num_calls / 2; ++i) {
manager.AnalyzePreProcess(audio_buffer);
}
// Make half of thecalls on a frame where all the samples equal
// `sample_values`.
WriteAudioBufferSamples(samples_value, /*clipped_ratio=*/0.0f,
audio_buffer);
for (int i = 0; i < num_calls - num_calls / 2; ++i) {
manager.AnalyzePreProcess(audio_buffer);
}
}
AudioBuffer audio_buffer;
MockAgc* mock_agc;
AgcManagerDirect manager;
MockGainControl mock_gain_control;
};
class AgcManagerDirectParametrizedTest
: public ::testing::TestWithParam<std::tuple<std::optional<int>, bool>> {
protected:
AgcManagerDirectParametrizedTest()
: field_trials_(
GetAgcMinMicLevelExperimentFieldTrial(std::get<0>(GetParam()))) {}
bool IsMinMicLevelOverridden() const {
return std::get<0>(GetParam()).has_value();
}
int GetMinMicLevel() const {
return std::get<0>(GetParam()).value_or(kMinMicLevel);
}
bool IsRmsErrorOverridden() const { return std::get<1>(GetParam()); }
std::optional<float> GetOverrideOrEmpty(float value) const {
return IsRmsErrorOverridden() ? std::optional<float>(value) : std::nullopt;
}
private:
test::ScopedFieldTrials field_trials_;
};
INSTANTIATE_TEST_SUITE_P(
,
AgcManagerDirectParametrizedTest,
::testing::Combine(testing::Values(std::nullopt, 12, 20), testing::Bool()));
// Checks that when the analog controller is disabled, no downward adaptation
// takes place.
// TODO(webrtc:7494): Revisit the test after moving the number of override wait
// frames to AMP config. The test passes but internally the gain update timing
// differs.
TEST_P(AgcManagerDirectParametrizedTest,
DisabledAnalogAgcDoesNotAdaptDownwards) {
AgcManagerDirect manager_no_analog_agc(kNumChannels,
GetDisabledAnalogAgcConfig());
manager_no_analog_agc.Initialize();
AgcManagerDirect manager_with_analog_agc(kNumChannels,
GetAnalogAgcTestConfig());
manager_with_analog_agc.Initialize();
AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz,
kNumChannels, kSampleRateHz, kNumChannels);
constexpr int kAnalogLevel = 250;
static_assert(kAnalogLevel > kInitialInputVolume, "Increase `kAnalogLevel`.");
manager_no_analog_agc.set_stream_analog_level(kAnalogLevel);
manager_with_analog_agc.set_stream_analog_level(kAnalogLevel);
// Make a first call with input that doesn't clip in order to let the
// controller read the input volume. That is needed because clipping input
// causes the controller to stay in idle state for
// `AnalogAgcConfig::clipped_wait_frames` frames.
WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipping_ratio=*/0.0f,
audio_buffer);
manager_no_analog_agc.AnalyzePreProcess(audio_buffer);
manager_with_analog_agc.AnalyzePreProcess(audio_buffer);
manager_no_analog_agc.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-18.0f));
manager_with_analog_agc.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-18.0f));
// Feed clipping input to trigger a downward adapation of the analog level.
WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipping_ratio=*/0.2f,
audio_buffer);
manager_no_analog_agc.AnalyzePreProcess(audio_buffer);
manager_with_analog_agc.AnalyzePreProcess(audio_buffer);
manager_no_analog_agc.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-10.0f));
manager_with_analog_agc.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-10.0f));
// Check that no adaptation occurs when the analog controller is disabled
// and make sure that the test triggers a downward adaptation otherwise.
EXPECT_EQ(manager_no_analog_agc.recommended_analog_level(), kAnalogLevel);
ASSERT_LT(manager_with_analog_agc.recommended_analog_level(), kAnalogLevel);
}
// Checks that when the analog controller is disabled, no upward adaptation
// takes place.
// TODO(webrtc:7494): Revisit the test after moving the number of override wait
// frames to APM config. The test passes but internally the gain update timing
// differs.
TEST_P(AgcManagerDirectParametrizedTest, DisabledAnalogAgcDoesNotAdaptUpwards) {
AgcManagerDirect manager_no_analog_agc(kNumChannels,
GetDisabledAnalogAgcConfig());
manager_no_analog_agc.Initialize();
AgcManagerDirect manager_with_analog_agc(kNumChannels,
GetAnalogAgcTestConfig());
manager_with_analog_agc.Initialize();
constexpr int kAnalogLevel = kInitialInputVolume;
manager_no_analog_agc.set_stream_analog_level(kAnalogLevel);
manager_with_analog_agc.set_stream_analog_level(kAnalogLevel);
// Feed speech with low energy to trigger an upward adapation of the analog
// level.
constexpr int kNumFrames = 125;
constexpr int kGainDb = -20;
SpeechSamplesReader reader;
reader.Feed(kNumFrames, kGainDb, GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-42.0f), manager_no_analog_agc);
reader.Feed(kNumFrames, kGainDb, GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-42.0f), manager_with_analog_agc);
// Check that no adaptation occurs when the analog controller is disabled
// and make sure that the test triggers an upward adaptation otherwise.
EXPECT_EQ(manager_no_analog_agc.recommended_analog_level(), kAnalogLevel);
ASSERT_GT(manager_with_analog_agc.recommended_analog_level(), kAnalogLevel);
}
TEST_P(AgcManagerDirectParametrizedTest,
StartupMinVolumeConfigurationIsRespected) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_EQ(kInitialInputVolume, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, MicVolumeResponseToRmsError) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Compressor default; no residual error.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(5), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-23.0f));
// Inside the compressor's window; no change of volume.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(10), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-28.0f));
// Above the compressor's window; volume should be increased.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-29.0f));
EXPECT_EQ(130, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(20), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-38.0f));
EXPECT_EQ(168, helper.manager.recommended_analog_level());
// Inside the compressor's window; no change of volume.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(5), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-23.0f));
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(0), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-18.0f));
// Below the compressor's window; volume should be decreased.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(167, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(163, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-9), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-9.0f));
EXPECT_EQ(129, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, MicVolumeIsLimited) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Maximum upwards change is limited.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(183, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(243, helper.manager.recommended_analog_level());
// Won't go higher than the maximum.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(255, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(254, helper.manager.recommended_analog_level());
// Maximum downwards change is limited.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(194, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(137, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(88, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(54, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(33, helper.manager.recommended_analog_level());
// Won't go lower than the minimum.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(std::max(18, GetMinMicLevel()),
helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(22.0f));
EXPECT_EQ(std::max(12, GetMinMicLevel()),
helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, CompressorStepsTowardsTarget) {
constexpr std::optional<float> kNoOverride = std::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Compressor default; no call to set_compression_gain_db.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(5), Return(true)))
.WillRepeatedly(Return(false));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-23.0f));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
// The mock `GetRmsErrorDb()` returns false; mimic this by passing
// std::nullopt as an override.
helper.CallProcess(/*num_calls=*/19, kNoOverride, kNoOverride);
// Moves slowly upwards.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(9), Return(true)))
.WillRepeatedly(Return(false));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-27.0f));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/19, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
// Moves slowly downward, then reverses before reaching the original target.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(5), Return(true)))
.WillRepeatedly(Return(false));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-23.0f));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(9), Return(true)))
.WillRepeatedly(Return(false));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-27.0f));
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
}
TEST_P(AgcManagerDirectParametrizedTest, CompressorErrorIsDeemphasized) {
constexpr std::optional<float> kNoOverride = std::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(10), Return(true)))
.WillRepeatedly(Return(false));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-28.0f));
// The mock `GetRmsErrorDb()` returns false; mimic this by passing
// std::nullopt as an override.
helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(0), Return(true)))
.WillRepeatedly(Return(false));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-18.0f));
helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(7))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(6))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0);
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
}
TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMaximum) {
constexpr std::optional<float> kNoOverride = std::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(10), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(10), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(10), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(10), Return(true)))
.WillRepeatedly(Return(false));
helper.CallProcess(/*num_calls=*/4, speech_probability_override,
GetOverrideOrEmpty(-28.0f));
// The mock `GetRmsErrorDb()` returns false; mimic this by passing
// std::nullopt as an override.
helper.CallProcess(/*num_calls=*/15, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(10))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(11))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(12))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
}
TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMinimum) {
constexpr std::optional<float> kNoOverride = std::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(0), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(0), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(0), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(0), Return(true)))
.WillRepeatedly(Return(false));
helper.CallProcess(/*num_calls=*/4, speech_probability_override,
GetOverrideOrEmpty(-18.0f));
// The mock `GetRmsErrorDb()` returns false; mimic this by passing
// std::nullopt as an override.
helper.CallProcess(/*num_calls=*/15, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(6))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(5))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(4))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(3))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(2))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
}
TEST_P(AgcManagerDirectParametrizedTest, NoActionWhileMuted) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
helper.manager.HandleCaptureOutputUsedChange(false);
helper.manager.Process(helper.audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
std::optional<int> new_digital_gain =
helper.manager.GetDigitalComressionGain();
if (new_digital_gain) {
helper.mock_gain_control.set_compression_gain_db(*new_digital_gain);
}
}
TEST_P(AgcManagerDirectParametrizedTest, UnmutingChecksVolumeWithoutRaising) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
helper.manager.HandleCaptureOutputUsedChange(false);
helper.manager.HandleCaptureOutputUsedChange(true);
constexpr int kInputVolume = 127;
helper.manager.set_stream_analog_level(kInputVolume);
EXPECT_CALL(*helper.mock_agc, Reset());
// SetMicVolume should not be called.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)).WillOnce(Return(false));
helper.CallProcess(/*num_calls=*/1,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_EQ(127, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, UnmutingRaisesTooLowVolume) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
helper.manager.HandleCaptureOutputUsedChange(false);
helper.manager.HandleCaptureOutputUsedChange(true);
constexpr int kInputVolume = 11;
helper.manager.set_stream_analog_level(kInputVolume);
EXPECT_CALL(*helper.mock_agc, Reset());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)).WillOnce(Return(false));
helper.CallProcess(/*num_calls=*/1,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest,
ManualLevelChangeResultsInNoSetMicCall) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Change outside of compressor's range, which would normally trigger a call
// to `SetMicVolume()`.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)));
// When the analog volume changes, the gain controller is reset.
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
// GetMicVolume returns a value outside of the quantization slack, indicating
// a manual volume change.
ASSERT_NE(helper.manager.recommended_analog_level(), 154);
helper.manager.set_stream_analog_level(154);
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-29.0f));
EXPECT_EQ(154, helper.manager.recommended_analog_level());
// Do the same thing, except downwards now.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.manager.set_stream_analog_level(100);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(100, helper.manager.recommended_analog_level());
// And finally verify the AGC continues working without a manual change.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(99, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest,
RecoveryAfterManualLevelChangeFromMax) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Force the mic up to max volume. Takes a few steps due to the residual
// gain limitation.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(183, helper.manager.recommended_analog_level());
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(243, helper.manager.recommended_analog_level());
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(255, helper.manager.recommended_analog_level());
// Manual change does not result in SetMicVolume call.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.manager.set_stream_analog_level(50);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(50, helper.manager.recommended_analog_level());
// Continues working as usual afterwards.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(20), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-38.0f));
EXPECT_EQ(69, helper.manager.recommended_analog_level());
}
// Checks that, when the min mic level override is not specified, AGC ramps up
// towards the minimum mic level after the mic level is manually set below the
// minimum gain to enforce.
TEST_P(AgcManagerDirectParametrizedTest,
RecoveryAfterManualLevelChangeBelowMinWithoutMiMicLevelnOverride) {
if (IsMinMicLevelOverridden()) {
GTEST_SKIP() << "Skipped. Min mic level overridden.";
}
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Manual change below min, but strictly positive, otherwise AGC won't take
// any action.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.manager.set_stream_analog_level(1);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(1, helper.manager.recommended_analog_level());
// Continues working as usual afterwards.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-29.0f));
EXPECT_EQ(2, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(11, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(20), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-38.0f));
EXPECT_EQ(18, helper.manager.recommended_analog_level());
}
// Checks that, when the min mic level override is specified, AGC immediately
// applies the minimum mic level after the mic level is manually set below the
// minimum gain to enforce.
TEST_P(AgcManagerDirectParametrizedTest,
RecoveryAfterManualLevelChangeBelowMin) {
if (!IsMinMicLevelOverridden()) {
GTEST_SKIP() << "Skipped. Min mic level not overridden.";
}
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
// Manual change below min, but strictly positive, otherwise
// AGC won't take any action.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-1), Return(true)));
helper.manager.set_stream_analog_level(1);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-17.0f));
EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, NoClippingHasNoImpact) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
helper.CallPreProc(/*num_calls=*/100, /*clipped_ratio=*/0);
EXPECT_EQ(128, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, ClippingUnderThresholdHasNoImpact) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.099);
EXPECT_EQ(128, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, ClippingLowersVolume) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.2);
EXPECT_EQ(240, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, WaitingPeriodBetweenClippingChecks) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(240, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, Reset()).Times(0);
helper.CallPreProc(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(240, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(225, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, ClippingLoweringIsLimited) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/180,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, Reset()).Times(0);
helper.CallPreProc(/*num_calls=*/1000,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest,
ClippingMaxIsRespectedWhenEqualToLevel) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(240, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/10, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(240, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest,
ClippingMaxIsRespectedWhenHigherThanLevel) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/200,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(185, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-58.0f));
EXPECT_EQ(240, helper.manager.recommended_analog_level());
helper.CallProcess(/*num_calls=*/10, speech_probability_override,
GetOverrideOrEmpty(-58.0f));
EXPECT_EQ(240, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest,
MaxCompressionIsIncreasedAfterClipping) {
constexpr std::optional<float> kNoOverride = std::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/210,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, kAboveClippedThreshold);
EXPECT_EQ(195, helper.manager.recommended_analog_level());
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(11), Return(true)))
.WillRepeatedly(Return(false));
helper.CallProcess(/*num_calls=*/5, speech_probability_override,
GetOverrideOrEmpty(-29.0f));
// The mock `GetRmsErrorDb()` returns false; mimic this by passing
// std::nullopt as an override.
helper.CallProcess(/*num_calls=*/14, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(10))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(11))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(12))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(13))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
// Continue clipping until we hit the maximum surplus compression.
helper.CallPreProc(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(180, helper.manager.recommended_analog_level());
helper.CallPreProc(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level());
// Current level is now at the minimum, but the maximum allowed level still
// has more to decrease.
helper.CallPreProc(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
helper.CallPreProc(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
helper.CallPreProc(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(16), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(16), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(16), Return(true)))
.WillOnce(DoAll(SetArgPointee<0>(16), Return(true)))
.WillRepeatedly(Return(false));
helper.CallProcess(/*num_calls=*/4, speech_probability_override,
GetOverrideOrEmpty(-34.0f));
helper.CallProcess(/*num_calls=*/15, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(14))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(15))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(16))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(17))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride);
EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(18))
.WillOnce(Return(0));
helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride);
}
TEST_P(AgcManagerDirectParametrizedTest, UserCanRaiseVolumeAfterClipping) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/225,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(210, helper.manager.recommended_analog_level());
// High enough error to trigger a volume check.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(14), Return(true)));
// User changed the volume.
helper.manager.set_stream_analog_level(250);
EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-32.0f));
EXPECT_EQ(250, helper.manager.recommended_analog_level());
// Move down...
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(-10), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-8.0f));
EXPECT_EQ(210, helper.manager.recommended_analog_level());
// And back up to the new max established by the user.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(40), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-58.0f));
EXPECT_EQ(250, helper.manager.recommended_analog_level());
// Will not move above new maximum.
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(30), Return(true)));
helper.CallProcess(/*num_calls=*/1, speech_probability_override,
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(250, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, ClippingDoesNotPullLowVolumeBackUp) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/80,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, Reset()).Times(0);
int initial_volume = helper.manager.recommended_analog_level();
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(initial_volume, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, TakesNoActionOnZeroMicVolume) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_))
.WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true)));
helper.manager.set_stream_analog_level(0);
helper.CallProcess(/*num_calls=*/10,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(-48.0f));
EXPECT_EQ(0, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, ClippingDetectionLowersVolume) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_EQ(255, helper.manager.recommended_analog_level());
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f);
EXPECT_EQ(255, helper.manager.recommended_analog_level());
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/1.0f);
EXPECT_EQ(240, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest,
DisabledClippingPredictorDoesNotLowerVolume) {
AgcManagerDirectTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_FALSE(helper.manager.clipping_predictor_enabled());
EXPECT_EQ(255, helper.manager.recommended_analog_level());
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f);
EXPECT_EQ(255, helper.manager.recommended_analog_level());
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f);
EXPECT_EQ(255, helper.manager.recommended_analog_level());
}
TEST_P(AgcManagerDirectParametrizedTest, DisableDigitalDisablesDigital) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
auto agc = std::unique_ptr<Agc>(new ::testing::NiceMock<MockAgc>());
MockGainControl mock_gain_control;
EXPECT_CALL(mock_gain_control, set_mode(GainControl::kFixedDigital));
EXPECT_CALL(mock_gain_control, set_target_level_dbfs(0));
EXPECT_CALL(mock_gain_control, set_compression_gain_db(0));
EXPECT_CALL(mock_gain_control, enable_limiter(false));
AnalogAgcConfig config;
config.enable_digital_adaptive = false;
auto manager = std::make_unique<AgcManagerDirect>(kNumChannels, config);
manager->Initialize();
manager->SetupDigitalGainControl(mock_gain_control);
}
TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentDefault) {
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
}
TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentDisabled) {
for (const std::string& field_trial_suffix : {"", "_20220210"}) {
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrial("Disabled" + field_trial_suffix));
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
}
}
// Checks that a field-trial parameter outside of the valid range [0,255] is
// ignored.
TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentOutOfRangeAbove) {
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrial("Enabled-256"));
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
}
// Checks that a field-trial parameter outside of the valid range [0,255] is
// ignored.
TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentOutOfRangeBelow) {
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrial("Enabled--1"));
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
}
// Verifies that a valid experiment changes the minimum microphone level. The
// start volume is larger than the min level and should therefore not be
// changed.
TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentEnabled50) {
constexpr int kMinMicLevelOverride = 50;
for (const std::string& field_trial_suffix : {"", "_20220210"}) {
SCOPED_TRACE(field_trial_suffix);
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride,
field_trial_suffix));
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevelOverride);
}
}
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
// specified with a valid value, the mic level never gets lowered beyond the
// override value in the presence of clipping.
TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentCheckMinLevelWithClipping) {
constexpr int kMinMicLevelOverride = 250;
// Create and initialize two AGCs by specifying and leaving unspecified the
// relevant field trial.
const auto factory = []() {
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
manager->Initialize();
manager->set_stream_analog_level(kInitialInputVolume);
return manager;
};
std::unique_ptr<AgcManagerDirect> manager = factory();
std::unique_ptr<AgcManagerDirect> manager_with_override;
{
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride));
manager_with_override = factory();
}
// Create a test input signal which containts 80% of clipped samples.
AudioBuffer audio_buffer(kSampleRateHz, 1, kSampleRateHz, 1, kSampleRateHz,
1);
WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f,
audio_buffer);
// Simulate 4 seconds of clipping; it is expected to trigger a downward
// adjustment of the analog gain.
CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::nullopt,
/*speech_level_override=*/std::nullopt, *manager);
CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::nullopt,
/*speech_level_override=*/std::nullopt,
*manager_with_override);
// Make sure that an adaptation occurred.
ASSERT_GT(manager->recommended_analog_level(), 0);
// Check that the test signal triggers a larger downward adaptation for
// `manager`, which is allowed to reach a lower gain.
EXPECT_GT(manager_with_override->recommended_analog_level(),
manager->recommended_analog_level());
// Check that the gain selected by `manager_with_override` equals the minimum
// value overridden via field trial.
EXPECT_EQ(manager_with_override->recommended_analog_level(),
kMinMicLevelOverride);
}
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
// specified with a valid value, the mic level never gets lowered beyond the
// override value in the presence of clipping when RMS error override is used.
// TODO(webrtc:7494): Revisit the test after moving the number of override wait
// frames to APM config. The test passes but internally the gain update timing
// differs.
TEST(AgcManagerDirectTest,
AgcMinMicLevelExperimentCheckMinLevelWithClippingWithRmsErrorOverride) {
constexpr int kMinMicLevelOverride = 250;
// Create and initialize two AGCs by specifying and leaving unspecified the
// relevant field trial.
const auto factory = []() {
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
manager->Initialize();
manager->set_stream_analog_level(kInitialInputVolume);
return manager;
};
std::unique_ptr<AgcManagerDirect> manager = factory();
std::unique_ptr<AgcManagerDirect> manager_with_override;
{
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride));
manager_with_override = factory();
}
// Create a test input signal which containts 80% of clipped samples.
AudioBuffer audio_buffer(kSampleRateHz, 1, kSampleRateHz, 1, kSampleRateHz,
1);
WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f,
audio_buffer);
// Simulate 4 seconds of clipping; it is expected to trigger a downward
// adjustment of the analog gain.
CallPreProcessAndProcess(
/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/0.7f,
/*speech_probability_level=*/-18.0f, *manager);
CallPreProcessAndProcess(
/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::optional<float>(0.7f),
/*speech_probability_level=*/std::optional<float>(-18.0f),
*manager_with_override);
// Make sure that an adaptation occurred.
ASSERT_GT(manager->recommended_analog_level(), 0);
// Check that the test signal triggers a larger downward adaptation for
// `manager`, which is allowed to reach a lower gain.
EXPECT_GT(manager_with_override->recommended_analog_level(),
manager->recommended_analog_level());
// Check that the gain selected by `manager_with_override` equals the minimum
// value overridden via field trial.
EXPECT_EQ(manager_with_override->recommended_analog_level(),
kMinMicLevelOverride);
}
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
// specified with a value lower than the `clipped_level_min`, the behavior of
// the analog gain controller is the same as that obtained when the field trial
// is not specified.
TEST(AgcManagerDirectTest,
AgcMinMicLevelExperimentCompareMicLevelWithClipping) {
// Create and initialize two AGCs by specifying and leaving unspecified the
// relevant field trial.
const auto factory = []() {
// Use a large clipped level step to more quickly decrease the analog gain
// with clipping.
AnalogAgcConfig config = kDefaultAnalogConfig;
config.startup_min_volume = kInitialInputVolume;
config.enable_digital_adaptive = false;
config.clipped_level_step = 64;
config.clipped_ratio_threshold = kClippedRatioThreshold;
config.clipped_wait_frames = kClippedWaitFrames;
auto controller =
std::make_unique<AgcManagerDirect>(/*num_capture_channels=*/1, config);
controller->Initialize();
controller->set_stream_analog_level(kInitialInputVolume);
return controller;
};
std::unique_ptr<AgcManagerDirect> manager = factory();
std::unique_ptr<AgcManagerDirect> manager_with_override;
{
constexpr int kMinMicLevelOverride = 20;
static_assert(
kDefaultAnalogConfig.clipped_level_min >= kMinMicLevelOverride,
"Use a lower override value.");
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride));
manager_with_override = factory();
}
// Create a test input signal which containts 80% of clipped samples.
AudioBuffer audio_buffer(kSampleRateHz, 1, kSampleRateHz, 1, kSampleRateHz,
1);
WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f,
audio_buffer);
// Simulate 4 seconds of clipping; it is expected to trigger a downward
// adjustment of the analog gain.
CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::nullopt,
/*speech_level_override=*/std::nullopt, *manager);
CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::nullopt,
/*speech_level_override=*/std::nullopt,
*manager_with_override);
// Make sure that an adaptation occurred.
ASSERT_GT(manager->recommended_analog_level(), 0);
// Check that the selected analog gain is the same for both controllers and
// that it equals the minimum level reached when clipping is handled. That is
// expected because the minimum microphone level override is less than the
// minimum level used when clipping is detected.
EXPECT_EQ(manager->recommended_analog_level(),
manager_with_override->recommended_analog_level());
EXPECT_EQ(manager_with_override->recommended_analog_level(),
kDefaultAnalogConfig.clipped_level_min);
}
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
// specified with a value lower than the `clipped_level_min`, the behavior of
// the analog gain controller is the same as that obtained when the field trial
// is not specified.
// TODO(webrtc:7494): Revisit the test after moving the number of override wait
// frames to APM config. The test passes but internally the gain update timing
// differs.
TEST(AgcManagerDirectTest,
AgcMinMicLevelExperimentCompareMicLevelWithClippingWithRmsErrorOverride) {
// Create and initialize two AGCs by specifying and leaving unspecified the
// relevant field trial.
const auto factory = []() {
// Use a large clipped level step to more quickly decrease the analog gain
// with clipping.
AnalogAgcConfig config = kDefaultAnalogConfig;
config.startup_min_volume = kInitialInputVolume;
config.enable_digital_adaptive = false;
config.clipped_level_step = 64;
config.clipped_ratio_threshold = kClippedRatioThreshold;
config.clipped_wait_frames = kClippedWaitFrames;
auto controller =
std::make_unique<AgcManagerDirect>(/*num_capture_channels=*/1, config);
controller->Initialize();
controller->set_stream_analog_level(kInitialInputVolume);
return controller;
};
std::unique_ptr<AgcManagerDirect> manager = factory();
std::unique_ptr<AgcManagerDirect> manager_with_override;
{
constexpr int kMinMicLevelOverride = 20;
static_assert(
kDefaultAnalogConfig.clipped_level_min >= kMinMicLevelOverride,
"Use a lower override value.");
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride));
manager_with_override = factory();
}
// Create a test input signal which containts 80% of clipped samples.
AudioBuffer audio_buffer(kSampleRateHz, 1, kSampleRateHz, 1, kSampleRateHz,
1);
WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f,
audio_buffer);
CallPreProcessAndProcess(
/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::optional<float>(0.7f),
/*speech_level_override=*/std::optional<float>(-18.0f), *manager);
CallPreProcessAndProcess(
/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/std::optional<float>(0.7f),
/*speech_level_override=*/std::optional<float>(-18.0f),
*manager_with_override);
// Make sure that an adaptation occurred.
ASSERT_GT(manager->recommended_analog_level(), 0);
// Check that the selected analog gain is the same for both controllers and
// that it equals the minimum level reached when clipping is handled. That is
// expected because the minimum microphone level override is less than the
// minimum level used when clipping is detected.
EXPECT_EQ(manager->recommended_analog_level(),
manager_with_override->recommended_analog_level());
EXPECT_EQ(manager_with_override->recommended_analog_level(),
kDefaultAnalogConfig.clipped_level_min);
}
// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_level_step`.
// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_ratio_threshold`.
// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_wait_frames`.
// Verifies that configurable clipping parameters are initialized as intended.
TEST_P(AgcManagerDirectParametrizedTest, ClippingParametersVerified) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
std::unique_ptr<AgcManagerDirect> manager =
CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
manager->Initialize();
EXPECT_EQ(manager->clipped_level_step_, kClippedLevelStep);
EXPECT_EQ(manager->clipped_ratio_threshold_, kClippedRatioThreshold);
EXPECT_EQ(manager->clipped_wait_frames_, kClippedWaitFrames);
std::unique_ptr<AgcManagerDirect> manager_custom =
CreateAgcManagerDirect(kInitialInputVolume,
/*clipped_level_step=*/10,
/*clipped_ratio_threshold=*/0.2f,
/*clipped_wait_frames=*/50);
manager_custom->Initialize();
EXPECT_EQ(manager_custom->clipped_level_step_, 10);
EXPECT_EQ(manager_custom->clipped_ratio_threshold_, 0.2f);
EXPECT_EQ(manager_custom->clipped_wait_frames_, 50);
}
TEST_P(AgcManagerDirectParametrizedTest,
DisableClippingPredictorDisablesClippingPredictor) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
// TODO(bugs.webrtc.org/12874): Use designated initializers once fixed.
ClippingPredictorConfig config;
config.enabled = false;
std::unique_ptr<AgcManagerDirect> manager = CreateAgcManagerDirect(
kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames, config);
manager->Initialize();
EXPECT_FALSE(manager->clipping_predictor_enabled());
EXPECT_FALSE(manager->use_clipping_predictor_step());
}
TEST_P(AgcManagerDirectParametrizedTest, ClippingPredictorDisabledByDefault) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
constexpr ClippingPredictorConfig kDefaultConfig;
EXPECT_FALSE(kDefaultConfig.enabled);
}
TEST_P(AgcManagerDirectParametrizedTest,
EnableClippingPredictorEnablesClippingPredictor) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
// TODO(bugs.webrtc.org/12874): Use designated initializers once fixed.
ClippingPredictorConfig config;
config.enabled = true;
config.use_predicted_step = true;
std::unique_ptr<AgcManagerDirect> manager = CreateAgcManagerDirect(
kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames, config);
manager->Initialize();
EXPECT_TRUE(manager->clipping_predictor_enabled());
EXPECT_TRUE(manager->use_clipping_predictor_step());
}
TEST_P(AgcManagerDirectParametrizedTest,
DisableClippingPredictorDoesNotLowerVolume) {
AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz,
kNumChannels, kSampleRateHz, kNumChannels);
AnalogAgcConfig config = GetAnalogAgcTestConfig();
config.clipping_predictor.enabled = false;
AgcManagerDirect manager(config, new ::testing::NiceMock<MockAgc>());
manager.Initialize();
manager.set_stream_analog_level(/*level=*/255);
EXPECT_FALSE(manager.clipping_predictor_enabled());
EXPECT_FALSE(manager.use_clipping_predictor_step());
EXPECT_EQ(manager.recommended_analog_level(), 255);
manager.Process(audio_buffer, GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
CallPreProcessAudioBuffer(/*num_calls=*/10, /*peak_ratio=*/0.99f, manager);
EXPECT_EQ(manager.recommended_analog_level(), 255);
CallPreProcessAudioBuffer(/*num_calls=*/300, /*peak_ratio=*/0.99f, manager);
EXPECT_EQ(manager.recommended_analog_level(), 255);
CallPreProcessAudioBuffer(/*num_calls=*/10, /*peak_ratio=*/0.99f, manager);
EXPECT_EQ(manager.recommended_analog_level(), 255);
}
TEST_P(AgcManagerDirectParametrizedTest,
UsedClippingPredictionsProduceLowerAnalogLevels) {
AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz,
kNumChannels, kSampleRateHz, kNumChannels);
AnalogAgcConfig config_with_prediction = GetAnalogAgcTestConfig();
config_with_prediction.clipping_predictor.enabled = true;
config_with_prediction.clipping_predictor.use_predicted_step = true;
AnalogAgcConfig config_without_prediction = GetAnalogAgcTestConfig();
config_without_prediction.clipping_predictor.enabled = false;
AgcManagerDirect manager_with_prediction(config_with_prediction,
new ::testing::NiceMock<MockAgc>());
AgcManagerDirect manager_without_prediction(
config_without_prediction, new ::testing::NiceMock<MockAgc>());
manager_with_prediction.Initialize();
manager_without_prediction.Initialize();
constexpr int kInitialLevel = 255;
constexpr float kClippingPeakRatio = 1.0f;
constexpr float kCloseToClippingPeakRatio = 0.99f;
constexpr float kZeroPeakRatio = 0.0f;
manager_with_prediction.set_stream_analog_level(kInitialLevel);
manager_without_prediction.set_stream_analog_level(kInitialLevel);
manager_with_prediction.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
manager_without_prediction.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_TRUE(manager_with_prediction.clipping_predictor_enabled());
EXPECT_FALSE(manager_without_prediction.clipping_predictor_enabled());
EXPECT_TRUE(manager_with_prediction.use_clipping_predictor_step());
EXPECT_EQ(manager_with_prediction.recommended_analog_level(), kInitialLevel);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel);
// Expect a change in the analog level when the prediction step is used.
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel);
// Expect no change during waiting.
CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel);
// Expect a change when the prediction step is used.
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - 2 * kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel);
// Expect no change when clipping is not detected or predicted.
CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - 2 * kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel);
// Expect a change for clipping frames.
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - 3 * kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel - kClippedLevelStep);
// Expect no change during waiting.
CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - 3 * kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel - kClippedLevelStep);
// Expect a change for clipping frames.
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
kInitialLevel - 4 * kClippedLevelStep);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel - 2 * kClippedLevelStep);
}
TEST_P(AgcManagerDirectParametrizedTest,
UnusedClippingPredictionsProduceEqualAnalogLevels) {
AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz,
kNumChannels, kSampleRateHz, kNumChannels);
AnalogAgcConfig config_with_prediction = GetAnalogAgcTestConfig();
config_with_prediction.clipping_predictor.enabled = true;
config_with_prediction.clipping_predictor.use_predicted_step = false;
AnalogAgcConfig config_without_prediction = GetAnalogAgcTestConfig();
config_without_prediction.clipping_predictor.enabled = false;
AgcManagerDirect manager_with_prediction(config_with_prediction,
new ::testing::NiceMock<MockAgc>());
AgcManagerDirect manager_without_prediction(
config_without_prediction, new ::testing::NiceMock<MockAgc>());
constexpr int kInitialLevel = 255;
constexpr float kClippingPeakRatio = 1.0f;
constexpr float kCloseToClippingPeakRatio = 0.99f;
constexpr float kZeroPeakRatio = 0.0f;
manager_with_prediction.Initialize();
manager_without_prediction.Initialize();
manager_with_prediction.set_stream_analog_level(kInitialLevel);
manager_without_prediction.set_stream_analog_level(kInitialLevel);
manager_with_prediction.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
manager_without_prediction.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevelDbfs));
EXPECT_TRUE(manager_with_prediction.clipping_predictor_enabled());
EXPECT_FALSE(manager_without_prediction.clipping_predictor_enabled());
EXPECT_FALSE(manager_with_prediction.use_clipping_predictor_step());
EXPECT_EQ(manager_with_prediction.recommended_analog_level(), kInitialLevel);
EXPECT_EQ(manager_without_prediction.recommended_analog_level(),
kInitialLevel);
// Expect no change in the analog level for non-clipping frames.
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
// Expect no change for non-clipping frames.
CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
// Expect no change for non-clipping frames.
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
// Expect no change when clipping is not detected or predicted.
CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
// Expect a change for clipping frames.
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
// Expect no change during waiting.
CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
// Expect a change for clipping frames.
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_with_prediction);
CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio,
manager_without_prediction);
EXPECT_EQ(manager_with_prediction.recommended_analog_level(),
manager_without_prediction.recommended_analog_level());
}
// Checks that passing an empty speech level and probability overrides to
// `Process()` has the same effect as passing no overrides.
TEST_P(AgcManagerDirectParametrizedTest, EmptyRmsErrorOverrideHasNoEffect) {
AgcManagerDirect manager_1(kNumChannels, GetAnalogAgcTestConfig());
AgcManagerDirect manager_2(kNumChannels, GetAnalogAgcTestConfig());
manager_1.Initialize();
manager_2.Initialize();
constexpr int kAnalogLevel = 50;
manager_1.set_stream_analog_level(kAnalogLevel);
manager_2.set_stream_analog_level(kAnalogLevel);
// Feed speech with low energy to trigger an upward adapation of the analog
// level.
constexpr int kNumFrames = 125;
constexpr int kGainDb = -20;
SpeechSamplesReader reader;
// Check the initial input volume.
ASSERT_EQ(manager_1.recommended_analog_level(), kAnalogLevel);
ASSERT_EQ(manager_2.recommended_analog_level(), kAnalogLevel);
reader.Feed(kNumFrames, kGainDb, std::nullopt, std::nullopt, manager_1);
reader.Feed(kNumFrames, kGainDb, manager_2);
// Check that the states are the same and adaptation occurs.
EXPECT_EQ(manager_1.recommended_analog_level(),
manager_2.recommended_analog_level());
ASSERT_GT(manager_1.recommended_analog_level(), kAnalogLevel);
EXPECT_EQ(manager_1.voice_probability(), manager_2.voice_probability());
EXPECT_EQ(manager_1.frames_since_clipped_, manager_2.frames_since_clipped_);
// Check that the states of the channel AGCs are the same.
EXPECT_EQ(manager_1.num_channels(), manager_2.num_channels());
for (int i = 0; i < manager_1.num_channels(); ++i) {
EXPECT_EQ(manager_1.channel_agcs_[i]->recommended_analog_level(),
manager_2.channel_agcs_[i]->recommended_analog_level());
EXPECT_EQ(manager_1.channel_agcs_[i]->voice_probability(),
manager_2.channel_agcs_[i]->voice_probability());
}
}
// Checks that passing a non-empty speech level and probability overrides to
// `Process()` has an effect.
TEST_P(AgcManagerDirectParametrizedTest, NonEmptyRmsErrorOverrideHasEffect) {
AgcManagerDirect manager_1(kNumChannels, GetAnalogAgcTestConfig());
AgcManagerDirect manager_2(kNumChannels, GetAnalogAgcTestConfig());
manager_1.Initialize();
manager_2.Initialize();
constexpr int kInputVolume = 128;
manager_1.set_stream_analog_level(kInputVolume);
manager_2.set_stream_analog_level(kInputVolume);
// Feed speech with low energy to trigger an upward adapation of the input
// volume.
constexpr int kNumFrames = 125;
constexpr int kGainDb = -20;
SpeechSamplesReader reader;
// Make sure that the feeding samples triggers an adaptation when no override
// is specified.
reader.Feed(kNumFrames, kGainDb, manager_1);
ASSERT_GT(manager_1.recommended_analog_level(), kInputVolume);
// Expect that feeding samples triggers an adaptation when the speech
// probability and speech level overrides are specified.
reader.Feed(kNumFrames, kGainDb,
/*speech_probability_override=*/kHighSpeechProbability,
/*speech_level_override=*/-45.0f, manager_2);
EXPECT_GT(manager_2.recommended_analog_level(), kInputVolume);
// The voice probability override does not affect the `voice_probability()`
// getter.
EXPECT_EQ(manager_1.voice_probability(), manager_2.voice_probability());
}
class AgcManagerDirectChannelSampleRateTest
: public ::testing::TestWithParam<std::tuple<int, int>> {
protected:
int GetNumChannels() const { return std::get<0>(GetParam()); }
int GetSampleRateHz() const { return std::get<1>(GetParam()); }
};
TEST_P(AgcManagerDirectChannelSampleRateTest, CheckIsAlive) {
const int num_channels = GetNumChannels();
const int sample_rate_hz = GetSampleRateHz();
constexpr AnalogAgcConfig kConfig{.enabled = true,
.clipping_predictor{.enabled = true}};
AgcManagerDirect manager(num_channels, kConfig);
manager.Initialize();
AudioBuffer buffer(sample_rate_hz, num_channels, sample_rate_hz, num_channels,
sample_rate_hz, num_channels);
constexpr int kStartupVolume = 100;
int applied_initial_volume = kStartupVolume;
// Trigger a downward adaptation with clipping.
WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipped_ratio=*/0.5f,
buffer);
const int initial_volume1 = applied_initial_volume;
for (int i = 0; i < 400; ++i) {
manager.set_stream_analog_level(applied_initial_volume);
manager.AnalyzePreProcess(buffer);
manager.Process(buffer, kLowSpeechProbability,
/*speech_level_dbfs=*/-20.0f);
applied_initial_volume = manager.recommended_analog_level();
}
ASSERT_LT(manager.recommended_analog_level(), initial_volume1);
// Fill in audio that does not clip.
WriteAudioBufferSamples(/*samples_value=*/1234.5f, /*clipped_ratio=*/0.0f,
buffer);
// Trigger an upward adaptation.
const int initial_volume2 = manager.recommended_analog_level();
for (int i = 0; i < kConfig.clipped_wait_frames; ++i) {
manager.set_stream_analog_level(applied_initial_volume);
manager.AnalyzePreProcess(buffer);
manager.Process(buffer, kHighSpeechProbability,
/*speech_level_dbfs=*/-65.0f);
applied_initial_volume = manager.recommended_analog_level();
}
EXPECT_GT(manager.recommended_analog_level(), initial_volume2);
// Trigger a downward adaptation.
const int initial_volume = manager.recommended_analog_level();
for (int i = 0; i < 100; ++i) {
manager.set_stream_analog_level(applied_initial_volume);
manager.AnalyzePreProcess(buffer);
manager.Process(buffer, kHighSpeechProbability,
/*speech_level_dbfs=*/-5.0f);
applied_initial_volume = manager.recommended_analog_level();
}
EXPECT_LT(manager.recommended_analog_level(), initial_volume);
}
INSTANTIATE_TEST_SUITE_P(
,
AgcManagerDirectChannelSampleRateTest,
::testing::Combine(::testing::Values(1, 2, 3, 6),
::testing::Values(8000, 16000, 32000, 48000)));
} // namespace webrtc