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}
This commit is contained in:
Per Åhgren 2019-11-06 22:17:14 +01:00 committed by Commit Bot
parent b81ab995a2
commit 361d1c3e5a
7 changed files with 63 additions and 97 deletions

View File

@ -33,17 +33,7 @@ Agc::Agc()
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);

View File

@ -24,9 +24,6 @@ class Agc {
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);

View File

@ -141,6 +141,24 @@ int InitializeGainControl(GainControl* gain_control,
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 @@ int AgcManagerDirect::Initialize() {
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 @@ void AgcManagerDirect::AnalyzePreProcess(float* audio,
// 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 @@ void AgcManagerDirect::AnalyzePreProcess(float* audio,
}
frames_since_clipped_ = 0;
}
if (audio) {
S16ToFloatS16(audio_fix, safe_length, audio);
}
}
void AgcManagerDirect::Process(const float* audio,

View File

@ -56,7 +56,7 @@ class AgcManagerDirect final {
~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);

View File

@ -71,9 +71,14 @@ class AgcManagerDirectTest : public ::testing::Test {
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 @@ class AgcManagerDirectTest : public ::testing::Test {
}
}
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 @@ class AgcManagerDirectTest : public ::testing::Test {
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, RecoveryAfterManualLevelChangeBelowMin) {
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, ClippingMaxIsRespectedWhenEqualToLevel) {
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, ClippingMaxIsRespectedWhenHigherThanLevel) {
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 @@ TEST_F(AgcManagerDirectTest, MaxCompressionIsIncreasedAfterClipping) {
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, MaxCompressionIsIncreasedAfterClipping) {
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, UserCanRaiseVolumeAfterClipping) {
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());
}

View File

@ -19,7 +19,6 @@ namespace webrtc {
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));

View File

@ -1278,7 +1278,7 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() {
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) {