Simplifications/refactoring of the analog AGC to make it multichannel
This CL prepares parts the analog AGC code to make it properly
multichannel.
Bug: webrtc:10859
Change-Id: I693d0d004dd2c7495ebdc60a43e9a53a441a93e0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158896
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29718}
diff --git a/modules/audio_processing/agc/agc.cc b/modules/audio_processing/agc/agc.cc
index c24db0d..a89ae11 100644
--- a/modules/audio_processing/agc/agc.cc
+++ b/modules/audio_processing/agc/agc.cc
@@ -33,17 +33,7 @@
histogram_(LoudnessHistogram::Create(kNumAnalysisFrames)),
inactive_histogram_(LoudnessHistogram::Create()) {}
-Agc::~Agc() {}
-
-float Agc::AnalyzePreproc(const int16_t* audio, size_t length) {
- RTC_DCHECK_GT(length, 0);
- size_t num_clipped = 0;
- for (size_t i = 0; i < length; ++i) {
- if (audio[i] == 32767 || audio[i] == -32768)
- ++num_clipped;
- }
- return 1.0f * num_clipped / length;
-}
+Agc::~Agc() = default;
void Agc::Process(const int16_t* audio, size_t length, int sample_rate_hz) {
vad_.ProcessChunk(audio, length, sample_rate_hz);
diff --git a/modules/audio_processing/agc/agc.h b/modules/audio_processing/agc/agc.h
index abd68d5..b9bd5ea 100644
--- a/modules/audio_processing/agc/agc.h
+++ b/modules/audio_processing/agc/agc.h
@@ -24,9 +24,6 @@
Agc();
virtual ~Agc();
- // Returns the proportion of samples in the buffer which are at full-scale
- // (and presumably clipped).
- virtual float AnalyzePreproc(const int16_t* audio, size_t length);
// |audio| must be mono; in a multi-channel stream, provide the first (usually
// left) channel.
virtual void Process(const int16_t* audio, size_t length, int sample_rate_hz);
diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc
index cc0b482..2a6b800 100644
--- a/modules/audio_processing/agc/agc_manager_direct.cc
+++ b/modules/audio_processing/agc/agc_manager_direct.cc
@@ -141,6 +141,24 @@
return 0;
}
+// Returns the proportion of samples in the buffer which are at full-scale
+// (and presumably clipped).
+float ComputeClippedRatio(const float* const* audio,
+ size_t num_channels,
+ size_t samples_per_channel) {
+ RTC_DCHECK_GT(num_channels * samples_per_channel, 0);
+ int num_clipped = 0;
+ for (size_t ch = 0; ch < num_channels; ++ch) {
+ for (size_t i = 0; i < samples_per_channel; ++i) {
+ RTC_DCHECK(audio[ch]);
+ if (audio[ch][i] >= 32767.f || audio[ch][i] <= -32768.f) {
+ ++num_clipped;
+ }
+ }
+ }
+ return static_cast<float>(num_clipped) / (num_channels * samples_per_channel);
+}
+
} // namespace
// Facility for dumping debug audio files. All methods are no-ops in the
@@ -253,28 +271,14 @@
return InitializeGainControl(gctrl_, disable_digital_adaptive_);
}
-void AgcManagerDirect::AnalyzePreProcess(float* audio,
+void AgcManagerDirect::AnalyzePreProcess(const float* const* audio,
int num_channels,
size_t samples_per_channel) {
- size_t length = num_channels * samples_per_channel;
+ RTC_DCHECK(audio);
if (capture_muted_) {
return;
}
- std::array<int16_t, kMaxNumSamplesPerChannel * kMaxNumChannels> audio_data;
- int16_t* audio_fix;
- size_t safe_length;
- if (audio) {
- audio_fix = audio_data.data();
- safe_length = std::min(audio_data.size(), length);
- FloatS16ToS16(audio, length, audio_fix);
- } else {
- audio_fix = nullptr;
- safe_length = length;
- }
-
- file_preproc_->Write(audio_fix, safe_length);
-
if (frames_since_clipped_ < kClippedWaitFrames) {
++frames_since_clipped_;
return;
@@ -289,7 +293,8 @@
// maximum. This harsh treatment is an effort to avoid repeated clipped echo
// events. As compensation for this restriction, the maximum compression
// gain is increased, through SetMaxLevel().
- float clipped_ratio = agc_->AnalyzePreproc(audio_fix, safe_length);
+ float clipped_ratio =
+ ComputeClippedRatio(audio, num_channels, samples_per_channel);
if (clipped_ratio > kClippedRatioThreshold) {
RTC_DLOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio="
<< clipped_ratio;
@@ -308,10 +313,6 @@
}
frames_since_clipped_ = 0;
}
-
- if (audio) {
- S16ToFloatS16(audio_fix, safe_length, audio);
- }
}
void AgcManagerDirect::Process(const float* audio,
diff --git a/modules/audio_processing/agc/agc_manager_direct.h b/modules/audio_processing/agc/agc_manager_direct.h
index ddb14e5..69bc358 100644
--- a/modules/audio_processing/agc/agc_manager_direct.h
+++ b/modules/audio_processing/agc/agc_manager_direct.h
@@ -56,7 +56,7 @@
~AgcManagerDirect();
int Initialize();
- void AnalyzePreProcess(float* audio,
+ void AnalyzePreProcess(const float* const* audio,
int num_channels,
size_t samples_per_channel);
void Process(const float* audio, size_t length, int sample_rate_hz);
diff --git a/modules/audio_processing/agc/agc_manager_direct_unittest.cc b/modules/audio_processing/agc/agc_manager_direct_unittest.cc
index faab5c0..41f1904 100644
--- a/modules/audio_processing/agc/agc_manager_direct_unittest.cc
+++ b/modules/audio_processing/agc/agc_manager_direct_unittest.cc
@@ -71,9 +71,14 @@
protected:
AgcManagerDirectTest()
: agc_(new MockAgc),
- manager_(agc_, &gctrl_, &volume_, kInitialVolume, kClippedMin) {
+ manager_(agc_, &gctrl_, &volume_, kInitialVolume, kClippedMin),
+ audio(kNumChannels),
+ audio_data(kNumChannels * kSamplesPerChannel, 0.f) {
ExpectInitialize();
manager_.Initialize();
+ for (size_t ch = 0; ch < kNumChannels; ++ch) {
+ audio[ch] = &audio_data[ch * kSamplesPerChannel];
+ }
}
void FirstProcess() {
@@ -106,9 +111,14 @@
}
}
- void CallPreProc(int num_calls) {
+ void CallPreProc(int num_calls, float clipped_ratio) {
+ RTC_DCHECK_GE(1.f, clipped_ratio);
+ int num_clipped = kNumChannels * kSamplesPerChannel * clipped_ratio;
+ std::fill(audio_data.begin(), audio_data.begin() + num_clipped, 32767.f);
+
for (int i = 0; i < num_calls; ++i) {
- manager_.AnalyzePreProcess(nullptr, kNumChannels, kSamplesPerChannel);
+ manager_.AnalyzePreProcess(audio.data(), kNumChannels,
+ kSamplesPerChannel);
}
}
@@ -116,6 +126,8 @@
MockGainControl gctrl_;
TestVolumeCallbacks volume_;
AgcManagerDirect manager_;
+ std::vector<float*> audio;
+ std::vector<float> audio_data;
};
TEST_F(AgcManagerDirectTest, StartupMinVolumeConfigurationIsRespected) {
@@ -477,73 +489,58 @@
TEST_F(AgcManagerDirectTest, NoClippingHasNoImpact) {
FirstProcess();
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _)).WillRepeatedly(Return(0));
- CallPreProc(100);
+ CallPreProc(100, 0);
EXPECT_EQ(128, volume_.GetMicVolume());
}
TEST_F(AgcManagerDirectTest, ClippingUnderThresholdHasNoImpact) {
FirstProcess();
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _)).WillOnce(Return(0.099));
- CallPreProc(1);
+ CallPreProc(1, 0.099);
EXPECT_EQ(128, volume_.GetMicVolume());
}
TEST_F(AgcManagerDirectTest, ClippingLowersVolume) {
SetVolumeAndProcess(255);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _)).WillOnce(Return(0.101));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, 0.2);
EXPECT_EQ(240, volume_.GetMicVolume());
}
TEST_F(AgcManagerDirectTest, WaitingPeriodBetweenClippingChecks) {
SetVolumeAndProcess(255);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(240, volume_.GetMicVolume());
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillRepeatedly(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(0);
- CallPreProc(300);
+ CallPreProc(300, kAboveClippedThreshold);
EXPECT_EQ(240, volume_.GetMicVolume());
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(225, volume_.GetMicVolume());
}
TEST_F(AgcManagerDirectTest, ClippingLoweringIsLimited) {
SetVolumeAndProcess(180);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillRepeatedly(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(0);
- CallPreProc(1000);
+ CallPreProc(1000, kAboveClippedThreshold);
EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
}
TEST_F(AgcManagerDirectTest, ClippingMaxIsRespectedWhenEqualToLevel) {
SetVolumeAndProcess(255);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(240, volume_.GetMicVolume());
EXPECT_CALL(*agc_, GetRmsErrorDb(_))
@@ -555,10 +552,8 @@
TEST_F(AgcManagerDirectTest, ClippingMaxIsRespectedWhenHigherThanLevel) {
SetVolumeAndProcess(200);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(185, volume_.GetMicVolume());
EXPECT_CALL(*agc_, GetRmsErrorDb(_))
@@ -572,10 +567,8 @@
TEST_F(AgcManagerDirectTest, MaxCompressionIsIncreasedAfterClipping) {
SetVolumeAndProcess(210);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(195, volume_.GetMicVolume());
EXPECT_CALL(*agc_, GetRmsErrorDb(_))
@@ -600,36 +593,26 @@
CallProcess(1);
// Continue clipping until we hit the maximum surplus compression.
- CallPreProc(300);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
+ CallPreProc(300, kAboveClippedThreshold);
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(180, volume_.GetMicVolume());
- CallPreProc(300);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
+ CallPreProc(300, kAboveClippedThreshold);
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
// Current level is now at the minimum, but the maximum allowed level still
// has more to decrease.
- CallPreProc(300);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
- CallPreProc(1);
+ CallPreProc(300, kAboveClippedThreshold);
+ CallPreProc(1, kAboveClippedThreshold);
- CallPreProc(300);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
- CallPreProc(1);
+ CallPreProc(300, kAboveClippedThreshold);
+ CallPreProc(1, kAboveClippedThreshold);
- CallPreProc(300);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
- CallPreProc(1);
+ CallPreProc(300, kAboveClippedThreshold);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_CALL(*agc_, GetRmsErrorDb(_))
.WillOnce(DoAll(SetArgPointee<0>(16), Return(true)))
@@ -653,10 +636,8 @@
TEST_F(AgcManagerDirectTest, UserCanRaiseVolumeAfterClipping) {
SetVolumeAndProcess(225);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(210, volume_.GetMicVolume());
// High enough error to trigger a volume check.
@@ -688,11 +669,9 @@
TEST_F(AgcManagerDirectTest, ClippingDoesNotPullLowVolumeBackUp) {
SetVolumeAndProcess(80);
- EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
- .WillOnce(Return(kAboveClippedThreshold));
EXPECT_CALL(*agc_, Reset()).Times(0);
int initial_volume = volume_.GetMicVolume();
- CallPreProc(1);
+ CallPreProc(1, kAboveClippedThreshold);
EXPECT_EQ(initial_volume, volume_.GetMicVolume());
}
diff --git a/modules/audio_processing/agc/mock_agc.h b/modules/audio_processing/agc/mock_agc.h
index d31c265..6542acc 100644
--- a/modules/audio_processing/agc/mock_agc.h
+++ b/modules/audio_processing/agc/mock_agc.h
@@ -19,7 +19,6 @@
class MockAgc : public Agc {
public:
virtual ~MockAgc() {}
- MOCK_METHOD2(AnalyzePreproc, float(const int16_t* audio, size_t length));
MOCK_METHOD3(Process,
void(const int16_t* audio, size_t length, int sample_rate_hz));
MOCK_METHOD1(GetRmsErrorDb, bool(int* error));
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index fb46e04..cab7677 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1278,7 +1278,7 @@
if (constants_.use_experimental_agc &&
submodules_.gain_control->is_enabled()) {
submodules_.agc_manager->AnalyzePreProcess(
- capture_buffer->channels_f()[0], capture_buffer->num_channels(),
+ capture_buffer->channels_const(), capture_buffer->num_channels(),
capture_nonlocked_.capture_processing_format.num_frames());
if (constants_.use_experimental_agc_process_before_aec) {