| /* |
| * 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, |
| std::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, |
| std::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. |
| std::optional<int> CallAgcSequence(int applied_input_volume, |
| float speech_probability, |
| std::optional<float> speech_level_dbfs, |
| int num_calls = 1) { |
| RTC_DCHECK_GE(num_calls, 1); |
| std::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, |
| std::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, std::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, std::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 |