AudioProcessingImpl: Add input volume unit tests
Bug: webrtc:7494
Change-Id: I5a32359cacfb7cd6b610ae13b95f92283c761362
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/275500
Commit-Queue: Hanna Silen <silen@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38132}
diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc
index bb016e0..e67c5ee 100644
--- a/modules/audio_processing/audio_processing_impl_unittest.cc
+++ b/modules/audio_processing/audio_processing_impl_unittest.cc
@@ -12,7 +12,9 @@
#include <array>
#include <memory>
+#include <tuple>
+#include "absl/types/optional.h"
#include "api/make_ref_counted.h"
#include "api/scoped_refptr.h"
#include "modules/audio_processing/include/audio_processing.h"
@@ -23,6 +25,7 @@
#include "modules/audio_processing/test/test_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/random.h"
+#include "rtc_base/strings/string_builder.h"
#include "test/field_trial.h"
#include "test/gmock.h"
#include "test/gtest.h"
@@ -128,6 +131,123 @@
static constexpr float ProcessSample(float x) { return 2.f * x; }
};
+// Creates a simple `AudioProcessing` instance for APM input volume testing
+// with analog and digital AGC enabled and minimum volume `startup_min_volume`
+// at the startup.
+rtc::scoped_refptr<AudioProcessing> CreateApmForInputVolumeTest(
+ int startup_min_volume) {
+ webrtc::AudioProcessing::Config config;
+ // Enable AGC1 analog.
+ config.gain_controller1.enabled = true;
+ config.gain_controller1.analog_gain_controller.enabled = true;
+ config.gain_controller1.analog_gain_controller.startup_min_volume =
+ startup_min_volume;
+ // Enable AGC2 digital.
+ config.gain_controller2.enabled = true;
+ config.gain_controller2.adaptive_digital.enabled = true;
+
+ auto apm(AudioProcessingBuilder().Create());
+ apm->ApplyConfig(config);
+ return apm;
+}
+
+// Runs `apm` input processing for volume adjustments for `num_frames` random
+// frames starting from the volume `initial_volume`. This includes three steps:
+// 1) Set the input volume 2) Process the stream 3) Set the new recommended
+// input volume. Returns the new recommended input volume.
+int ProcessInputVolume(AudioProcessing& apm,
+ int num_frames,
+ int initial_volume) {
+ constexpr int kSampleRateHz = 48000;
+ constexpr int kNumChannels = 1;
+ std::array<float, kSampleRateHz / 100> buffer;
+ float* channel_pointers[] = {buffer.data()};
+ StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz,
+ /*num_channels=*/kNumChannels);
+ int recommended_input_volume = initial_volume;
+ for (int i = 0; i < num_frames; ++i) {
+ Random random_generator(2341U);
+ RandomizeSampleVector(&random_generator, buffer);
+
+ apm.set_stream_analog_level(recommended_input_volume);
+ apm.ProcessStream(channel_pointers, stream_config, stream_config,
+ channel_pointers);
+ recommended_input_volume = apm.recommended_stream_analog_level();
+ }
+ return recommended_input_volume;
+}
+
+constexpr char kMinMicLevelFieldTrial[] =
+ "WebRTC-Audio-2ndAgcMinMicLevelExperiment";
+constexpr int kMinInputVolume = 12;
+
+std::string GetMinMicLevelExperimentFieldTrial(absl::optional<int> value) {
+ char field_trial_buffer[64];
+ rtc::SimpleStringBuilder builder(field_trial_buffer);
+ if (value.has_value()) {
+ RTC_DCHECK_GE(*value, 0);
+ RTC_DCHECK_LE(*value, 255);
+ builder << kMinMicLevelFieldTrial << "/Enabled-" << *value << "/";
+ } else {
+ builder << kMinMicLevelFieldTrial << "/Disabled/";
+ }
+ return builder.str();
+}
+
+// TODO(webrtc:7494): Remove the fieldtrial from the input volume tests when
+// "WebRTC-Audio-2ndAgcMinMicLevelExperiment" is removed.
+class InputVolumeStartupParameterizedTest
+ : public ::testing::TestWithParam<
+ std::tuple<int, int, absl::optional<int>>> {
+ protected:
+ InputVolumeStartupParameterizedTest()
+ : field_trials_(
+ GetMinMicLevelExperimentFieldTrial(std::get<2>(GetParam()))) {}
+ int GetMinStartupVolume() const { return std::get<0>(GetParam()); }
+ int GetStartupVolume() const { return std::get<1>(GetParam()); }
+ int GetMinVolume() const {
+ return std::get<2>(GetParam()).value_or(kMinInputVolume);
+ }
+
+ private:
+ test::ScopedFieldTrials field_trials_;
+};
+
+class InputVolumeNotZeroParameterizedTest
+ : public ::testing::TestWithParam<
+ std::tuple<int, int, absl::optional<int>>> {
+ protected:
+ InputVolumeNotZeroParameterizedTest()
+ : field_trials_(
+ GetMinMicLevelExperimentFieldTrial(std::get<2>(GetParam()))) {}
+ int GetStartupVolume() const { return std::get<0>(GetParam()); }
+ int GetVolume() const { return std::get<1>(GetParam()); }
+ int GetMinVolume() const {
+ return std::get<2>(GetParam()).value_or(kMinInputVolume);
+ }
+ bool GetMinMicLevelExperimentEnabled() {
+ return std::get<2>(GetParam()).has_value();
+ }
+
+ private:
+ test::ScopedFieldTrials field_trials_;
+};
+
+class InputVolumeZeroParameterizedTest
+ : public ::testing::TestWithParam<std::tuple<int, absl::optional<int>>> {
+ protected:
+ InputVolumeZeroParameterizedTest()
+ : field_trials_(
+ GetMinMicLevelExperimentFieldTrial(std::get<1>(GetParam()))) {}
+ int GetStartupVolume() const { return std::get<0>(GetParam()); }
+ int GetMinVolume() const {
+ return std::get<1>(GetParam()).value_or(kMinInputVolume);
+ }
+
+ private:
+ test::ScopedFieldTrials field_trials_;
+};
+
} // namespace
TEST(AudioProcessingImplTest, AudioParameterChangeTriggersInit) {
@@ -813,4 +933,119 @@
kNoErr);
}
}
+
+// Tests that the minimum startup volume is applied at the startup.
+TEST_P(InputVolumeStartupParameterizedTest,
+ VerifyStartupMinVolumeAppliedAtStartup) {
+ const int applied_startup_input_volume = GetStartupVolume();
+ const int startup_min_volume = GetMinStartupVolume();
+ const int min_volume = std::max(startup_min_volume, GetMinVolume());
+ const int expected_volume =
+ std::max(applied_startup_input_volume, min_volume);
+ auto apm(CreateApmForInputVolumeTest(startup_min_volume));
+
+ const int recommended_input_volume =
+ ProcessInputVolume(*apm, /*num_frames=*/1, applied_startup_input_volume);
+
+ ASSERT_EQ(recommended_input_volume, expected_volume);
+}
+
+// Tests that the minimum input volume is applied if the volume is manually
+// adjusted to a non-zero value only if
+// "WebRTC-Audio-2ndAgcMinMicLevelExperiment" is enabled.
+TEST_P(InputVolumeNotZeroParameterizedTest,
+ VerifyMinVolumeMaybeAppliedAfterManualVolumeAdjustments) {
+ constexpr int kStartupMinVolume = 0;
+ const int applied_startup_input_volume = GetStartupVolume();
+ const int applied_input_volume = GetVolume();
+ const int expected_volume = std::max(applied_input_volume, GetMinVolume());
+ auto apm(CreateApmForInputVolumeTest(kStartupMinVolume));
+
+ ProcessInputVolume(*apm, /*num_frames=*/1, applied_startup_input_volume);
+ const int recommended_input_volume =
+ ProcessInputVolume(*apm, /*num_frames=*/1, applied_input_volume);
+
+ ASSERT_NE(applied_input_volume, 0);
+ if (GetMinMicLevelExperimentEnabled()) {
+ ASSERT_EQ(recommended_input_volume, expected_volume);
+ } else {
+ ASSERT_EQ(recommended_input_volume, applied_input_volume);
+ }
+}
+
+// Tests that the minimum input volume is not applied if the volume is manually
+// adjusted to zero.
+TEST_P(InputVolumeZeroParameterizedTest,
+ VerifyMinVolumeNotAppliedAfterManualVolumeAdjustments) {
+ constexpr int kStartupMinVolume = 0;
+ constexpr int kZeroVolume = 0;
+ const int applied_startup_input_volume = GetStartupVolume();
+ auto apm(CreateApmForInputVolumeTest(kStartupMinVolume));
+
+ const int recommended_input_volume_after_startup =
+ ProcessInputVolume(*apm, /*num_frames=*/1, applied_startup_input_volume);
+ const int recommended_input_volume =
+ ProcessInputVolume(*apm, /*num_frames=*/1, kZeroVolume);
+
+ ASSERT_NE(recommended_input_volume, recommended_input_volume_after_startup);
+ ASSERT_EQ(recommended_input_volume, kZeroVolume);
+}
+
+// Tests that the minimum input volume is applied if the volume is not zero
+// before it is automatically adjusted.
+TEST_P(InputVolumeNotZeroParameterizedTest,
+ VerifyMinVolumeAppliedAfterAutomaticVolumeAdjustments) {
+ constexpr int kStartupMinVolume = 0;
+ const int applied_startup_input_volume = GetStartupVolume();
+ const int applied_input_volume = GetVolume();
+ auto apm(CreateApmForInputVolumeTest(kStartupMinVolume));
+
+ ProcessInputVolume(*apm, /*num_frames=*/1, applied_startup_input_volume);
+ const int recommended_input_volume =
+ ProcessInputVolume(*apm, /*num_frames=*/400, applied_input_volume);
+
+ ASSERT_NE(applied_input_volume, 0);
+ if (recommended_input_volume != applied_input_volume) {
+ ASSERT_GE(recommended_input_volume, GetMinVolume());
+ }
+}
+
+// Tests that the minimum input volume is not applied if the volume is zero
+// before it is automatically adjusted.
+TEST_P(InputVolumeZeroParameterizedTest,
+ VerifyMinVolumeNotAppliedAfterAutomaticVolumeAdjustments) {
+ constexpr int kStartupMinVolume = 0;
+ constexpr int kZeroVolume = 0;
+ const int applied_startup_input_volume = GetStartupVolume();
+ auto apm(CreateApmForInputVolumeTest(kStartupMinVolume));
+
+ const int recommended_input_volume_after_startup =
+ ProcessInputVolume(*apm, /*num_frames=*/1, applied_startup_input_volume);
+ const int recommended_input_volume =
+ ProcessInputVolume(*apm, /*num_frames=*/400, kZeroVolume);
+
+ ASSERT_NE(recommended_input_volume, recommended_input_volume_after_startup);
+ ASSERT_EQ(recommended_input_volume, kZeroVolume);
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioProcessingImplTest,
+ InputVolumeStartupParameterizedTest,
+ ::testing::Combine(::testing::Values(0, 5, 15),
+ ::testing::Values(0, 5, 30),
+ ::testing::Values(absl::nullopt,
+ 20)));
+
+INSTANTIATE_TEST_SUITE_P(AudioProcessingImplTest,
+ InputVolumeNotZeroParameterizedTest,
+ ::testing::Combine(::testing::Values(0, 5, 15),
+ ::testing::Values(1, 5, 30),
+ ::testing::Values(absl::nullopt,
+ 20)));
+
+INSTANTIATE_TEST_SUITE_P(AudioProcessingImplTest,
+ InputVolumeZeroParameterizedTest,
+ ::testing::Combine(::testing::Values(0, 5, 15),
+ ::testing::Values(absl::nullopt,
+ 20)));
+
} // namespace webrtc