AGC2 dummy module: fixed gain param, APM integration, audioproc_f adaptation

In preparation of coming CLs that will add an AGC interface to make the
gain controller injectable.

This CL simplifies AGC2 (dummy sub-module of audioproc_f) since it only
implements the fixed digital mode with hard-clipping - i.e., no limiter
is used.
The AGC2 config now includes the fixed gain to apply and audioproc_f
has been adapted accordingly.
Finally, this CL slightly simplifies the AGC2 integration into APM.

This CL is a continuation of https://codereview.webrtc.org/2995043002/

Bug: webrtc:7494
Change-Id: I3d554ea4dc6208928352059feb14987edabf14c7
Reviewed-on: https://webrtc-review.googlesource.com/4661
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Per Åhgren <peah@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20278}
This commit is contained in:
Alessio Bazzica
2017-10-13 11:05:17 +02:00
committed by Commit Bot
parent 65ceb6649f
commit 270f7b5353
12 changed files with 133 additions and 184 deletions

View File

@ -108,8 +108,6 @@ rtc_static_library("audio_processing") {
"agc/loudness_histogram.h",
"agc/utility.cc",
"agc/utility.h",
"agc2/digital_gain_applier.cc",
"agc2/digital_gain_applier.h",
"agc2/gain_controller2.cc",
"agc2/gain_controller2.h",
"audio_buffer.cc",

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/audio_processing/agc2/digital_gain_applier.h"
#include <algorithm>
namespace webrtc {
namespace {
constexpr float kMaxSampleValue = 32767.0f;
constexpr float kMinSampleValue = -32767.0f;
} // namespace
DigitalGainApplier::DigitalGainApplier() = default;
void DigitalGainApplier::Process(float gain, rtc::ArrayView<float> samples) {
if (gain == 1.f) { return; }
for (auto& v : samples) { v *= gain; }
LimitToAllowedRange(samples);
}
void DigitalGainApplier::LimitToAllowedRange(rtc::ArrayView<float> x) {
for (auto& v : x) {
v = std::max(kMinSampleValue, v);
v = std::min(kMaxSampleValue, v);
}
}
} // namespace webrtc

View File

@ -1,32 +0,0 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_AUDIO_PROCESSING_AGC2_DIGITAL_GAIN_APPLIER_H_
#define MODULES_AUDIO_PROCESSING_AGC2_DIGITAL_GAIN_APPLIER_H_
#include "api/array_view.h"
#include "modules/audio_processing/audio_buffer.h"
namespace webrtc {
class DigitalGainApplier {
public:
DigitalGainApplier();
// Applies the specified gain to an array of samples.
void Process(float gain, rtc::ArrayView<float> samples);
private:
void LimitToAllowedRange(rtc::ArrayView<float> x);
};
} // namespace webrtc
#endif // MODULES_AUDIO_PROCESSING_AGC2_DIGITAL_GAIN_APPLIER_H_

View File

@ -10,55 +10,65 @@
#include "modules/audio_processing/agc2/gain_controller2.h"
#include <cmath>
#include "modules/audio_processing/audio_buffer.h"
#include "modules/audio_processing/logging/apm_data_dumper.h"
#include "rtc_base/atomicops.h"
#include "rtc_base/checks.h"
#include "rtc_base/safe_minmax.h"
namespace webrtc {
namespace {
constexpr float kGain = 0.5f;
} // namespace
int GainController2::instance_count_ = 0;
GainController2::GainController2(int sample_rate_hz)
: sample_rate_hz_(sample_rate_hz),
data_dumper_(new ApmDataDumper(
rtc::AtomicOps::Increment(&instance_count_))),
digital_gain_applier_(),
gain_(kGain) {
RTC_DCHECK(sample_rate_hz_ == AudioProcessing::kSampleRate8kHz ||
sample_rate_hz_ == AudioProcessing::kSampleRate16kHz ||
sample_rate_hz_ == AudioProcessing::kSampleRate32kHz ||
sample_rate_hz_ == AudioProcessing::kSampleRate48kHz);
data_dumper_->InitiateNewSetOfRecordings();
data_dumper_->DumpRaw("gain_", 1, &gain_);
}
GainController2::GainController2()
: data_dumper_(
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
sample_rate_hz_(AudioProcessing::kSampleRate48kHz),
fixed_gain_(1.f) {}
GainController2::~GainController2() = default;
void GainController2::Process(AudioBuffer* audio) {
for (size_t k = 0; k < audio->num_channels(); ++k) {
auto channel_view = rtc::ArrayView<float>(
audio->channels_f()[k], audio->num_frames());
digital_gain_applier_.Process(gain_, channel_view);
void GainController2::Initialize(int sample_rate_hz) {
RTC_DCHECK(sample_rate_hz == AudioProcessing::kSampleRate8kHz ||
sample_rate_hz == AudioProcessing::kSampleRate16kHz ||
sample_rate_hz == AudioProcessing::kSampleRate32kHz ||
sample_rate_hz == AudioProcessing::kSampleRate48kHz);
sample_rate_hz_ = sample_rate_hz;
data_dumper_->InitiateNewSetOfRecordings();
data_dumper_->DumpRaw("sample_rate_hz", sample_rate_hz_);
data_dumper_->DumpRaw("fixed_gain_linear", fixed_gain_);
}
void GainController2::Process(AudioBuffer* audio) {
if (fixed_gain_ == 1.f)
return;
for (size_t k = 0; k < audio->num_channels(); ++k) {
for (size_t j = 0; j < audio->num_frames(); ++j) {
audio->channels_f()[k][j] = rtc::SafeClamp(
fixed_gain_ * audio->channels_f()[k][j], -32768.f, 32767.f);
}
}
}
void GainController2::ApplyConfig(
const AudioProcessing::Config::GainController2& config) {
RTC_DCHECK(Validate(config));
fixed_gain_ = std::pow(10.f, config.fixed_gain_db / 20.f);
}
bool GainController2::Validate(
const AudioProcessing::Config::GainController2& config) {
return true;
return config.fixed_gain_db >= 0.f;
}
std::string GainController2::ToString(
const AudioProcessing::Config::GainController2& config) {
std::stringstream ss;
ss << "{"
<< "enabled: " << (config.enabled ? "true" : "false") << "}";
ss << "{enabled: " << (config.enabled ? "true" : "false") << ", "
<< "fixed_gain_dB: " << config.fixed_gain_db << "}";
return ss.str();
}

View File

@ -14,7 +14,6 @@
#include <memory>
#include <string>
#include "modules/audio_processing/agc2/digital_gain_applier.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "rtc_base/constructormagic.h"
@ -26,28 +25,26 @@ class AudioBuffer;
// Gain Controller 2 aims to automatically adjust levels by acting on the
// microphone gain and/or applying digital gain.
//
// It temporarily implements a hard-coded gain mode only.
// Temporarily implements a fixed gain mode with hard-clipping.
class GainController2 {
public:
explicit GainController2(int sample_rate_hz);
GainController2();
~GainController2();
int sample_rate_hz() { return sample_rate_hz_; }
void Initialize(int sample_rate_hz);
void Process(AudioBuffer* audio);
void ApplyConfig(const AudioProcessing::Config::GainController2& config);
static bool Validate(const AudioProcessing::Config::GainController2& config);
static std::string ToString(
const AudioProcessing::Config::GainController2& config);
private:
int sample_rate_hz_;
std::unique_ptr<ApmDataDumper> data_dumper_;
DigitalGainApplier digital_gain_applier_;
static int instance_count_;
// TODO(alessiob): Remove once a meaningful gain controller mode is
// implemented.
const float gain_;
std::unique_ptr<ApmDataDumper> data_dumper_;
int sample_rate_hz_;
float fixed_gain_;
RTC_DISALLOW_COPY_AND_ASSIGN(GainController2);
};

View File

@ -8,13 +8,12 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <string>
#include <algorithm>
#include "api/array_view.h"
#include "modules/audio_processing/agc2/digital_gain_applier.h"
#include "modules/audio_processing/agc2/gain_controller2.h"
#include "modules/audio_processing/audio_buffer.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"
namespace webrtc {
@ -22,77 +21,76 @@ namespace test {
namespace {
constexpr size_t kNumFrames = 480u;
constexpr size_t kFrameSizeMs = 10u;
constexpr size_t kStereo = 2u;
void SetAudioBufferSamples(float value, AudioBuffer* ab) {
// Sets all the samples in |ab| to |value|.
for (size_t k = 0; k < ab->num_channels(); ++k) {
auto channel = rtc::ArrayView<float>(ab->channels_f()[k], ab->num_frames());
for (auto& sample : channel) { sample = value; }
std::fill(ab->channels_f()[k], ab->channels_f()[k] + ab->num_frames(),
value);
}
}
template<typename Functor>
bool CheckAudioBufferSamples(Functor validator, AudioBuffer* ab) {
for (size_t k = 0; k < ab->num_channels(); ++k) {
auto channel = rtc::ArrayView<float>(ab->channels_f()[k], ab->num_frames());
for (auto& sample : channel) { if (!validator(sample)) { return false; } }
}
return true;
}
bool TestDigitalGainApplier(float sample_value, float gain, float expected) {
AudioBuffer ab(kNumFrames, kStereo, kNumFrames, kStereo, kNumFrames);
SetAudioBufferSamples(sample_value, &ab);
DigitalGainApplier gain_applier;
for (size_t k = 0; k < ab.num_channels(); ++k) {
auto channel_view = rtc::ArrayView<float>(
ab.channels_f()[k], ab.num_frames());
gain_applier.Process(gain, channel_view);
}
auto check_expectation = [expected](float sample) {
return sample == expected; };
return CheckAudioBufferSamples(check_expectation, &ab);
}
} // namespace
TEST(GainController2, Instance) {
std::unique_ptr<GainController2> gain_controller2;
gain_controller2.reset(new GainController2(
AudioProcessing::kSampleRate48kHz));
TEST(GainController2, CreateApplyConfig) {
// Instances GainController2 and applies different configurations.
std::unique_ptr<GainController2> gain_controller2(new GainController2());
// Check that the default config is valid.
AudioProcessing::Config::GainController2 config;
EXPECT_TRUE(GainController2::Validate(config));
gain_controller2->ApplyConfig(config);
// Check that attenuation is not allowed.
config.fixed_gain_db = -5.f;
EXPECT_FALSE(GainController2::Validate(config));
// Check that valid configurations are applied.
for (const float& fixed_gain_db : {0.f, 5.f, 10.f, 50.f}) {
config.fixed_gain_db = fixed_gain_db;
EXPECT_TRUE(GainController2::Validate(config));
gain_controller2->ApplyConfig(config);
}
}
TEST(GainController2, ToString) {
AudioProcessing::Config config;
// Tests GainController2::ToString().
AudioProcessing::Config::GainController2 config;
config.fixed_gain_db = 5.f;
config.gain_controller2.enabled = false;
EXPECT_EQ("{enabled: false}",
GainController2::ToString(config.gain_controller2));
config.enabled = false;
EXPECT_EQ("{enabled: false, fixed_gain_dB: 5}",
GainController2::ToString(config));
config.gain_controller2.enabled = true;
EXPECT_EQ("{enabled: true}",
GainController2::ToString(config.gain_controller2));
}
TEST(GainController2, DigitalGainApplierProcess) {
EXPECT_TRUE(TestDigitalGainApplier(1000.0f, 0.5, 500.0f));
}
TEST(GainController2, DigitalGainApplierCheckClipping) {
EXPECT_TRUE(TestDigitalGainApplier(30000.0f, 1.5, 32767.0f));
EXPECT_TRUE(TestDigitalGainApplier(-30000.0f, 1.5, -32767.0f));
config.enabled = true;
EXPECT_EQ("{enabled: true, fixed_gain_dB: 5}",
GainController2::ToString(config));
}
TEST(GainController2, Usage) {
std::unique_ptr<GainController2> gain_controller2;
gain_controller2.reset(new GainController2(
AudioProcessing::kSampleRate48kHz));
AudioBuffer ab(kNumFrames, kStereo, kNumFrames, kStereo, kNumFrames);
SetAudioBufferSamples(1000.0f, &ab);
// Tests GainController2::Process() on an AudioBuffer instance.
std::unique_ptr<GainController2> gain_controller2(new GainController2());
gain_controller2->Initialize(AudioProcessing::kSampleRate48kHz);
const size_t num_frames = rtc::CheckedDivExact<size_t>(
kFrameSizeMs * AudioProcessing::kSampleRate48kHz, 1000);
AudioBuffer ab(num_frames, kStereo, num_frames, kStereo, num_frames);
constexpr float sample_value = 1000.f;
SetAudioBufferSamples(sample_value, &ab);
AudioProcessing::Config::GainController2 config;
// Check that samples are not modified when the fixed gain is 0 dB.
ASSERT_EQ(config.fixed_gain_db, 0.f);
gain_controller2->ApplyConfig(config);
gain_controller2->Process(&ab);
EXPECT_EQ(ab.channels_f()[0][0], sample_value);
// Check that samples are amplified when the fixed gain is greater than 0 dB.
config.fixed_gain_db = 5.f;
gain_controller2->ApplyConfig(config);
gain_controller2->Process(&ab);
EXPECT_LT(sample_value, ab.channels_f()[0][0]);
}
} // namespace test

View File

@ -253,7 +253,8 @@ bool AudioProcessingImpl::ApmSubmoduleStates::CaptureMultiBandProcessingActive()
bool AudioProcessingImpl::ApmSubmoduleStates::CaptureFullBandProcessingActive()
const {
return level_controller_enabled_ || capture_post_processor_enabled_;
return level_controller_enabled_ || gain_controller2_enabled_ ||
capture_post_processor_enabled_;
}
bool AudioProcessingImpl::ApmSubmoduleStates::RenderMultiBandSubModulesActive()
@ -401,6 +402,10 @@ AudioProcessingImpl::AudioProcessingImpl(
// is enabled.
private_submodules_->level_controller.reset(new LevelController());
// TODO(alessiob): Move the injected gain controller once injection is
// implemented.
private_submodules_->gain_controller2.reset(new GainController2());
LOG(LS_INFO) << "Capture post processor activated: "
<< !!private_submodules_->capture_post_processor;
}
@ -714,21 +719,16 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
config_ok = GainController2::Validate(config_.gain_controller2);
if (!config_ok) {
LOG(LS_ERROR) << "AudioProcessing module config error" << std::endl
<< "gain_controller2: "
<< "Gain Controller 2: "
<< GainController2::ToString(config_.gain_controller2)
<< std::endl
<< "Reverting to default parameter set";
config_.gain_controller2 = AudioProcessing::Config::GainController2();
}
if (config.gain_controller2.enabled !=
capture_nonlocked_.gain_controller2_enabled) {
capture_nonlocked_.gain_controller2_enabled =
config_.gain_controller2.enabled;
InitializeGainController2();
LOG(LS_INFO) << "Gain controller 2 activated: "
<< capture_nonlocked_.gain_controller2_enabled;
}
private_submodules_->gain_controller2->ApplyConfig(config_.gain_controller2);
LOG(LS_INFO) << "Gain Controller 2 activated: "
<< config_.gain_controller2.enabled;
}
void AudioProcessingImpl::SetExtraOptions(const webrtc::Config& config) {
@ -1305,7 +1305,7 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() {
capture_.key_pressed);
}
if (capture_nonlocked_.gain_controller2_enabled) {
if (config_.gain_controller2.enabled) {
private_submodules_->gain_controller2->Process(capture_buffer);
}
@ -1657,7 +1657,7 @@ bool AudioProcessingImpl::UpdateActiveSubmoduleStates() {
capture_nonlocked_.intelligibility_enabled,
capture_nonlocked_.beamformer_enabled,
public_submodules_->gain_control->is_enabled(),
capture_nonlocked_.gain_controller2_enabled,
config_.gain_controller2.enabled,
capture_nonlocked_.level_controller_enabled,
capture_nonlocked_.echo_canceller3_enabled,
public_submodules_->voice_detection->is_enabled(),
@ -1723,11 +1723,8 @@ void AudioProcessingImpl::InitializeEchoCanceller3() {
}
void AudioProcessingImpl::InitializeGainController2() {
if (capture_nonlocked_.gain_controller2_enabled) {
private_submodules_->gain_controller2.reset(
new GainController2(proc_sample_rate_hz()));
} else {
private_submodules_->gain_controller2.reset();
if (config_.gain_controller2.enabled) {
private_submodules_->gain_controller2->Initialize(proc_sample_rate_hz());
}
}
@ -1750,7 +1747,7 @@ void AudioProcessingImpl::MaybeUpdateHistograms() {
static const int kMinDiffDelayMs = 60;
if (echo_cancellation()->is_enabled()) {
// Activate delay_jumps_ counters if we know echo_cancellation is runnning.
// Activate delay_jumps_ counters if we know echo_cancellation is running.
// If a stream has echo we know that the echo_cancellation is in process.
if (capture_.stream_delay_jumps == -1 &&
echo_cancellation()->stream_has_echo()) {
@ -1836,6 +1833,9 @@ void AudioProcessingImpl::WriteAecDumpConfigMessage(bool forced) {
if (capture_nonlocked_.echo_canceller3_enabled) {
experiments_description += "EchoCanceller3;";
}
if (config_.gain_controller2.enabled) {
experiments_description += "GainController2;";
}
InternalAPMConfig apm_config;

View File

@ -374,7 +374,6 @@ class AudioProcessingImpl : public AudioProcessing {
bool intelligibility_enabled;
bool level_controller_enabled = false;
bool echo_canceller3_enabled = false;
bool gain_controller2_enabled = false;
} capture_nonlocked_;
struct ApmRenderState {

View File

@ -340,6 +340,7 @@ class AudioProcessing : public rtc::RefCountInterface {
// does not yet have the desired behavior.
struct GainController2 {
bool enabled = false;
float fixed_gain_db = 0.f;
} gain_controller2;
// Explicit copy assignment implementation to avoid issues with memory

View File

@ -323,6 +323,7 @@ void AudioProcessingSimulator::CreateAudioProcessor() {
}
if (settings_.use_agc2) {
apm_config.gain_controller2.enabled = *settings_.use_agc2;
apm_config.gain_controller2.fixed_gain_db = settings_.agc2_fixed_gain_db;
}
if (settings_.use_lc) {
apm_config.level_controller.enabled = *settings_.use_lc;

View File

@ -74,6 +74,7 @@ struct SimulationSettings {
rtc::Optional<int> agc_target_level;
rtc::Optional<bool> use_agc_limiter;
rtc::Optional<int> agc_compression_gain;
float agc2_fixed_gain_db;
rtc::Optional<int> vad_likelihood;
rtc::Optional<int> ns_level;
rtc::Optional<bool> use_refined_adaptive_filter;

View File

@ -149,6 +149,7 @@ DEFINE_int(agc_limiter,
DEFINE_int(agc_compression_gain,
kParameterNotSpecifiedValue,
"Specify the AGC compression gain (0-90)");
DEFINE_float(agc2_fixed_gain_db, 0.f, "AGC2 fixed gain (dB) to apply");
DEFINE_int(vad_likelihood,
kParameterNotSpecifiedValue,
"Specify the VAD likelihood (0-3)");
@ -214,6 +215,7 @@ SimulationSettings CreateSettings() {
settings.use_ns = rtc::Optional<bool>(true);
settings.use_hpf = rtc::Optional<bool>(true);
settings.use_agc = rtc::Optional<bool>(true);
settings.use_agc2 = rtc::Optional<bool>(false);
settings.use_aec = rtc::Optional<bool>(true);
settings.use_aecm = rtc::Optional<bool>(false);
settings.use_ed = rtc::Optional<bool>(false);
@ -269,6 +271,7 @@ SimulationSettings CreateSettings() {
SetSettingIfFlagSet(FLAG_agc_limiter, &settings.use_agc_limiter);
SetSettingIfSpecified(FLAG_agc_compression_gain,
&settings.agc_compression_gain);
settings.agc2_fixed_gain_db = FLAG_agc2_fixed_gain_db;
SetSettingIfSpecified(FLAG_vad_likelihood, &settings.vad_likelihood);
SetSettingIfSpecified(FLAG_ns_level, &settings.ns_level);
SetSettingIfSpecified(FLAG_stream_delay, &settings.stream_delay);
@ -375,6 +378,17 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) {
(*settings.agc_compression_gain) > 90),
"Error: --agc_compression_gain must be specified between 0 and 90.\n");
ReportConditionalErrorAndExit(
settings.use_agc && *settings.use_agc && settings.use_agc2 &&
*settings.use_agc2,
"Error: --agc and --agc2 cannot be both active.\n");
ReportConditionalErrorAndExit(
settings.use_agc2 && *settings.use_agc2 &&
((settings.agc2_fixed_gain_db) < 0 ||
(settings.agc2_fixed_gain_db) > 90),
"Error: --agc2_fixed_gain_db must be specified between 0 and 90.\n");
ReportConditionalErrorAndExit(
settings.vad_likelihood &&
((*settings.vad_likelihood) < 0 || (*settings.vad_likelihood) > 3),