InputVolumeStatsReporter: replace WebRTC.Audio.AgcSetLevel

The `WebRTC.Audio.AgcSetLevel` name is misleading and the histogram
is logged for each channel - but the input volume is one for all the
channels.

Changes:
- `WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget`
  is the new name
- Now available not only in `AgcManagerDirect` (AGC1), but also in
  `InputVolumeController` (AGC2)
- Logged once and not for each channel
- Also add the following AGC implementation agnostic histograms
  - `WebRTC.Audio.Apm.AppliedInputVolume.OnChange`
  - `WebRTC.Audio.Apm.RecommendedInputVolume.OnChange`
- Fix `SpeechSamplesReader::Feed()` in the unit tests, which did
  not set the applied input volume and apply the recommended one

The histogram definitions are updated in crrev.com/c/4087426.

Bug: webrtc:7494
Change-Id: I03c5dfb08165805215ca2c4bb6509b16de8d68da
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/287081
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Hanna Silen <silen@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38852}
This commit is contained in:
Alessio Bazzica 2022-12-08 15:33:01 +01:00 committed by WebRTC LUCI CQ
parent e04726281c
commit da964d7559
6 changed files with 169 additions and 58 deletions

View File

@ -407,9 +407,6 @@ void MonoAgc::UpdateGain(int rms_error_db) {
int old_level = level_;
SetLevel(LevelFromGainError(residual_gain, level_, min_mic_level_));
if (old_level != level_) {
// level_ was updated by SetLevel; log the new value.
RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.AgcSetLevel", level_, 1,
kMaxMicLevel, 50);
// Reset the AGC since the level has changed.
agc_->Reset();
}
@ -627,6 +624,7 @@ void AgcManagerDirect::Process(const AudioBuffer& audio_buffer,
absl::optional<float> speech_probability,
absl::optional<float> speech_level_dbfs) {
AggregateChannelLevels();
const int volume_after_clipping_handling = recommended_input_volume_;
if (!capture_output_used_) {
return;
@ -649,6 +647,13 @@ void AgcManagerDirect::Process(const AudioBuffer& audio_buffer,
}
AggregateChannelLevels();
if (volume_after_clipping_handling != recommended_input_volume_) {
// The recommended input volume was adjusted in order to match the target
// level.
RTC_HISTOGRAM_COUNTS_LINEAR(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget",
recommended_input_volume_, 1, kMaxMicLevel, 50);
}
}
absl::optional<int> AgcManagerDirect::GetDigitalComressionGain() {

View File

@ -509,6 +509,7 @@ void InputVolumeController::AnalyzePreProcess(const AudioBuffer& audio_buffer) {
void InputVolumeController::Process(float speech_probability,
absl::optional<float> speech_level_dbfs) {
AggregateChannelLevels();
const int volume_after_clipping_handling = recommended_input_volume_;
if (!capture_output_used_) {
return;
@ -526,6 +527,13 @@ void InputVolumeController::Process(float speech_probability,
}
AggregateChannelLevels();
if (volume_after_clipping_handling != recommended_input_volume_) {
// The recommended input volume was adjusted in order to match the target
// level.
RTC_HISTOGRAM_COUNTS_LINEAR(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget",
recommended_input_volume_, 1, kMaxInputVolume, 50);
}
}
void InputVolumeController::HandleCaptureOutputUsedChange(

View File

@ -18,6 +18,7 @@
#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"
@ -59,9 +60,9 @@ constexpr InputVolumeControllerConfig kDefaultInputVolumeControllerConfig{};
constexpr ClippingPredictorConfig kDefaultClippingPredictorConfig{};
std::unique_ptr<InputVolumeController> CreateInputVolumeController(
int clipped_level_step,
float clipped_ratio_threshold,
int clipped_wait_frames,
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{
@ -194,6 +195,7 @@ class SpeechSamplesReader {
// does not loop. `speech_probability` and `speech_level_dbfs` are passed to
// `Process()`.
void Feed(int num_frames,
int applied_input_volume,
int gain_db,
float speech_probability,
absl::optional<float> speech_level_dbfs,
@ -214,9 +216,10 @@ class SpeechSamplesReader {
return rtc::SafeClamp(static_cast<float>(v) * gain,
kMinSample, kMaxSample);
});
controller.set_stream_analog_level(applied_input_volume);
controller.AnalyzePreProcess(audio_buffer_);
controller.Process(speech_probability, speech_level_dbfs);
applied_input_volume = controller.recommended_analog_level();
}
}
@ -1250,19 +1253,16 @@ TEST_P(InputVolumeControllerParametrizedTest, EmptyRmsErrorHasNoEffect) {
GetInputVolumeControllerTestConfig());
controller.Initialize();
constexpr int kInputVolume = kInitialInputVolume;
controller.set_stream_analog_level(kInputVolume);
// 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;
reader.Feed(kNumFrames, kGainDb, kLowSpeechProbability, absl::nullopt,
controller);
reader.Feed(kNumFrames, kInitialInputVolume, kGainDb, kLowSpeechProbability,
absl::nullopt, controller);
// Check that no adaptation occurs.
ASSERT_EQ(controller.recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller.recommended_analog_level(), kInitialInputVolume);
}
// Checks that the recommended input volume is not updated unless enough
@ -1281,23 +1281,26 @@ TEST(InputVolumeControllerTest, UpdateInputVolumeWaitFramesIsEffective) {
/*update_input_volume_wait_frames=*/100);
controller_wait_0->Initialize();
controller_wait_100->Initialize();
controller_wait_0->set_stream_analog_level(kInputVolume);
controller_wait_100->set_stream_analog_level(kInputVolume);
SpeechSamplesReader reader_1;
SpeechSamplesReader reader_2;
reader_1.Feed(/*num_frames=*/99, /*gain_db=*/0, kHighSpeechProbability,
reader_1.Feed(/*num_frames=*/99, kInputVolume, /*gain_db=*/0,
kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_0);
reader_2.Feed(/*num_frames=*/99, /*gain_db=*/0, kHighSpeechProbability,
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(controller_wait_0->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_wait_100->recommended_analog_level(), kInputVolume);
reader_1.Feed(/*num_frames=*/1, /*gain_db=*/0, kHighSpeechProbability,
reader_1.Feed(/*num_frames=*/1, controller_wait_0->recommended_analog_level(),
/*gain_db=*/0, kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_0);
reader_2.Feed(/*num_frames=*/1, /*gain_db=*/0, kHighSpeechProbability,
reader_2.Feed(/*num_frames=*/1,
controller_wait_100->recommended_analog_level(), /*gain_db=*/0,
kHighSpeechProbability,
/*speech_level_dbfs=*/-42.0f, *controller_wait_100);
// Check that adaptation only occurs when enough frames have been processed.
@ -1321,38 +1324,36 @@ TEST(InputVolumeControllerTest, SpeechRatioThresholdIsEffective) {
/*update_input_volume_wait_frames=*/10);
controller_1->Initialize();
controller_2->Initialize();
controller_1->set_stream_analog_level(kInputVolume);
controller_2->set_stream_analog_level(kInputVolume);
SpeechSamplesReader reader_1;
SpeechSamplesReader reader_2;
reader_1.Feed(/*num_frames=*/1, /*gain_db=*/0,
reader_1.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/1, /*gain_db=*/0,
reader_2.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.4f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
ASSERT_EQ(controller_1->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_2->recommended_analog_level(), kInputVolume);
reader_1.Feed(/*num_frames=*/2, /*gain_db=*/0,
/*speech_probability=*/0.4f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/2, /*gain_db=*/0,
/*speech_probability=*/0.4f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
reader_1.Feed(
/*num_frames=*/2, controller_1->recommended_analog_level(), /*gain_db=*/0,
/*speech_probability=*/0.4f, /*speech_level_dbfs=*/-42.0f, *controller_1);
reader_2.Feed(
/*num_frames=*/2, controller_2->recommended_analog_level(), /*gain_db=*/0,
/*speech_probability=*/0.4f, /*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_EQ(controller_1->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_2->recommended_analog_level(), kInputVolume);
reader_1.Feed(/*num_frames=*/7, /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/7, /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
reader_1.Feed(
/*num_frames=*/7, controller_1->recommended_analog_level(), /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f, *controller_1);
reader_2.Feed(
/*num_frames=*/7, controller_2->recommended_analog_level(), /*gain_db=*/0,
/*speech_probability=*/0.7f, /*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_GT(controller_1->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_2->recommended_analog_level(), kInputVolume);
@ -1374,8 +1375,6 @@ TEST(InputVolumeControllerTest, SpeechProbabilityThresholdIsEffective) {
/*update_input_volume_wait_frames=*/10);
controller_1->Initialize();
controller_2->Initialize();
controller_1->set_stream_analog_level(kInputVolume);
controller_2->set_stream_analog_level(kInputVolume);
SpeechSamplesReader reader_1;
SpeechSamplesReader reader_2;
@ -1384,37 +1383,95 @@ TEST(InputVolumeControllerTest, SpeechProbabilityThresholdIsEffective) {
// 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.
reader_1.Feed(/*num_frames=*/1, /*gain_db=*/0,
reader_1.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/1, /*gain_db=*/0,
reader_2.Feed(/*num_frames=*/1, kInputVolume, /*gain_db=*/0,
/*speech_probability=*/0.49f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
ASSERT_EQ(controller_1->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_2->recommended_analog_level(), kInputVolume);
reader_1.Feed(/*num_frames=*/2, /*gain_db=*/0,
reader_1.Feed(/*num_frames=*/2, controller_1->recommended_analog_level(),
/*gain_db=*/0,
/*speech_probability=*/0.49f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/2, /*gain_db=*/0,
reader_2.Feed(/*num_frames=*/2, controller_2->recommended_analog_level(),
/*gain_db=*/0,
/*speech_probability=*/0.49f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
ASSERT_EQ(controller_1->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_2->recommended_analog_level(), kInputVolume);
reader_1.Feed(/*num_frames=*/7, /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f,
*controller_1);
reader_2.Feed(/*num_frames=*/7, /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f,
*controller_2);
reader_1.Feed(
/*num_frames=*/7, controller_1->recommended_analog_level(), /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f, *controller_1);
reader_2.Feed(
/*num_frames=*/7, controller_2->recommended_analog_level(), /*gain_db=*/0,
/*speech_probability=*/0.5f, /*speech_level_dbfs=*/-42.0f, *controller_2);
ASSERT_GT(controller_1->recommended_analog_level(), kInputVolume);
ASSERT_EQ(controller_2->recommended_analog_level(), kInputVolume);
}
TEST(InputVolumeControllerTest,
DoNotLogRecommendedInputVolumeOnChangeToMatchTarget) {
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;
reader.Feed(/*num_frames=*/14, kStartupVolume, /*gain_db=*/50,
kHighSpeechProbability,
/*speech_level_dbfs=*/-20.0f, *controller);
ASSERT_LT(controller->recommended_analog_level(), kStartupVolume);
EXPECT_METRIC_THAT(
metrics::Samples(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget"),
::testing::IsEmpty());
}
TEST(InputVolumeControllerTest,
LogRecommendedInputVolumeOnUpwardChangeToMatchTarget) {
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.
reader.Feed(/*num_frames=*/14, kStartupVolume, /*gain_db=*/-6,
kHighSpeechProbability,
/*speech_level_dbfs=*/-50.0f, *controller);
ASSERT_GT(controller->recommended_analog_level(), kStartupVolume);
EXPECT_METRIC_THAT(
metrics::Samples(
"WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget"),
::testing::Not(::testing::IsEmpty()));
}
TEST(InputVolumeControllerTest,
LogRecommendedInputVolumeOnDownwardChangeToMatchTarget) {
SpeechSamplesReader reader;
auto controller = CreateInputVolumeController();
controller->Initialize();
constexpr int kStartupVolume = 100;
controller->set_stream_analog_level(kStartupVolume);
// Trigger a downward volume change by inputting audio that does not clip and
// by passing a speech level above the target range.
reader.Feed(/*num_frames=*/14, kStartupVolume, /*gain_db=*/-6,
kHighSpeechProbability,
/*speech_level_dbfs=*/-5.0f, *controller);
ASSERT_LT(controller->recommended_analog_level(), 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;

View File

@ -50,6 +50,16 @@ constexpr absl::string_view MetricNamePrefix(
}
}
metrics::Histogram* CreateVolumeHistogram(InputVolumeType input_volume_type) {
char buffer[64];
rtc::SimpleStringBuilder builder(buffer);
builder << MetricNamePrefix(input_volume_type) << "OnChange";
return metrics::HistogramFactoryGetCountsLinear(/*name=*/builder.str(),
/*min=*/1,
/*max=*/kMaxInputVolume,
/*bucket_count=*/50);
}
metrics::Histogram* CreateRateHistogram(InputVolumeType input_volume_type,
absl::string_view name) {
char buffer[64];
@ -76,7 +86,8 @@ metrics::Histogram* CreateAverageHistogram(InputVolumeType input_volume_type,
InputVolumeStatsReporter::InputVolumeStatsReporter(InputVolumeType type)
: histograms_(
{.decrease_rate = CreateRateHistogram(type, "DecreaseRate"),
{.on_volume_change = CreateVolumeHistogram(type),
.decrease_rate = CreateRateHistogram(type, "DecreaseRate"),
.decrease_average = CreateAverageHistogram(type, "DecreaseAverage"),
.increase_rate = CreateRateHistogram(type, "IncreaseRate"),
.increase_average = CreateAverageHistogram(type, "IncreaseAverage"),
@ -101,6 +112,9 @@ void InputVolumeStatsReporter::UpdateStatistics(int input_volume) {
RTC_DCHECK_LE(input_volume, kMaxInputVolume);
if (previous_input_volume_.has_value() &&
input_volume != previous_input_volume_.value()) {
// Update stats when the input volume changes.
metrics::HistogramAdd(histograms_.on_volume_change, input_volume);
// Update stats that are periodically logged.
const int volume_change = input_volume - previous_input_volume_.value();
if (volume_change < 0) {
++volume_update_stats_.num_decreases;

View File

@ -65,6 +65,7 @@ class InputVolumeStatsReporter {
// Histograms.
struct Histograms {
metrics::Histogram* const on_volume_change;
metrics::Histogram* const decrease_rate;
metrics::Histogram* const decrease_average;
metrics::Histogram* const increase_rate;
@ -72,8 +73,9 @@ class InputVolumeStatsReporter {
metrics::Histogram* const update_rate;
metrics::Histogram* const update_average;
bool AllPointersSet() const {
return !!decrease_rate && !!decrease_average && !!increase_rate &&
!!increase_average && !!update_rate && !!update_average;
return !!on_volume_change && !!decrease_rate && !!decrease_average &&
!!increase_rate && !!increase_average && !!update_rate &&
!!update_average;
}
} histograms_;

View File

@ -31,6 +31,10 @@ class InputVolumeStatsReporterTest
protected:
InputVolumeType InputVolumeType() const { return GetParam(); }
std::string VolumeLabel() const {
return (rtc::StringBuilder(kLabelPrefix) << VolumeTypeLabel() << "OnChange")
.str();
}
std::string DecreaseRateLabel() const {
return (rtc::StringBuilder(kLabelPrefix)
<< VolumeTypeLabel() << "DecreaseRate")
@ -73,7 +77,13 @@ class InputVolumeStatsReporterTest
}
};
TEST_P(InputVolumeStatsReporterTest, CheckLogVolumeUpdateStatsEmpty) {
TEST_P(InputVolumeStatsReporterTest, CheckVolumeOnChangeIsEmpty) {
InputVolumeStatsReporter stats_reporter(InputVolumeType());
stats_reporter.UpdateStatistics(10);
EXPECT_METRIC_THAT(metrics::Samples(VolumeLabel()), ::testing::ElementsAre());
}
TEST_P(InputVolumeStatsReporterTest, CheckRateAverageStatsEmpty) {
InputVolumeStatsReporter stats_reporter(InputVolumeType());
constexpr int kInputVolume = 10;
stats_reporter.UpdateStatistics(kInputVolume);
@ -96,20 +106,33 @@ TEST_P(InputVolumeStatsReporterTest, CheckLogVolumeUpdateStatsEmpty) {
::testing::ElementsAre());
}
TEST_P(InputVolumeStatsReporterTest, CheckLogVolumeUpdateStatsNotEmpty) {
TEST_P(InputVolumeStatsReporterTest, CheckSamples) {
InputVolumeStatsReporter stats_reporter(InputVolumeType());
constexpr int kInputVolume = 10;
stats_reporter.UpdateStatistics(kInputVolume);
constexpr int kInputVolume1 = 10;
stats_reporter.UpdateStatistics(kInputVolume1);
// Update until periodic logging.
constexpr int kInputVolume2 = 12;
for (int i = 0; i < kFramesIn60Seconds; i += 2) {
stats_reporter.UpdateStatistics(kInputVolume + 2);
stats_reporter.UpdateStatistics(kInputVolume);
stats_reporter.UpdateStatistics(kInputVolume2);
stats_reporter.UpdateStatistics(kInputVolume1);
}
// Update until periodic logging.
constexpr int kInputVolume3 = 13;
for (int i = 0; i < kFramesIn60Seconds; i += 2) {
stats_reporter.UpdateStatistics(kInputVolume + 3);
stats_reporter.UpdateStatistics(kInputVolume);
stats_reporter.UpdateStatistics(kInputVolume3);
stats_reporter.UpdateStatistics(kInputVolume1);
}
// Check volume changes stats.
EXPECT_METRIC_THAT(
metrics::Samples(VolumeLabel()),
::testing::ElementsAre(
::testing::Pair(kInputVolume1, kFramesIn60Seconds),
::testing::Pair(kInputVolume2, kFramesIn60Seconds / 2),
::testing::Pair(kInputVolume3, kFramesIn60Seconds / 2)));
// Check volume change rate stats.
EXPECT_METRIC_THAT(
metrics::Samples(UpdateRateLabel()),
::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds - 1, 1),
@ -121,6 +144,8 @@ TEST_P(InputVolumeStatsReporterTest, CheckLogVolumeUpdateStatsNotEmpty) {
EXPECT_METRIC_THAT(
metrics::Samples(IncreaseRateLabel()),
::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds / 2, 2)));
// Check volume change average stats.
EXPECT_METRIC_THAT(
metrics::Samples(UpdateAverageLabel()),
::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));