webrtc/modules/audio_processing/agc2/input_volume_controller_unittest.cc
Alessio Bazzica 6b7834c14f Add generic input volume controller test for both AGC1 and AGC2
Make sure that the input volume controller implementations exhibit
the adaptive behavior regardless of the sample rate and the number
of channels. The newly added tests check that:
- a downward adjustment takes place with clipping input
- an upward adjustment takes place with a too low speech level
- a downward adjustment takes place with a too high speech level

Bug: webrtc:14761
Change-Id: I1795e74c5f219e15107e928ebaca2bfa75214526
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/287301
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Hanna Silen <silen@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38930}
2022-12-20 14:41:31 +00:00

1857 lines
79 KiB
C++

/*
* 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 <vector>
#include "rtc_base/numerics/safe_minmax.h"
#include "rtc_base/strings/string_builder.h"
#include "system_wrappers/include/metrics.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 = 20;
constexpr int kClippedLevelStep = 15;
constexpr float kClippedRatioThreshold = 0.1f;
constexpr int kClippedWaitFrames = 300;
constexpr float kHighSpeechProbability = 0.7f;
constexpr float kLowSpeechProbability = 0.1f;
constexpr float kSpeechLevel = -25.0f;
constexpr float kSpeechProbabilityThreshold = 0.5f;
constexpr float kSpeechRatioThreshold = 0.8f;
constexpr float kMinSample = std::numeric_limits<int16_t>::min();
constexpr float kMaxSample = std::numeric_limits<int16_t>::max();
using ClippingPredictorConfig = AudioProcessing::Config::GainController1::
AnalogGainController::ClippingPredictor;
using InputVolumeControllerConfig = InputVolumeController::Config;
constexpr ClippingPredictorConfig kDefaultClippingPredictorConfig{};
std::unique_ptr<InputVolumeController> CreateInputVolumeController(
int clipped_level_step = kClippedLevelStep,
float clipped_ratio_threshold = kClippedRatioThreshold,
int clipped_wait_frames = kClippedWaitFrames,
bool enable_clipping_predictor = false,
int update_input_volume_wait_frames = 0) {
InputVolumeControllerConfig config{
.min_input_volume = kMinMicLevel,
.clipped_level_min = kClippedMin,
.clipped_level_step = clipped_level_step,
.clipped_ratio_threshold = clipped_ratio_threshold,
.clipped_wait_frames = clipped_wait_frames,
.enable_clipping_predictor = enable_clipping_predictor,
.target_range_max_dbfs = -18,
.target_range_min_dbfs = -30,
.update_input_volume_wait_frames = update_input_volume_wait_frames,
.speech_probability_threshold = kSpeechProbabilityThreshold,
.speech_ratio_threshold = kSpeechRatioThreshold,
};
return std::make_unique<InputVolumeController>(/*num_capture_channels=*/1,
config);
}
// (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;
}
}
}
// (Over)writes samples in `audio_buffer`. Alternates samples `samples_value`
// and zero.
void WriteAlternatingAudioBufferSamples(float samples_value,
AudioBuffer& audio_buffer) {
RTC_DCHECK_GE(samples_value, kMinSample);
RTC_DCHECK_LE(samples_value, kMaxSample);
const int num_channels = audio_buffer.num_channels();
const int num_frames = audio_buffer.num_frames();
for (int ch = 0; ch < num_channels; ++ch) {
for (int i = 0; i < num_frames; i += 2) {
audio_buffer.channels()[ch][i] = samples_value;
audio_buffer.channels()[ch][i + 1] = 0.0f;
}
}
}
// 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 `controller` by calling
// `AnalyzeInputAudio()` and `RecommendInputVolume()` 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` and `speech_level_dbfs`
// are passed to `RecommendInputVolume()`.
int Feed(int num_frames,
int applied_input_volume,
int gain_db,
float speech_probability,
absl::optional<float> speech_level_dbfs,
InputVolumeController& controller) {
RTC_DCHECK(controller.capture_output_used());
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);
});
controller.AnalyzeInputAudio(applied_input_volume, audio_buffer_);
const auto recommended_input_volume = controller.RecommendInputVolume(
speech_probability, speech_level_dbfs);
// Expect no errors: Applied volume set for every frame;
// `RecommendInputVolume()` returns a non-empty value.
EXPECT_TRUE(recommended_input_volume.has_value());
applied_input_volume = *recommended_input_volume;
}
return applied_input_volume;
}
private:
std::ifstream is_;
AudioBuffer audio_buffer_;
std::vector<int16_t> buffer_;
const std::streamsize buffer_num_bytes_;
};
// Runs the MonoInputVolumeControl processing sequence following the API
// contract. Returns the updated recommended input volume.
float UpdateRecommendedInputVolume(MonoInputVolumeController& mono_controller,
int applied_input_volume,
float speech_probability,
absl::optional<float> rms_error_dbfs) {
mono_controller.set_stream_analog_level(applied_input_volume);
EXPECT_EQ(mono_controller.recommended_analog_level(), applied_input_volume);
mono_controller.Process(rms_error_dbfs, speech_probability);
return mono_controller.recommended_analog_level();
}
} // namespace
// TODO(bugs.webrtc.org/12874): Use constexpr struct with designated
// initializers once fixed.
constexpr InputVolumeControllerConfig GetInputVolumeControllerTestConfig() {
InputVolumeControllerConfig config{
.clipped_level_min = kClippedMin,
.clipped_level_step = kClippedLevelStep,
.clipped_ratio_threshold = kClippedRatioThreshold,
.clipped_wait_frames = kClippedWaitFrames,
.enable_clipping_predictor = kDefaultClippingPredictorConfig.enabled,
.target_range_max_dbfs = -18,
.target_range_min_dbfs = -30,
.update_input_volume_wait_frames = 0,
.speech_probability_threshold = 0.5f,
.speech_ratio_threshold = 1.0f,
};
return config;
}
// Helper class that provides an `InputVolumeController` instance with 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.
// TODO(bugs.webrtc.org/7494): Remove the default argument.
InputVolumeControllerTestHelper(const InputVolumeController::Config& config =
GetInputVolumeControllerTestConfig())
: audio_buffer(kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels),
controller(/*num_capture_channels=*/1, config) {
controller.Initialize();
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 `AnalyzeInputAudio()` and
// `RecommendInputVolume()`;
// Returns the recommended input volume.
absl::optional<int> CallAgcSequence(int applied_input_volume,
float speech_probability,
absl::optional<float> speech_level_dbfs,
int num_calls = 1) {
RTC_DCHECK_GE(num_calls, 1);
absl::optional<int> volume = applied_input_volume;
for (int i = 0; i < num_calls; ++i) {
// Repeat the initial volume if `RecommendInputVolume()` doesn't return a
// value.
controller.AnalyzeInputAudio(volume.value_or(applied_input_volume),
audio_buffer);
volume = controller.RecommendInputVolume(speech_probability,
speech_level_dbfs);
// Allow deviation from the API contract: `RecommendInputVolume()` doesn't
// return a recommended input volume.
if (volume.has_value()) {
EXPECT_EQ(*volume, controller.recommended_input_volume());
}
}
return volume;
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use
// `CallAgcSequence()`.
int CallRecommendInputVolume(int num_calls,
int initial_volume,
float speech_probability,
absl::optional<float> speech_level_dbfs) {
RTC_DCHECK(controller.capture_output_used());
// Create non-clipping audio for `AnalyzeInputAudio()`.
WriteAlternatingAudioBufferSamples(0.1f * kMaxSample, audio_buffer);
int volume = initial_volume;
for (int i = 0; i < num_calls; ++i) {
controller.AnalyzeInputAudio(volume, audio_buffer);
const auto recommended_input_volume = controller.RecommendInputVolume(
speech_probability, speech_level_dbfs);
// Expect no errors: Applied volume set for every frame;
// `RecommendInputVolume()` returns a non-empty value.
EXPECT_TRUE(recommended_input_volume.has_value());
volume = *recommended_input_volume;
}
return volume;
}
// Deprecated.
// TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use
// `CallAgcSequence()`.
void CallAnalyzeInputAudio(int num_calls, float clipped_ratio) {
RTC_DCHECK(controller.capture_output_used());
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) {
controller.AnalyzeInputAudio(controller.recommended_input_volume(),
audio_buffer);
}
}
AudioBuffer audio_buffer;
InputVolumeController controller;
};
class InputVolumeControllerChannelSampleRateTest
: 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(InputVolumeControllerChannelSampleRateTest, CheckIsAlive) {
const int num_channels = GetNumChannels();
const int sample_rate_hz = GetSampleRateHz();
constexpr InputVolumeController::Config kConfig{.enable_clipping_predictor =
true};
InputVolumeController controller(num_channels, kConfig);
controller.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.
constexpr int kLevelWithinTargetDbfs =
(kConfig.target_range_min_dbfs + kConfig.target_range_max_dbfs) / 2;
WriteAlternatingAudioBufferSamples(/*samples_value=*/kMaxSample, buffer);
const int initial_volume1 = applied_initial_volume;
for (int i = 0; i < 400; ++i) {
controller.AnalyzeInputAudio(applied_initial_volume, buffer);
auto recommended_input_volume = controller.RecommendInputVolume(
kLowSpeechProbability,
/*speech_level_dbfs=*/kLevelWithinTargetDbfs);
ASSERT_TRUE(recommended_input_volume.has_value());
applied_initial_volume = *recommended_input_volume;
}
ASSERT_LT(controller.recommended_input_volume(), initial_volume1);
// Fill in audio that does not clip.
WriteAlternatingAudioBufferSamples(/*samples_value=*/1234.5f, buffer);
// Trigger an upward adaptation.
const int initial_volume2 = controller.recommended_input_volume();
for (int i = 0; i < kConfig.clipped_wait_frames; ++i) {
controller.AnalyzeInputAudio(applied_initial_volume, buffer);
auto recommended_input_volume = controller.RecommendInputVolume(
kHighSpeechProbability,
/*speech_level_dbfs=*/kConfig.target_range_min_dbfs - 5);
ASSERT_TRUE(recommended_input_volume.has_value());
applied_initial_volume = *recommended_input_volume;
}
EXPECT_GT(controller.recommended_input_volume(), initial_volume2);
// Trigger a downward adaptation.
const int initial_volume = controller.recommended_input_volume();
for (int i = 0; i < kConfig.update_input_volume_wait_frames; ++i) {
controller.AnalyzeInputAudio(applied_initial_volume, buffer);
auto recommended_input_volume = controller.RecommendInputVolume(
kHighSpeechProbability,
/*speech_level_dbfs=*/kConfig.target_range_max_dbfs + 5);
ASSERT_TRUE(recommended_input_volume.has_value());
applied_initial_volume = *recommended_input_volume;
}
EXPECT_LT(controller.recommended_input_volume(), initial_volume);
}
INSTANTIATE_TEST_SUITE_P(
,
InputVolumeControllerChannelSampleRateTest,
::testing::Combine(::testing::Values(1, 2, 3, 6),
::testing::Values(8000, 16000, 32000, 48000)));
class InputVolumeControllerParametrizedTest
: public ::testing::TestWithParam<int> {};
TEST_P(InputVolumeControllerParametrizedTest,
StartupMinVolumeConfigurationRespectedWhenAppliedInputVolumeAboveMin) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
EXPECT_EQ(*helper.CallAgcSequence(/*applied_input_volume=*/128,
/*speech_probability=*/0.9f,
/*speech_level_dbfs=*/-80),
128);
}
TEST_P(
InputVolumeControllerParametrizedTest,
StartupMinVolumeConfigurationRespectedWhenAppliedInputVolumeMaybeBelowMin) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
EXPECT_GE(*helper.CallAgcSequence(/*applied_input_volume=*/10,
/*speech_probability=*/0.9f,
/*speech_level_dbfs=*/-80),
10);
}
TEST_P(InputVolumeControllerParametrizedTest,
StartupMinVolumeRespectedWhenAppliedVolumeNonZero) {
const int kMinInputVolume = GetParam();
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = kMinInputVolume,
.target_range_min_dbfs = -30,
.update_input_volume_wait_frames = 1,
.speech_probability_threshold = 0.5f,
.speech_ratio_threshold = 0.5f});
// Volume change possible; speech level below the digital gain window.
int volume = *helper.CallAgcSequence(/*applied_input_volume=*/1,
/*speech_probability=*/0.9f,
/*speech_level_dbfs=*/-80);
EXPECT_EQ(volume, kMinInputVolume);
}
TEST_P(InputVolumeControllerParametrizedTest,
MinVolumeRepeatedlyRespectedWhenAppliedVolumeNonZero) {
const int kMinInputVolume = GetParam();
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = kMinInputVolume,
.target_range_min_dbfs = -30,
.update_input_volume_wait_frames = 1,
.speech_probability_threshold = 0.5f,
.speech_ratio_threshold = 0.5f});
// Volume change possible; speech level below the digital gain window.
for (int i = 0; i < 100; ++i) {
const int volume = *helper.CallAgcSequence(/*applied_input_volume=*/1,
/*speech_probability=*/0.9f,
/*speech_level_dbfs=*/-80);
EXPECT_GE(volume, kMinInputVolume);
}
}
TEST_P(InputVolumeControllerParametrizedTest,
StartupMinVolumeRespectedOnceWhenAppliedVolumeZero) {
const int kMinInputVolume = GetParam();
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = kMinInputVolume,
.target_range_min_dbfs = -30,
.update_input_volume_wait_frames = 1,
.speech_probability_threshold = 0.5f,
.speech_ratio_threshold = 0.5f});
int volume = *helper.CallAgcSequence(/*applied_input_volume=*/0,
/*speech_probability=*/0.9f,
/*speech_level_dbfs=*/-80);
EXPECT_EQ(volume, kMinInputVolume);
// No change of volume regardless of a speech level below the digital gain
// window; applied volume is zero.
volume = *helper.CallAgcSequence(/*applied_input_volume=*/0,
/*speech_probability=*/0.9f,
/*speech_level_dbfs=*/-80);
EXPECT_EQ(volume, 0);
}
TEST_P(InputVolumeControllerParametrizedTest, MicVolumeResponseToRmsError) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
int volume = *helper.CallAgcSequence(kInitialInputVolume,
kHighSpeechProbability, kSpeechLevel);
// Inside the digital gain's window; no change of volume.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -23.0f);
// Inside the digital gain's window; no change of volume.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -28.0f);
// Above the digital gain's window; volume should be increased.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -29.0f);
EXPECT_EQ(volume, 128);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -38.0f);
EXPECT_EQ(volume, 156);
// Inside the digital gain's window; no change of volume.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -23.0f);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -18.0f);
// Below the digial gain's window; volume should be decreased.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -17.0f);
EXPECT_EQ(volume, 155);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -17.0f);
EXPECT_EQ(volume, 151);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -9.0f);
EXPECT_EQ(volume, 119);
}
TEST_P(InputVolumeControllerParametrizedTest, MicVolumeIsLimited) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
const int min_input_volume = GetParam();
config.min_input_volume = min_input_volume;
InputVolumeControllerTestHelper helper(config);
int volume = *helper.CallAgcSequence(kInitialInputVolume,
kHighSpeechProbability, kSpeechLevel);
// Maximum upwards change is limited.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 183);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 243);
// Won't go higher than the maximum.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 255);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -17.0f);
EXPECT_EQ(volume, 254);
// Maximum downwards change is limited.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, 194);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, 137);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, 88);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, 54);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, 33);
// Won't go lower than the minimum.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, std::max(18, min_input_volume));
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, 22.0f);
EXPECT_EQ(volume, std::max(12, min_input_volume));
}
TEST_P(InputVolumeControllerParametrizedTest, NoActionWhileMuted) {
InputVolumeControllerTestHelper helper_1(
/*config=*/{.min_input_volume = GetParam()});
InputVolumeControllerTestHelper helper_2(
/*config=*/{.min_input_volume = GetParam()});
int volume_1 = *helper_1.CallAgcSequence(/*applied_input_volume=*/255,
kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/1);
int volume_2 = *helper_2.CallAgcSequence(/*applied_input_volume=*/255,
kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/1);
EXPECT_EQ(volume_1, 255);
EXPECT_EQ(volume_2, 255);
helper_2.controller.HandleCaptureOutputUsedChange(false);
WriteAlternatingAudioBufferSamples(kMaxSample, helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kMaxSample, helper_2.audio_buffer);
volume_1 =
*helper_1.CallAgcSequence(volume_1, kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/1);
volume_2 =
*helper_2.CallAgcSequence(volume_2, kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/1);
EXPECT_LT(volume_1, 255);
EXPECT_EQ(volume_2, 255);
}
TEST_P(InputVolumeControllerParametrizedTest,
UnmutingChecksVolumeWithoutRaising) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(kInitialInputVolume, kHighSpeechProbability,
kSpeechLevel);
helper.controller.HandleCaptureOutputUsedChange(false);
helper.controller.HandleCaptureOutputUsedChange(true);
constexpr int kInputVolume = 127;
// SetMicVolume should not be called.
EXPECT_EQ(
helper.CallRecommendInputVolume(/*num_calls=*/1, kInputVolume,
kHighSpeechProbability, kSpeechLevel),
kInputVolume);
}
TEST_P(InputVolumeControllerParametrizedTest, UnmutingRaisesTooLowVolume) {
const int min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = min_input_volume});
helper.CallAgcSequence(kInitialInputVolume, kHighSpeechProbability,
kSpeechLevel);
helper.controller.HandleCaptureOutputUsedChange(false);
helper.controller.HandleCaptureOutputUsedChange(true);
constexpr int kInputVolume = 11;
EXPECT_EQ(
helper.CallRecommendInputVolume(/*num_calls=*/1, kInputVolume,
kHighSpeechProbability, kSpeechLevel),
min_input_volume);
}
TEST_P(InputVolumeControllerParametrizedTest,
ManualLevelChangeResultsInNoSetMicCall) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
int volume = *helper.CallAgcSequence(kInitialInputVolume,
kHighSpeechProbability, kSpeechLevel);
// GetMicVolume returns a value outside of the quantization slack, indicating
// a manual volume change.
ASSERT_NE(volume, 154);
volume = helper.CallRecommendInputVolume(
/*num_calls=*/1, /*initial_volume=*/154, kHighSpeechProbability, -29.0f);
EXPECT_EQ(volume, 154);
// Do the same thing, except downwards now.
volume = helper.CallRecommendInputVolume(
/*num_calls=*/1, /*initial_volume=*/100, kHighSpeechProbability, -17.0f);
EXPECT_EQ(volume, 100);
// And finally verify the AGC continues working without a manual change.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -17.0f);
EXPECT_EQ(volume, 99);
}
TEST_P(InputVolumeControllerParametrizedTest,
RecoveryAfterManualLevelChangeFromMax) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
int volume = *helper.CallAgcSequence(kInitialInputVolume,
kHighSpeechProbability, kSpeechLevel);
// Force the mic up to max volume. Takes a few steps due to the residual
// gain limitation.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 183);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 243);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 255);
// Manual change does not result in SetMicVolume call.
volume = helper.CallRecommendInputVolume(
/*num_calls=*/1, /*initial_volume=*/50, kHighSpeechProbability, -17.0f);
EXPECT_EQ(helper.controller.recommended_input_volume(), 50);
// Continues working as usual afterwards.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -38.0f);
EXPECT_EQ(volume, 65);
}
// Checks that the minimum input volume is enforced during the upward adjustment
// of the input volume.
TEST_P(InputVolumeControllerParametrizedTest,
EnforceMinInputVolumeDuringUpwardsAdjustment) {
const int min_input_volume = GetParam();
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = min_input_volume;
InputVolumeControllerTestHelper helper(config);
int volume = *helper.CallAgcSequence(kInitialInputVolume,
kHighSpeechProbability, kSpeechLevel);
// Manual change below min, but strictly positive, otherwise no action will be
// taken.
volume = helper.CallRecommendInputVolume(
/*num_calls=*/1, /*initial_volume=*/1, kHighSpeechProbability, -17.0f);
// Trigger an upward adjustment of the input volume.
EXPECT_EQ(volume, min_input_volume);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -29.0f);
EXPECT_EQ(volume, min_input_volume);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -30.0f);
EXPECT_EQ(volume, min_input_volume);
// After a number of consistently low speech level observations, the input
// volume is eventually raised above the minimum.
volume = helper.CallRecommendInputVolume(/*num_calls=*/10, volume,
kHighSpeechProbability, -38.0f);
EXPECT_GT(volume, min_input_volume);
}
// 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) {
const int min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = min_input_volume});
int volume = *helper.CallAgcSequence(kInitialInputVolume,
kHighSpeechProbability, kSpeechLevel);
// Manual change below min, but strictly positive, otherwise
// AGC won't take any action.
volume = helper.CallRecommendInputVolume(
/*num_calls=*/1, /*initial_volume=*/1, kHighSpeechProbability, -17.0f);
EXPECT_EQ(volume, min_input_volume);
}
TEST_P(InputVolumeControllerParametrizedTest, NoClippingHasNoImpact) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(kInitialInputVolume, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/100, /*clipped_ratio=*/0);
EXPECT_EQ(helper.controller.recommended_input_volume(), 128);
}
TEST_P(InputVolumeControllerParametrizedTest,
ClippingUnderThresholdHasNoImpact) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(kInitialInputVolume, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1, /*clipped_ratio=*/0.099);
EXPECT_EQ(helper.controller.recommended_input_volume(), 128);
}
TEST_P(InputVolumeControllerParametrizedTest, ClippingLowersVolume) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(/*applied_input_volume=*/255, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1, /*clipped_ratio=*/0.2);
EXPECT_EQ(helper.controller.recommended_input_volume(), 240);
}
TEST_P(InputVolumeControllerParametrizedTest,
WaitingPeriodBetweenClippingChecks) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(/*applied_input_volume=*/255, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), 240);
helper.CallAnalyzeInputAudio(/*num_calls=*/300,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), 240);
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), 225);
}
TEST_P(InputVolumeControllerParametrizedTest, ClippingLoweringIsLimited) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
helper.CallAgcSequence(/*applied_input_volume=*/180, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), kClippedMin);
helper.CallAnalyzeInputAudio(/*num_calls=*/1000,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), kClippedMin);
}
TEST_P(InputVolumeControllerParametrizedTest,
ClippingMaxIsRespectedWhenEqualToLevel) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(/*applied_input_volume=*/255, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), 240);
helper.CallRecommendInputVolume(/*num_calls=*/10, /*initial_volume=*/240,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(helper.controller.recommended_input_volume(), 240);
}
TEST_P(InputVolumeControllerParametrizedTest,
ClippingMaxIsRespectedWhenHigherThanLevel) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
helper.CallAgcSequence(/*applied_input_volume=*/200, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
int volume = helper.controller.recommended_input_volume();
EXPECT_EQ(volume, 185);
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -58.0f);
EXPECT_EQ(volume, 240);
volume = helper.CallRecommendInputVolume(/*num_calls=*/10, volume,
kHighSpeechProbability, -58.0f);
EXPECT_EQ(volume, 240);
}
TEST_P(InputVolumeControllerParametrizedTest, UserCanRaiseVolumeAfterClipping) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
helper.CallAgcSequence(/*applied_input_volume=*/225, kHighSpeechProbability,
kSpeechLevel);
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), 210);
// User changed the volume.
int volume = helper.CallRecommendInputVolume(
/*num_calls=*/1, /*initial_volume-*/ 250, kHighSpeechProbability, -32.0f);
EXPECT_EQ(volume, 250);
// Move down...
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -8.0f);
EXPECT_EQ(volume, 210);
// And back up to the new max established by the user.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -58.0f);
EXPECT_EQ(volume, 250);
// Will not move above new maximum.
volume = helper.CallRecommendInputVolume(/*num_calls=*/1, volume,
kHighSpeechProbability, -48.0f);
EXPECT_EQ(volume, 250);
}
TEST_P(InputVolumeControllerParametrizedTest,
ClippingDoesNotPullLowVolumeBackUp) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
helper.CallAgcSequence(/*applied_input_volume=*/80, kHighSpeechProbability,
kSpeechLevel);
int initial_volume = helper.controller.recommended_input_volume();
helper.CallAnalyzeInputAudio(/*num_calls=*/1,
/*clipped_ratio=*/kAboveClippedThreshold);
EXPECT_EQ(helper.controller.recommended_input_volume(), initial_volume);
}
TEST_P(InputVolumeControllerParametrizedTest, TakesNoActionOnZeroMicVolume) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = GetParam()});
helper.CallAgcSequence(kInitialInputVolume, kHighSpeechProbability,
kSpeechLevel);
EXPECT_EQ(
helper.CallRecommendInputVolume(/*num_calls=*/10, /*initial_volume=*/0,
kHighSpeechProbability, -48.0f),
0);
}
TEST_P(InputVolumeControllerParametrizedTest, ClippingDetectionLowersVolume) {
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.min_input_volume = GetParam();
InputVolumeControllerTestHelper helper(config);
int volume = *helper.CallAgcSequence(/*applied_input_volume=*/255,
kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/1);
EXPECT_EQ(volume, 255);
WriteAlternatingAudioBufferSamples(0.99f * kMaxSample, helper.audio_buffer);
volume = *helper.CallAgcSequence(volume, kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/100);
EXPECT_EQ(volume, 255);
WriteAlternatingAudioBufferSamples(kMaxSample, helper.audio_buffer);
volume = *helper.CallAgcSequence(volume, kHighSpeechProbability, kSpeechLevel,
/*num_calls=*/100);
EXPECT_EQ(volume, 240);
}
// 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) {
std::unique_ptr<InputVolumeController> controller =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames);
controller->Initialize();
EXPECT_EQ(controller->clipped_level_step_, kClippedLevelStep);
EXPECT_EQ(controller->clipped_ratio_threshold_, kClippedRatioThreshold);
EXPECT_EQ(controller->clipped_wait_frames_, kClippedWaitFrames);
std::unique_ptr<InputVolumeController> controller_custom =
CreateInputVolumeController(/*clipped_level_step=*/10,
/*clipped_ratio_threshold=*/0.2f,
/*clipped_wait_frames=*/50);
controller_custom->Initialize();
EXPECT_EQ(controller_custom->clipped_level_step_, 10);
EXPECT_EQ(controller_custom->clipped_ratio_threshold_, 0.2f);
EXPECT_EQ(controller_custom->clipped_wait_frames_, 50);
}
TEST_P(InputVolumeControllerParametrizedTest,
DisableClippingPredictorDisablesClippingPredictor) {
std::unique_ptr<InputVolumeController> controller =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false);
controller->Initialize();
EXPECT_FALSE(controller->clipping_predictor_enabled());
EXPECT_FALSE(controller->use_clipping_predictor_step());
}
TEST_P(InputVolumeControllerParametrizedTest,
EnableClippingPredictorEnablesClippingPredictor) {
std::unique_ptr<InputVolumeController> controller =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/true);
controller->Initialize();
EXPECT_TRUE(controller->clipping_predictor_enabled());
EXPECT_TRUE(controller->use_clipping_predictor_step());
}
TEST_P(InputVolumeControllerParametrizedTest,
DisableClippingPredictorDoesNotLowerVolume) {
int volume = 255;
InputVolumeControllerConfig config = GetInputVolumeControllerTestConfig();
config.enable_clipping_predictor = false;
auto helper = InputVolumeControllerTestHelper(config);
helper.controller.Initialize();
EXPECT_FALSE(helper.controller.clipping_predictor_enabled());
EXPECT_FALSE(helper.controller.use_clipping_predictor_step());
// Expect no change if clipping prediction is enabled.
for (int j = 0; j < 31; ++j) {
WriteAlternatingAudioBufferSamples(0.99f * kMaxSample, helper.audio_buffer);
volume =
*helper.CallAgcSequence(volume, kLowSpeechProbability, kSpeechLevel,
/*num_calls=*/5);
WriteAudioBufferSamples(0.99f * kMaxSample, /*clipped_ratio=*/0.0f,
helper.audio_buffer);
volume =
*helper.CallAgcSequence(volume, kLowSpeechProbability, kSpeechLevel,
/*num_calls=*/5);
EXPECT_EQ(volume, 255);
}
}
// TODO(bugs.webrtc.org/7494): Split into several smaller tests.
TEST_P(InputVolumeControllerParametrizedTest,
UsedClippingPredictionsProduceLowerAnalogLevels) {
constexpr int kInitialLevel = 255;
constexpr float kCloseToClippingPeakRatio = 0.99f;
int volume_1 = kInitialLevel;
int volume_2 = kInitialLevel;
// Create two helpers, one with clipping prediction and one without.
auto config_1 = GetInputVolumeControllerTestConfig();
auto config_2 = GetInputVolumeControllerTestConfig();
config_1.enable_clipping_predictor = true;
config_2.enable_clipping_predictor = false;
auto helper_1 = InputVolumeControllerTestHelper(config_1);
auto helper_2 = InputVolumeControllerTestHelper(config_2);
helper_1.controller.Initialize();
helper_2.controller.Initialize();
EXPECT_TRUE(helper_1.controller.clipping_predictor_enabled());
EXPECT_FALSE(helper_2.controller.clipping_predictor_enabled());
EXPECT_TRUE(helper_1.controller.use_clipping_predictor_step());
// Expect a change if clipping prediction is enabled.
WriteAlternatingAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
WriteAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
/*clipped_ratio=*/0.0f, helper_1.audio_buffer);
WriteAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
/*clipped_ratio=*/0.0f, helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
EXPECT_EQ(volume_1, kInitialLevel - kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel);
// Expect no change during waiting.
for (int i = 0; i < kClippedWaitFrames / 10; ++i) {
WriteAlternatingAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
WriteAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
/*clipped_ratio=*/0.0f, helper_1.audio_buffer);
WriteAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
/*clipped_ratio=*/0.0f, helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
EXPECT_EQ(volume_1, kInitialLevel - kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel);
}
// Expect a change when the prediction step is used.
WriteAlternatingAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
WriteAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
/*clipped_ratio=*/0.0f, helper_1.audio_buffer);
WriteAudioBufferSamples(kCloseToClippingPeakRatio * kMaxSample,
/*clipped_ratio=*/0.0f, helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
EXPECT_EQ(volume_1, kInitialLevel - 2 * kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel);
// Expect no change when clipping is not detected or predicted.
for (int i = 0; i < 2 * kClippedWaitFrames / 10; ++i) {
WriteAlternatingAudioBufferSamples(/*samples_value=*/0.0f,
helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(/*samples_value=*/0.0f,
helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipped_ratio=*/0.0f,
helper_1.audio_buffer);
WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipped_ratio=*/0.0f,
helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
}
EXPECT_EQ(volume_1, kInitialLevel - 2 * kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel);
// Expect a change for clipping frames.
WriteAlternatingAudioBufferSamples(kMaxSample, helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kMaxSample, helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 1);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 1);
EXPECT_EQ(volume_1, kInitialLevel - 3 * kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel - kClippedLevelStep);
// Expect no change during waiting.
for (int i = 0; i < kClippedWaitFrames / 10; ++i) {
WriteAlternatingAudioBufferSamples(kMaxSample, helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kMaxSample, helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
WriteAudioBufferSamples(kMaxSample, /*clipped_ratio=*/1.0f,
helper_1.audio_buffer);
WriteAudioBufferSamples(kMaxSample, /*clipped_ratio=*/1.0f,
helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 5);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 5);
}
EXPECT_EQ(volume_1, kInitialLevel - 3 * kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel - kClippedLevelStep);
// Expect a change for clipping frames.
WriteAlternatingAudioBufferSamples(kMaxSample, helper_1.audio_buffer);
WriteAlternatingAudioBufferSamples(kMaxSample, helper_2.audio_buffer);
volume_1 = *helper_1.CallAgcSequence(volume_1, kLowSpeechProbability,
kSpeechLevel, 1);
volume_2 = *helper_2.CallAgcSequence(volume_2, kLowSpeechProbability,
kSpeechLevel, 1);
EXPECT_EQ(volume_1, kInitialLevel - 4 * kClippedLevelStep);
EXPECT_EQ(volume_2, kInitialLevel - 2 * kClippedLevelStep);
}
// Checks that passing an empty speech level has no effect on the input volume.
TEST_P(InputVolumeControllerParametrizedTest, EmptyRmsErrorHasNoEffect) {
InputVolumeController controller(kNumChannels,
GetInputVolumeControllerTestConfig());
controller.Initialize();
// Feed speech with low energy that would trigger an upward adapation of
// the analog level if an speech level was not low and the RMS level empty.
constexpr int kNumFrames = 125;
constexpr int kGainDb = -20;
SpeechSamplesReader reader;
int volume = reader.Feed(kNumFrames, kInitialInputVolume, kGainDb,
kLowSpeechProbability, absl::nullopt, controller);
// Check that no adaptation occurs.
ASSERT_EQ(volume, kInitialInputVolume);
}
// Checks that the recommended input volume is not updated unless enough
// frames have been processed after the previous update.
TEST(InputVolumeControllerTest, UpdateInputVolumeWaitFramesIsEffective) {
constexpr int kInputVolume = kInitialInputVolume;
std::unique_ptr<InputVolumeController> controller_wait_0 =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false,
/*update_input_volume_wait_frames=*/0);
std::unique_ptr<InputVolumeController> controller_wait_100 =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false,
/*update_input_volume_wait_frames=*/100);
controller_wait_0->Initialize();
controller_wait_100->Initialize();
SpeechSamplesReader reader_1;
SpeechSamplesReader reader_2;
int volume_wait_0 = reader_1.Feed(
/*num_frames=*/99, kInputVolume, /*gain_db=*/0, kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_0);
int volume_wait_100 = reader_2.Feed(
/*num_frames=*/99, kInputVolume, /*gain_db=*/0, kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_100);
// Check that adaptation only occurs if enough frames have been processed.
ASSERT_GT(volume_wait_0, kInputVolume);
ASSERT_EQ(volume_wait_100, kInputVolume);
volume_wait_0 =
reader_1.Feed(/*num_frames=*/1, volume_wait_0,
/*gain_db=*/0, kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_0);
volume_wait_100 =
reader_2.Feed(/*num_frames=*/1, volume_wait_100,
/*gain_db=*/0, kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_100);
// Check that adaptation only occurs when enough frames have been processed.
ASSERT_GT(volume_wait_0, kInputVolume);
ASSERT_GT(volume_wait_100, kInputVolume);
}
INSTANTIATE_TEST_SUITE_P(,
InputVolumeControllerParametrizedTest,
::testing::Values(12, 20));
TEST(InputVolumeControllerTest,
MinInputVolumeEnforcedWithClippingWhenAboveClippedLevelMin) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = 80, .clipped_level_min = 70});
// Trigger a downward adjustment caused by clipping input. Use a low speech
// probability to limit the volume changes to clipping handling.
WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f,
helper.audio_buffer);
constexpr int kNumCalls = 800;
helper.CallAgcSequence(/*applied_input_volume=*/100, kLowSpeechProbability,
/*speech_level_dbfs=*/-18.0f, kNumCalls);
EXPECT_EQ(helper.controller.recommended_input_volume(), 80);
}
TEST(InputVolumeControllerTest,
ClippedlevelMinEnforcedWithClippingWhenAboveMinInputVolume) {
InputVolumeControllerTestHelper helper(
/*config=*/{.min_input_volume = 70, .clipped_level_min = 80});
// Trigger a downward adjustment caused by clipping input. Use a low speech
// probability to limit the volume changes to clipping handling.
WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f,
helper.audio_buffer);
constexpr int kNumCalls = 800;
helper.CallAgcSequence(/*applied_input_volume=*/100, kLowSpeechProbability,
/*speech_level_dbfs=*/-18.0f, kNumCalls);
EXPECT_EQ(helper.controller.recommended_input_volume(), 80);
}
TEST(InputVolumeControllerTest, SpeechRatioThresholdIsEffective) {
constexpr int kInputVolume = kInitialInputVolume;
// Create two input volume controllers with 10 frames between volume updates
// and the minimum speech ratio of 0.8 and speech probability threshold 0.5.
std::unique_ptr<InputVolumeController> controller_1 =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false,
/*update_input_volume_wait_frames=*/10);
std::unique_ptr<InputVolumeController> controller_2 =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false,
/*update_input_volume_wait_frames=*/10);
controller_1->Initialize();
controller_2->Initialize();
SpeechSamplesReader reader_1;
SpeechSamplesReader reader_2;
int volume_1 = reader_1.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.7f,
/*speech_level_dbfs=*/-42.0f, *controller_1);
int volume_2 = reader_2.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.4f,
/*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_EQ(volume_1, kInputVolume);
ASSERT_EQ(volume_2, kInputVolume);
volume_1 = reader_1.Feed(/*num_frames=*/2, volume_1, /*gain_db=*/0,
/*speech_probability=*/0.4f,
/*speech_level_dbfs=*/-42.0f, *controller_1);
volume_2 = reader_2.Feed(/*num_frames=*/2, volume_2, /*gain_db=*/0,
/*speech_probability=*/0.4f,
/*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_EQ(volume_1, kInputVolume);
ASSERT_EQ(volume_2, kInputVolume);
volume_1 = reader_1.Feed(
/*num_frames=*/7, volume_1, /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f, *controller_1);
volume_2 = reader_2.Feed(
/*num_frames=*/7, volume_2, /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_GT(volume_1, kInputVolume);
ASSERT_EQ(volume_2, kInputVolume);
}
TEST(InputVolumeControllerTest, SpeechProbabilityThresholdIsEffective) {
constexpr int kInputVolume = kInitialInputVolume;
// Create two input volume controllers with the exact same settings and
// 10 frames between volume updates.
std::unique_ptr<InputVolumeController> controller_1 =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false,
/*update_input_volume_wait_frames=*/10);
std::unique_ptr<InputVolumeController> controller_2 =
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
kClippedWaitFrames,
/*enable_clipping_predictor=*/false,
/*update_input_volume_wait_frames=*/10);
controller_1->Initialize();
controller_2->Initialize();
SpeechSamplesReader reader_1;
SpeechSamplesReader reader_2;
// Process with two sets of inputs: Use `reader_1` to process inputs
// that make the volume to be adjusted after enough frames have been
// processsed and `reader_2` to process inputs that won't make the volume
// to be adjusted.
int volume_1 = reader_1.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.5f,
/*speech_level_dbfs=*/-42.0f, *controller_1);
int volume_2 = reader_2.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.49f,
/*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_EQ(volume_1, kInputVolume);
ASSERT_EQ(volume_2, kInputVolume);
reader_1.Feed(/*num_frames=*/2, volume_1, /*gain_db=*/0,
/*speech_probability=*/0.49f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/2, volume_2, /*gain_db=*/0,
/*speech_probability=*/0.49f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
ASSERT_EQ(volume_1, kInputVolume);
ASSERT_EQ(volume_2, kInputVolume);
volume_1 = reader_1.Feed(
/*num_frames=*/7, volume_1, /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f, *controller_1);
volume_2 = reader_2.Feed(
/*num_frames=*/7, volume_2, /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_GT(volume_1, kInputVolume);
ASSERT_EQ(volume_2, kInputVolume);
}
TEST(InputVolumeControllerTest,
DoNotLogRecommendedInputVolumeOnChangeToMatchTarget) {
metrics::Reset();
SpeechSamplesReader reader;
auto controller = CreateInputVolumeController();
controller->Initialize();
// Trigger a downward volume change by inputting audio that clips. Pass a
// speech level that falls in the target range to make sure that the
// adaptation is not made to match the target range.
constexpr int kStartupVolume = 255;
const int volume = reader.Feed(/*num_frames=*/14, kStartupVolume,
/*gain_db=*/50, kHighSpeechProbability,
/*speech_level_dbfs=*/-20.0f, *controller);
ASSERT_LT(volume, kStartupVolume);
EXPECT_METRIC_THAT(
metrics::Samples(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget"),
::testing::IsEmpty());
}
TEST(InputVolumeControllerTest,
LogRecommendedInputVolumeOnUpwardChangeToMatchTarget) {
metrics::Reset();
SpeechSamplesReader reader;
auto controller = CreateInputVolumeController();
controller->Initialize();
constexpr int kStartupVolume = 100;
// Trigger an upward volume change by inputting audio that does not clip and
// by passing a speech level below the target range.
const int volume = reader.Feed(/*num_frames=*/14, kStartupVolume,
/*gain_db=*/-6, kHighSpeechProbability,
/*speech_level_dbfs=*/-50.0f, *controller);
ASSERT_GT(volume, kStartupVolume);
EXPECT_METRIC_THAT(
metrics::Samples(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget"),
::testing::Not(::testing::IsEmpty()));
}
TEST(InputVolumeControllerTest,
LogRecommendedInputVolumeOnDownwardChangeToMatchTarget) {
metrics::Reset();
SpeechSamplesReader reader;
auto controller = CreateInputVolumeController();
controller->Initialize();
constexpr int kStartupVolume = 100;
// Trigger a downward volume change by inputting audio that does not clip and
// by passing a speech level above the target range.
const int volume = reader.Feed(/*num_frames=*/14, kStartupVolume,
/*gain_db=*/-6, kHighSpeechProbability,
/*speech_level_dbfs=*/-5.0f, *controller);
ASSERT_LT(volume, kStartupVolume);
EXPECT_METRIC_THAT(
metrics::Samples(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget"),
::testing::Not(::testing::IsEmpty()));
}
TEST(MonoInputVolumeControllerTest, CheckHandleClippingLowersVolume) {
constexpr int kInitialInputVolume = 100;
constexpr int kInputVolumeStep = 29;
MonoInputVolumeController mono_controller(
/*clipped_level_min=*/70,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/3, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller.Initialize();
UpdateRecommendedInputVolume(mono_controller, kInitialInputVolume,
kLowSpeechProbability,
/*rms_error_dbfs*/ -10.0f);
mono_controller.HandleClipping(kInputVolumeStep);
EXPECT_EQ(mono_controller.recommended_analog_level(),
kInitialInputVolume - kInputVolumeStep);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessNegativeRmsErrorDecreasesInputVolume) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/3, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller.Initialize();
int volume = UpdateRecommendedInputVolume(
mono_controller, kInitialInputVolume, kHighSpeechProbability, -10.0f);
volume = UpdateRecommendedInputVolume(mono_controller, volume,
kHighSpeechProbability, -10.0f);
volume = UpdateRecommendedInputVolume(mono_controller, volume,
kHighSpeechProbability, -10.0f);
EXPECT_LT(volume, kInitialInputVolume);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessPositiveRmsErrorIncreasesInputVolume) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/3, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller.Initialize();
int volume = UpdateRecommendedInputVolume(
mono_controller, kInitialInputVolume, kHighSpeechProbability, 10.0f);
volume = UpdateRecommendedInputVolume(mono_controller, volume,
kHighSpeechProbability, 10.0f);
volume = UpdateRecommendedInputVolume(mono_controller, volume,
kHighSpeechProbability, 10.0f);
EXPECT_GT(volume, kInitialInputVolume);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessNegativeRmsErrorDecreasesInputVolumeWithLimit) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_3(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2,
/*speech_probability_threshold=*/0.7,
/*speech_ratio_threshold=*/0.8);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
mono_controller_3.Initialize();
// Process RMS errors in the range
// [`-kMaxResidualGainChange`, `kMaxResidualGainChange`].
int volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, kInitialInputVolume, kHighSpeechProbability, -14.0f);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kHighSpeechProbability, -14.0f);
// Process RMS errors outside the range
// [`-kMaxResidualGainChange`, `kMaxResidualGainChange`].
int volume_2 = UpdateRecommendedInputVolume(
mono_controller_2, kInitialInputVolume, kHighSpeechProbability, -15.0f);
int volume_3 = UpdateRecommendedInputVolume(
mono_controller_3, kInitialInputVolume, kHighSpeechProbability, -30.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -15.0f);
volume_3 = UpdateRecommendedInputVolume(mono_controller_3, volume_3,
kHighSpeechProbability, -30.0f);
EXPECT_LT(volume_1, kInitialInputVolume);
EXPECT_LT(volume_2, volume_1);
EXPECT_EQ(volume_2, volume_3);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessPositiveRmsErrorIncreasesInputVolumeWithLimit) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_3(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
mono_controller_3.Initialize();
// Process RMS errors in the range
// [`-kMaxResidualGainChange`, `kMaxResidualGainChange`].
int volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, kInitialInputVolume, kHighSpeechProbability, 14.0f);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kHighSpeechProbability, 14.0f);
// Process RMS errors outside the range
// [`-kMaxResidualGainChange`, `kMaxResidualGainChange`].
int volume_2 = UpdateRecommendedInputVolume(
mono_controller_2, kInitialInputVolume, kHighSpeechProbability, 15.0f);
int volume_3 = UpdateRecommendedInputVolume(
mono_controller_3, kInitialInputVolume, kHighSpeechProbability, 30.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, 15.0f);
volume_3 = UpdateRecommendedInputVolume(mono_controller_3, volume_3,
kHighSpeechProbability, 30.0f);
EXPECT_GT(volume_1, kInitialInputVolume);
EXPECT_GT(volume_2, volume_1);
EXPECT_EQ(volume_2, volume_3);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessRmsErrorDecreasesInputVolumeRepeatedly) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller.Initialize();
int volume_before = UpdateRecommendedInputVolume(
mono_controller, kInitialInputVolume, kHighSpeechProbability, -10.0f);
volume_before = UpdateRecommendedInputVolume(mono_controller, volume_before,
kHighSpeechProbability, -10.0f);
EXPECT_LT(volume_before, kInitialInputVolume);
int volume_after = UpdateRecommendedInputVolume(
mono_controller, volume_before, kHighSpeechProbability, -10.0f);
volume_after = UpdateRecommendedInputVolume(mono_controller, volume_after,
kHighSpeechProbability, -10.0f);
EXPECT_LT(volume_after, volume_before);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessPositiveRmsErrorIncreasesInputVolumeRepeatedly) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller(
/*clipped_level_min=*/64,
/*min_mic_level=*/32,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller.Initialize();
int volume_before = UpdateRecommendedInputVolume(
mono_controller, kInitialInputVolume, kHighSpeechProbability, 10.0f);
volume_before = UpdateRecommendedInputVolume(mono_controller, volume_before,
kHighSpeechProbability, 10.0f);
EXPECT_GT(volume_before, kInitialInputVolume);
int volume_after = UpdateRecommendedInputVolume(
mono_controller, volume_before, kHighSpeechProbability, 10.0f);
volume_after = UpdateRecommendedInputVolume(mono_controller, volume_after,
kHighSpeechProbability, 10.0f);
EXPECT_GT(volume_after, volume_before);
}
TEST(MonoInputVolumeControllerTest, CheckClippedLevelMinIsEffective) {
constexpr int kInitialInputVolume = 100;
constexpr int kClippedLevelMin = 70;
MonoInputVolumeController mono_controller_1(
kClippedLevelMin,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
kClippedLevelMin,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
// Process one frame to reset the state for `HandleClipping()`.
EXPECT_EQ(UpdateRecommendedInputVolume(mono_controller_1, kInitialInputVolume,
kLowSpeechProbability, -10.0f),
kInitialInputVolume);
EXPECT_EQ(UpdateRecommendedInputVolume(mono_controller_2, kInitialInputVolume,
kLowSpeechProbability, -10.0f),
kInitialInputVolume);
mono_controller_1.HandleClipping(29);
mono_controller_2.HandleClipping(31);
EXPECT_EQ(mono_controller_2.recommended_analog_level(), kClippedLevelMin);
EXPECT_LT(mono_controller_2.recommended_analog_level(),
mono_controller_1.recommended_analog_level());
}
TEST(MonoInputVolumeControllerTest, CheckMinMicLevelIsEffective) {
constexpr int kInitialInputVolume = 100;
constexpr int kMinMicLevel = 64;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64, kMinMicLevel,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64, kMinMicLevel,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
int volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, kInitialInputVolume, kHighSpeechProbability, -10.0f);
int volume_2 = UpdateRecommendedInputVolume(
mono_controller_2, kInitialInputVolume, kHighSpeechProbability, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_EQ(volume_2, kInitialInputVolume);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kHighSpeechProbability, -10.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -30.0f);
EXPECT_LT(volume_1, kInitialInputVolume);
EXPECT_LT(volume_2, volume_1);
EXPECT_EQ(volume_2, kMinMicLevel);
}
TEST(MonoInputVolumeControllerTest,
CheckUpdateInputVolumeWaitFramesIsEffective) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/1, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/3, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
int volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, kInitialInputVolume, kHighSpeechProbability, -10.0f);
int volume_2 = UpdateRecommendedInputVolume(
mono_controller_2, kInitialInputVolume, kHighSpeechProbability, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_EQ(volume_2, kInitialInputVolume);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kHighSpeechProbability, -10.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -10.0f);
EXPECT_LT(volume_1, kInitialInputVolume);
EXPECT_EQ(volume_2, kInitialInputVolume);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -10.0f);
EXPECT_LT(volume_2, kInitialInputVolume);
}
TEST(MonoInputVolumeControllerTest,
CheckSpeechProbabilityThresholdIsEffective) {
constexpr int kInitialInputVolume = 100;
constexpr float kSpeechProbabilityThreshold = 0.8f;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/2, kSpeechProbabilityThreshold,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/2, kSpeechProbabilityThreshold,
kSpeechRatioThreshold);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
int volume_1 =
UpdateRecommendedInputVolume(mono_controller_1, kInitialInputVolume,
kSpeechProbabilityThreshold, -10.0f);
int volume_2 =
UpdateRecommendedInputVolume(mono_controller_2, kInitialInputVolume,
kSpeechProbabilityThreshold, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_EQ(volume_2, kInitialInputVolume);
volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, volume_1, kSpeechProbabilityThreshold - 0.1f, -10.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kSpeechProbabilityThreshold, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_LT(volume_2, volume_1);
}
TEST(MonoInputVolumeControllerTest, CheckSpeechRatioThresholdIsEffective) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/4, kHighSpeechProbability,
/*speech_ratio_threshold=*/0.75f);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/4, kHighSpeechProbability,
/*speech_ratio_threshold=*/0.75f);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
int volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, kInitialInputVolume, kHighSpeechProbability, -10.0f);
int volume_2 = UpdateRecommendedInputVolume(
mono_controller_2, kInitialInputVolume, kHighSpeechProbability, -10.0f);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kHighSpeechProbability, -10.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -10.0f);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kLowSpeechProbability, -10.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kLowSpeechProbability, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_EQ(volume_2, kInitialInputVolume);
volume_1 = UpdateRecommendedInputVolume(mono_controller_1, volume_1,
kLowSpeechProbability, -10.0f);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_LT(volume_2, volume_1);
}
TEST(MonoInputVolumeControllerTest,
CheckProcessEmptyRmsErrorDoesNotLowerVolume) {
constexpr int kInitialInputVolume = 100;
MonoInputVolumeController mono_controller_1(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
MonoInputVolumeController mono_controller_2(
/*clipped_level_min=*/64,
/*min_mic_level=*/84,
/*update_input_volume_wait_frames=*/2, kHighSpeechProbability,
kSpeechRatioThreshold);
mono_controller_1.Initialize();
mono_controller_2.Initialize();
int volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, kInitialInputVolume, kHighSpeechProbability, -10.0f);
int volume_2 = UpdateRecommendedInputVolume(
mono_controller_2, kInitialInputVolume, kHighSpeechProbability, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_EQ(volume_2, kInitialInputVolume);
volume_1 = UpdateRecommendedInputVolume(
mono_controller_1, volume_1, kHighSpeechProbability, absl::nullopt);
volume_2 = UpdateRecommendedInputVolume(mono_controller_2, volume_2,
kHighSpeechProbability, -10.0f);
EXPECT_EQ(volume_1, kInitialInputVolume);
EXPECT_LT(volume_2, volume_1);
}
} // namespace webrtc