webrtc_m130/modules/audio_processing/agc2/input_volume_controller_unittest.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2148 lines
96 KiB
C++
Raw Normal View History

Copy AgcManagerDirect files to agc2 and rename the classes Copy AgcManagerDirect files from agc to agc2. Rename the newly created files and classes ahead of refactoring. Add a build target. This change is done to enable creating a class InputVolumeController based on AgcManagerDirect. The added temporary dependency on files in agc will be removed in https://webrtc-review.googlesource.com/c/src/+/278625. The exact copy of the files happened in the 1st patchset and it has been verified as follows: Checksum check: ``` $ git checkout main && git pull # Go back to the tree state before [1] landed $ git new-branch tmp $ git reset --hard 2235776597e2f47ec353ac911428eb9a54d64a10 $ cd modules/audio_processing/agc/ $ md5 agc_manager_direct* MD5 (agc_manager_direct.cc) = e661481a85f72596cae4599b62907f5b MD5 (agc_manager_direct.h) = bf68280e2d0f689b4ebcd665b5db6052 MD5 (agc_manager_direct_unittest.cc) = 6bf0bf45ff5e940b1a3bb37154f09269 ``` Patchset 1 (see [2]) ``` $ cd modules/audio_processing/agc2/ $ md5 input_volume_controlle* MD5 (input_volume_controller.cc) = e661481a85f72596cae4599b62907f5b MD5 (input_volume_controller.h) = bf68280e2d0f689b4ebcd665b5db6052 MD5 (input_volume_controller_unittest.cc) = 6bf0bf45ff5e940b1a3bb37154f09269 ``` [1] https://webrtc-review.googlesource.com/c/src/+/278781 [2] https://webrtc-review.googlesource.com/c/src/+/278624/1 Bug: webrtc:7494 Change-Id: I7804da899d18adf556b089c76a567ce27c299a62 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/278624 Reviewed-by: Alessio Bazzica <alessiob@webrtc.org> Reviewed-by: Artem Titov <titovartem@webrtc.org> Commit-Queue: Hanna Silen <silen@webrtc.org> Cr-Commit-Position: refs/heads/main@{#38512}
2022-10-18 16:57:36 +02:00
/*
* 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/agc2/input_volume_controller.h"
#include <algorithm>
#include <fstream>
#include <limits>
#include <string>
#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 kHighSpeechProbability = 0.7f;
constexpr float kSpeechLevel = -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<InputVolumeController> CreateInputVolumeController(
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<InputVolumeController>(/*num_capture_channels=*/1,
config);
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Delete this helper, use
// `InputVolumeControllerTestHelper::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,
InputVolumeController& 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(
absl::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
// `InputVolumeControllerTestHelper::CallAgcSequence()` instead.
void CallPreProcessAndProcess(int num_calls,
const AudioBuffer& audio_buffer,
absl::optional<float> speech_probability_override,
absl::optional<float> speech_level_override,
InputVolumeController& 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
// `InputVolumeController`.
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, InputVolumeController& 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,
absl::optional<float> speech_probability_override,
absl::optional<float> speech_level_override,
InputVolumeController& 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 `InputVolumeController` instance with an
// injected `Agc` mock, an `AudioBuffer` instance and `CallAgcSequence()`, a
// helper method that runs the `InputVolumeController` instance on the
// `AudioBuffer` one by sticking to the API contract.
class InputVolumeControllerTestHelper {
public:
// Ctor. Initializes `audio_buffer` with zeros.
InputVolumeControllerTestHelper()
: 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 `InputVolumeController` 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,
absl::optional<float> speech_probability_override,
absl::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);
absl::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,
absl::optional<float> speech_probability_override,
absl::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);
absl::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;
InputVolumeController manager;
MockGainControl mock_gain_control;
};
class InputVolumeControllerParametrizedTest
: public ::testing::TestWithParam<std::tuple<absl::optional<int>, bool>> {
protected:
InputVolumeControllerParametrizedTest()
: 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()); }
absl::optional<float> GetOverrideOrEmpty(float value) const {
return IsRmsErrorOverridden() ? absl::optional<float>(value)
: absl::nullopt;
}
private:
test::ScopedFieldTrials field_trials_;
};
INSTANTIATE_TEST_SUITE_P(
,
InputVolumeControllerParametrizedTest,
::testing::Combine(testing::Values(absl::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(InputVolumeControllerParametrizedTest,
DisabledAnalogAgcDoesNotAdaptDownwards) {
InputVolumeController manager_no_analog_agc(kNumChannels,
GetDisabledAnalogAgcConfig());
manager_no_analog_agc.Initialize();
InputVolumeController 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(InputVolumeControllerParametrizedTest,
DisabledAnalogAgcDoesNotAdaptUpwards) {
InputVolumeController manager_no_analog_agc(kNumChannels,
GetDisabledAnalogAgcConfig());
manager_no_analog_agc.Initialize();
InputVolumeController 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(InputVolumeControllerParametrizedTest,
StartupMinVolumeConfigurationIsRespected) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
EXPECT_EQ(kInitialInputVolume, helper.manager.recommended_analog_level());
}
TEST_P(InputVolumeControllerParametrizedTest, MicVolumeResponseToRmsError) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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(InputVolumeControllerParametrizedTest, MicVolumeIsLimited) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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(InputVolumeControllerParametrizedTest, CompressorStepsTowardsTarget) {
constexpr absl::optional<float> kNoOverride = absl::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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
// absl::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(InputVolumeControllerParametrizedTest, CompressorErrorIsDeemphasized) {
constexpr absl::optional<float> kNoOverride = absl::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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
// absl::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(InputVolumeControllerParametrizedTest, CompressorReachesMaximum) {
constexpr absl::optional<float> kNoOverride = absl::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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
// absl::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(InputVolumeControllerParametrizedTest, CompressorReachesMinimum) {
constexpr absl::optional<float> kNoOverride = absl::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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
// absl::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(InputVolumeControllerParametrizedTest, NoActionWhileMuted) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
helper.manager.HandleCaptureOutputUsedChange(false);
helper.manager.Process(helper.audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
absl::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(InputVolumeControllerParametrizedTest,
UnmutingChecksVolumeWithoutRaising) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(kSpeechLevel));
EXPECT_EQ(127, helper.manager.recommended_analog_level());
}
TEST_P(InputVolumeControllerParametrizedTest, UnmutingRaisesTooLowVolume) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(kSpeechLevel));
EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level());
}
TEST_P(InputVolumeControllerParametrizedTest,
ManualLevelChangeResultsInNoSetMicCall) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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(InputVolumeControllerParametrizedTest,
RecoveryAfterManualLevelChangeFromMax) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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(InputVolumeControllerParametrizedTest,
RecoveryAfterManualLevelChangeBelowMinWithoutMiMicLevelnOverride) {
if (IsMinMicLevelOverridden()) {
GTEST_SKIP() << "Skipped. Min mic level overridden.";
}
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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(InputVolumeControllerParametrizedTest,
RecoveryAfterManualLevelChangeBelowMin) {
if (!IsMinMicLevelOverridden()) {
GTEST_SKIP() << "Skipped. Min mic level not overridden.";
}
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume, speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
// 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(InputVolumeControllerParametrizedTest, NoClippingHasNoImpact) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
helper.CallPreProc(/*num_calls=*/100, /*clipped_ratio=*/0);
EXPECT_EQ(128, helper.manager.recommended_analog_level());
}
TEST_P(InputVolumeControllerParametrizedTest,
ClippingUnderThresholdHasNoImpact) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.099);
EXPECT_EQ(128, helper.manager.recommended_analog_level());
}
TEST_P(InputVolumeControllerParametrizedTest, ClippingLowersVolume) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
WaitingPeriodBetweenClippingChecks) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest, ClippingLoweringIsLimited) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/180,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
ClippingMaxIsRespectedWhenEqualToLevel) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
ClippingMaxIsRespectedWhenHigherThanLevel) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/200,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
MaxCompressionIsIncreasedAfterClipping) {
constexpr absl::optional<float> kNoOverride = absl::nullopt;
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/210,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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
// absl::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(InputVolumeControllerParametrizedTest, UserCanRaiseVolumeAfterClipping) {
const auto speech_probability_override =
GetOverrideOrEmpty(kHighSpeechProbability);
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/225,
speech_probability_override,
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
ClippingDoesNotPullLowVolumeBackUp) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/80,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest, TakesNoActionOnZeroMicVolume) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(kInitialInputVolume,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest, ClippingDetectionLowersVolume) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
DisabledClippingPredictorDoesNotLowerVolume) {
InputVolumeControllerTestHelper helper;
helper.CallAgcSequence(/*applied_input_volume=*/255,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest, 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<InputVolumeController>(kNumChannels, config);
manager->Initialize();
manager->SetupDigitalGainControl(mock_gain_control);
}
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentDefault) {
std::unique_ptr<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(),
kInitialInputVolume);
}
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentDisabled) {
for (const std::string& field_trial_suffix : {"", "_20220210"}) {
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrial("Disabled" + field_trial_suffix));
std::unique_ptr<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(),
kInitialInputVolume);
}
}
// Checks that a field-trial parameter outside of the valid range [0,255] is
// ignored.
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentOutOfRangeAbove) {
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrial("Enabled-256"));
std::unique_ptr<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(),
kInitialInputVolume);
}
// Checks that a field-trial parameter outside of the valid range [0,255] is
// ignored.
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentOutOfRangeBelow) {
test::ScopedFieldTrials field_trial(
GetAgcMinMicLevelExperimentFieldTrial("Enabled--1"));
std::unique_ptr<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(),
kInitialInputVolume);
}
// 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(InputVolumeControllerTest, 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<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevelOverride);
EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(),
kInitialInputVolume);
}
}
// 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(InputVolumeControllerTest,
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<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
manager->Initialize();
manager->set_stream_analog_level(kInitialInputVolume);
return manager;
};
std::unique_ptr<InputVolumeController> manager = factory();
std::unique_ptr<InputVolumeController> 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=*/absl::nullopt,
/*speech_level_override=*/absl::nullopt, *manager);
CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/absl::nullopt,
/*speech_level_override=*/absl::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(InputVolumeControllerTest,
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<InputVolumeController> manager =
CreateInputVolumeController(kInitialInputVolume, kClippedLevelStep,
kClippedRatioThreshold, kClippedWaitFrames);
manager->Initialize();
manager->set_stream_analog_level(kInitialInputVolume);
return manager;
};
std::unique_ptr<InputVolumeController> manager = factory();
std::unique_ptr<InputVolumeController> 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=*/absl::optional<float>(0.7f),
/*speech_probability_level=*/absl::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(InputVolumeControllerTest,
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<InputVolumeController>(
/*num_capture_channels=*/1, config);
controller->Initialize();
controller->set_stream_analog_level(kInitialInputVolume);
return controller;
};
std::unique_ptr<InputVolumeController> manager = factory();
std::unique_ptr<InputVolumeController> 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=*/absl::nullopt,
/*speech_level_override=*/absl::nullopt, *manager);
CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/absl::nullopt,
/*speech_level_override=*/absl::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(InputVolumeControllerTest,
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<InputVolumeController>(
/*num_capture_channels=*/1, config);
controller->Initialize();
controller->set_stream_analog_level(kInitialInputVolume);
return controller;
};
std::unique_ptr<InputVolumeController> manager = factory();
std::unique_ptr<InputVolumeController> 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=*/absl::optional<float>(0.7f),
/*speech_level_override=*/absl::optional<float>(-18.0f), *manager);
CallPreProcessAndProcess(
/*num_calls=*/400, audio_buffer,
/*speech_probability_override=*/absl::optional<float>(0.7f),
/*speech_level_override=*/absl::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(InputVolumeControllerParametrizedTest, ClippingParametersVerified) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
std::unique_ptr<InputVolumeController> manager =
CreateInputVolumeController(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<InputVolumeController> manager_custom =
CreateInputVolumeController(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(InputVolumeControllerParametrizedTest,
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<InputVolumeController> manager = CreateInputVolumeController(
kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames, config);
manager->Initialize();
EXPECT_FALSE(manager->clipping_predictor_enabled());
EXPECT_FALSE(manager->use_clipping_predictor_step());
}
TEST_P(InputVolumeControllerParametrizedTest,
ClippingPredictorDisabledByDefault) {
if (IsRmsErrorOverridden()) {
GTEST_SKIP() << "Skipped. RMS error override does not affect the test.";
}
constexpr ClippingPredictorConfig kDefaultConfig;
EXPECT_FALSE(kDefaultConfig.enabled);
}
TEST_P(InputVolumeControllerParametrizedTest,
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<InputVolumeController> manager = CreateInputVolumeController(
kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames, config);
manager->Initialize();
EXPECT_TRUE(manager->clipping_predictor_enabled());
EXPECT_TRUE(manager->use_clipping_predictor_step());
}
TEST_P(InputVolumeControllerParametrizedTest,
DisableClippingPredictorDoesNotLowerVolume) {
AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz,
kNumChannels, kSampleRateHz, kNumChannels);
AnalogAgcConfig config = GetAnalogAgcTestConfig();
config.clipping_predictor.enabled = false;
InputVolumeController 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(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
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;
InputVolumeController manager_with_prediction(
config_with_prediction, new ::testing::NiceMock<MockAgc>());
InputVolumeController 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(kSpeechLevel));
manager_without_prediction.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
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;
InputVolumeController manager_with_prediction(
config_with_prediction, new ::testing::NiceMock<MockAgc>());
InputVolumeController 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(kSpeechLevel));
manager_without_prediction.Process(audio_buffer,
GetOverrideOrEmpty(kHighSpeechProbability),
GetOverrideOrEmpty(kSpeechLevel));
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(InputVolumeControllerParametrizedTest,
EmptyRmsErrorOverrideHasNoEffect) {
InputVolumeController manager_1(kNumChannels, GetAnalogAgcTestConfig());
InputVolumeController 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, absl::nullopt, absl::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(InputVolumeControllerParametrizedTest,
NonEmptyRmsErrorOverrideHasEffect) {
InputVolumeController manager_1(kNumChannels, GetAnalogAgcTestConfig());
InputVolumeController 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,
absl::optional<float>(kHighSpeechProbability),
absl::optional<float>(kSpeechLevel), manager_1);
reader.Feed(kNumFrames, kGainDb, manager_2);
// Check that different adaptation occurs. The voice probability estimate from
// AGC is not affected.
ASSERT_GT(manager_1.recommended_analog_level(), kAnalogLevel);
ASSERT_GT(manager_2.recommended_analog_level(), kAnalogLevel);
ASSERT_NE(manager_1.recommended_analog_level(),
manager_2.recommended_analog_level());
EXPECT_EQ(manager_1.voice_probability(), manager_2.voice_probability());
EXPECT_EQ(manager_1.num_channels(), manager_2.num_channels());
for (int i = 0; i < manager_1.num_channels(); ++i) {
EXPECT_NE(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());
}
}
} // namespace webrtc