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:
parent
b81ab995a2
commit
361d1c3e5a
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user