diff --git a/modules/audio_processing/agc/agc.cc b/modules/audio_processing/agc/agc.cc index c24db0dd52..a89ae111ea 100644 --- a/modules/audio_processing/agc/agc.cc +++ b/modules/audio_processing/agc/agc.cc @@ -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); diff --git a/modules/audio_processing/agc/agc.h b/modules/audio_processing/agc/agc.h index abd68d5e31..b9bd5ea07b 100644 --- a/modules/audio_processing/agc/agc.h +++ b/modules/audio_processing/agc/agc.h @@ -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); diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc index cc0b482732..2a6b80021a 100644 --- a/modules/audio_processing/agc/agc_manager_direct.cc +++ b/modules/audio_processing/agc/agc_manager_direct.cc @@ -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(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 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, diff --git a/modules/audio_processing/agc/agc_manager_direct.h b/modules/audio_processing/agc/agc_manager_direct.h index ddb14e5b51..69bc358e38 100644 --- a/modules/audio_processing/agc/agc_manager_direct.h +++ b/modules/audio_processing/agc/agc_manager_direct.h @@ -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); diff --git a/modules/audio_processing/agc/agc_manager_direct_unittest.cc b/modules/audio_processing/agc/agc_manager_direct_unittest.cc index faab5c0f8c..41f1904bf6 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 @@ 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 audio; + std::vector 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()); } diff --git a/modules/audio_processing/agc/mock_agc.h b/modules/audio_processing/agc/mock_agc.h index d31c2650a2..6542acc8d5 100644 --- a/modules/audio_processing/agc/mock_agc.h +++ b/modules/audio_processing/agc/mock_agc.h @@ -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)); diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index fb46e04577..cab7677897 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -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) {