Pre-amplification in the audio processing module.

Added a new sub-module 'GainApplier'. The build target is
'modules/audio_processing/agc2:gain_applier'. A small refactoring
makes the GainApplier used in adaptive-digital AGC2.

The AGC2 now multiplies samples with a gain in 3 places. It's the
GainApplier, the GainCurveApplier, and the FixedGainController. The
GainApplier is used in AdaptiveDigitalGainApplier and will be used as
a pre-amplifier.

Bug: webrtc:9138
Change-Id: Ibc4c0ea109c6757f159d4adb6e3d8614179c9bc6
Reviewed-on: https://webrtc-review.googlesource.com/69321
Commit-Queue: Alex Loiko <aleloi@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22849}
This commit is contained in:
Alex Loiko 2018-04-13 11:15:34 +02:00 committed by Commit Bot
parent aaa85ae565
commit 8a3eaddc95
7 changed files with 262 additions and 54 deletions

View File

@ -31,6 +31,7 @@ rtc_source_set("adaptive_digital") {
deps = [ deps = [
":common", ":common",
":gain_applier",
":noise_level_estimator", ":noise_level_estimator",
"..:aec_core", "..:aec_core",
"..:apm_logging", "..:apm_logging",
@ -108,6 +109,18 @@ rtc_source_set("noise_level_estimator") {
configs += [ "..:apm_debug_dump" ] configs += [ "..:apm_debug_dump" ]
} }
rtc_source_set("gain_applier") {
sources = [
"gain_applier.cc",
"gain_applier.h",
]
deps = [
":common",
"..:audio_frame_view",
"../../../rtc_base:safe_minmax",
]
}
rtc_source_set("test_utils") { rtc_source_set("test_utils") {
testonly = true testonly = true
visibility = [ ":*" ] visibility = [ ":*" ]
@ -161,11 +174,13 @@ rtc_source_set("adaptive_digital_unittests") {
sources = [ sources = [
"adaptive_digital_gain_applier_unittest.cc", "adaptive_digital_gain_applier_unittest.cc",
"adaptive_mode_level_estimator_unittest.cc", "adaptive_mode_level_estimator_unittest.cc",
"gain_applier_unittest.cc",
"saturation_protector_unittest.cc", "saturation_protector_unittest.cc",
] ]
deps = [ deps = [
":adaptive_digital", ":adaptive_digital",
":common", ":common",
":gain_applier",
":test_utils", ":test_utils",
"..:apm_logging", "..:apm_logging",
"..:audio_frame_view", "..:audio_frame_view",

View File

@ -64,52 +64,13 @@ float ComputeGainChangeThisFrameDb(float target_gain_db,
return rtc::SafeClamp(target_gain_difference_db, -kMaxGainChangePerFrameDb, return rtc::SafeClamp(target_gain_difference_db, -kMaxGainChangePerFrameDb,
kMaxGainChangePerFrameDb); kMaxGainChangePerFrameDb);
} }
// Returns true when the gain factor is so close to 1 that it would
// not affect int16 samples.
bool GainCloseToOne(float gain_factor) {
return 1.f - 1.f / kMaxFloatS16Value <= gain_factor &&
gain_factor <= 1.f + 1.f / kMaxFloatS16Value;
}
void ApplyGainWithRamping(float last_gain_linear,
float gain_at_end_of_frame_linear,
AudioFrameView<float> float_frame) {
// Do not modify the signal when input is loud.
if (last_gain_linear == gain_at_end_of_frame_linear &&
GainCloseToOne(gain_at_end_of_frame_linear)) {
return;
}
// A typical case: gain is constant and different from 1.
if (last_gain_linear == gain_at_end_of_frame_linear) {
for (size_t k = 0; k < float_frame.num_channels(); ++k) {
rtc::ArrayView<float> channel_view = float_frame.channel(k);
for (auto& sample : channel_view) {
sample *= gain_at_end_of_frame_linear;
}
}
return;
}
// The gain changes. We have to change slowly to avoid discontinuities.
const size_t samples = float_frame.samples_per_channel();
RTC_DCHECK_GT(samples, 0);
const float increment =
(gain_at_end_of_frame_linear - last_gain_linear) / samples;
float gain = last_gain_linear;
for (size_t i = 0; i < samples; ++i) {
for (size_t ch = 0; ch < float_frame.num_channels(); ++ch) {
float_frame.channel(ch)[i] *= gain;
}
gain += increment;
}
}
} // namespace } // namespace
AdaptiveDigitalGainApplier::AdaptiveDigitalGainApplier( AdaptiveDigitalGainApplier::AdaptiveDigitalGainApplier(
ApmDataDumper* apm_data_dumper) ApmDataDumper* apm_data_dumper)
: apm_data_dumper_(apm_data_dumper) {} : gain_applier_(false, 1.f), // Initial gain is 1, and we do not
// clip after gain.
apm_data_dumper_(apm_data_dumper) {}
void AdaptiveDigitalGainApplier::Process( void AdaptiveDigitalGainApplier::Process(
float input_level_dbfs, float input_level_dbfs,
@ -151,15 +112,13 @@ void AdaptiveDigitalGainApplier::Process(
// Optimization: avoid calling math functions if gain does not // Optimization: avoid calling math functions if gain does not
// change. // change.
const float gain_at_end_of_frame = if (gain_change_this_frame_db != 0.f) {
gain_change_this_frame_db == 0.f gain_applier_.SetGainFactor(
? last_gain_linear_ DbToRatio(last_gain_db_ + gain_change_this_frame_db));
: DbToRatio(last_gain_db_ + gain_change_this_frame_db); }
gain_applier_.ApplyGain(float_frame);
ApplyGainWithRamping(last_gain_linear_, gain_at_end_of_frame, float_frame);
// Remember that the gain has changed for the next iteration. // Remember that the gain has changed for the next iteration.
last_gain_linear_ = gain_at_end_of_frame;
last_gain_db_ = last_gain_db_ + gain_change_this_frame_db; last_gain_db_ = last_gain_db_ + gain_change_this_frame_db;
apm_data_dumper_->DumpRaw("agc2_applied_gain_db", last_gain_db_); apm_data_dumper_->DumpRaw("agc2_applied_gain_db", last_gain_db_);
} }

View File

@ -11,6 +11,7 @@
#ifndef MODULES_AUDIO_PROCESSING_AGC2_ADAPTIVE_DIGITAL_GAIN_APPLIER_H_ #ifndef MODULES_AUDIO_PROCESSING_AGC2_ADAPTIVE_DIGITAL_GAIN_APPLIER_H_
#define MODULES_AUDIO_PROCESSING_AGC2_ADAPTIVE_DIGITAL_GAIN_APPLIER_H_ #define MODULES_AUDIO_PROCESSING_AGC2_ADAPTIVE_DIGITAL_GAIN_APPLIER_H_
#include "modules/audio_processing/agc2/gain_applier.h"
#include "modules/audio_processing/include/audio_frame_view.h" #include "modules/audio_processing/include/audio_frame_view.h"
#include "modules/audio_processing/vad/vad_with_level.h" #include "modules/audio_processing/vad/vad_with_level.h"
@ -29,11 +30,8 @@ class AdaptiveDigitalGainApplier {
AudioFrameView<float> float_frame); AudioFrameView<float> float_frame);
private: private:
// Keep track of current gain for ramping up and down and
// logging. This member variable is redundant together with
// last_gain_db_. Both are kept as an optimization.
float last_gain_linear_ = 1.f;
float last_gain_db_ = 0.f; float last_gain_db_ = 0.f;
GainApplier gain_applier_;
// For some combinations of noise and speech probability, increasing // For some combinations of noise and speech probability, increasing
// the level is not allowed. Since we may get VAD results in bursts, // the level is not allowed. Since we may get VAD results in bursts,

View File

@ -55,7 +55,7 @@ void FixedGainController::SetSampleRate(size_t sample_rate_hz) {
} }
void FixedGainController::Process(AudioFrameView<float> signal) { void FixedGainController::Process(AudioFrameView<float> signal) {
// Apply fixed digital gain; interpolate if necessary. One of the // Apply fixed digital gain. One of the
// planned usages of the FGC is to only use the limiter. In that // planned usages of the FGC is to only use the limiter. In that
// case, the gain would be 1.0. Not doing the multiplications speeds // case, the gain would be 1.0. Not doing the multiplications speeds
// it up considerably. Hence the check. // it up considerably. Hence the check.

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2018 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/gain_applier.h"
#include "modules/audio_processing/agc2/agc2_common.h"
#include "rtc_base/numerics/safe_minmax.h"
namespace webrtc {
namespace {
// Returns true when the gain factor is so close to 1 that it would
// not affect int16 samples.
bool GainCloseToOne(float gain_factor) {
return 1.f - 1.f / kMaxFloatS16Value <= gain_factor &&
gain_factor <= 1.f + 1.f / kMaxFloatS16Value;
}
void ClipSignal(AudioFrameView<float> signal) {
for (size_t k = 0; k < signal.num_channels(); ++k) {
rtc::ArrayView<float> channel_view = signal.channel(k);
for (auto& sample : channel_view) {
sample = rtc::SafeClamp(sample, kMinFloatS16Value, kMaxFloatS16Value);
}
}
}
void ApplyGainWithRamping(float last_gain_linear,
float gain_at_end_of_frame_linear,
float inverse_samples_per_channel,
AudioFrameView<float> float_frame) {
// Do not modify the signal.
if (last_gain_linear == gain_at_end_of_frame_linear &&
GainCloseToOne(gain_at_end_of_frame_linear)) {
return;
}
// Gain is constant and different from 1.
if (last_gain_linear == gain_at_end_of_frame_linear) {
for (size_t k = 0; k < float_frame.num_channels(); ++k) {
rtc::ArrayView<float> channel_view = float_frame.channel(k);
for (auto& sample : channel_view) {
sample *= gain_at_end_of_frame_linear;
}
}
return;
}
// The gain changes. We have to change slowly to avoid discontinuities.
const float increment = (gain_at_end_of_frame_linear - last_gain_linear) *
inverse_samples_per_channel;
float gain = last_gain_linear;
for (size_t i = 0; i < float_frame.samples_per_channel(); ++i) {
for (size_t ch = 0; ch < float_frame.num_channels(); ++ch) {
float_frame.channel(ch)[i] *= gain;
}
gain += increment;
}
}
} // namespace
GainApplier::GainApplier(bool hard_clip_samples, float initial_gain_factor)
: hard_clip_samples_(hard_clip_samples),
last_gain_factor_(initial_gain_factor),
current_gain_factor_(initial_gain_factor) {}
void GainApplier::ApplyGain(AudioFrameView<float> signal) {
if (static_cast<int>(signal.samples_per_channel()) != samples_per_channel_) {
Initialize(signal.samples_per_channel());
}
ApplyGainWithRamping(last_gain_factor_, current_gain_factor_,
inverse_samples_per_channel_, signal);
last_gain_factor_ = current_gain_factor_;
if (hard_clip_samples_) {
ClipSignal(signal);
}
}
void GainApplier::SetGainFactor(float gain_factor) {
RTC_DCHECK_GT(gain_factor, 0.f);
current_gain_factor_ = gain_factor;
}
void GainApplier::Initialize(size_t samples_per_channel) {
RTC_DCHECK_GT(samples_per_channel, 0);
samples_per_channel_ = static_cast<int>(samples_per_channel);
inverse_samples_per_channel_ = 1.f / samples_per_channel_;
}
} // namespace webrtc

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2018 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_GAIN_APPLIER_H_
#define MODULES_AUDIO_PROCESSING_AGC2_GAIN_APPLIER_H_
#include "modules/audio_processing/include/audio_frame_view.h"
namespace webrtc {
class GainApplier {
public:
GainApplier(bool hard_clip_samples, float initial_gain_factor);
void ApplyGain(AudioFrameView<float> signal);
void SetGainFactor(float gain_factor);
private:
void Initialize(size_t samples_per_channel);
// Whether to clip samples after gain is applied. If 'true', result
// will fit in FloatS16 range.
const bool hard_clip_samples_;
float last_gain_factor_;
// If this value is not equal to 'last_gain_factor', gain will be
// ramped from 'last_gain_factor_' to this value during the next
// 'ApplyGain'.
float current_gain_factor_;
int samples_per_channel_ = -1;
float inverse_samples_per_channel_ = -1.f;
};
} // namespace webrtc
#endif // MODULES_AUDIO_PROCESSING_AGC2_GAIN_APPLIER_H_

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2018 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/gain_applier.h"
#include <math.h>
#include <algorithm>
#include <limits>
#include "modules/audio_processing/agc2/vector_float_frame.h"
#include "rtc_base/gunit.h"
namespace webrtc {
TEST(AutomaticGainController2GainApplier, InitialGainIsRespected) {
constexpr float initial_signal_level = 123.f;
constexpr float gain_factor = 10.f;
VectorFloatFrame fake_audio(1, 1, initial_signal_level);
GainApplier gain_applier(true, gain_factor);
gain_applier.ApplyGain(fake_audio.float_frame_view());
EXPECT_NEAR(fake_audio.float_frame_view().channel(0)[0],
initial_signal_level * gain_factor, 0.1f);
}
TEST(AutomaticGainController2GainApplier, ClippingIsDone) {
constexpr float initial_signal_level = 30000.f;
constexpr float gain_factor = 10.f;
VectorFloatFrame fake_audio(1, 1, initial_signal_level);
GainApplier gain_applier(true, gain_factor);
gain_applier.ApplyGain(fake_audio.float_frame_view());
EXPECT_NEAR(fake_audio.float_frame_view().channel(0)[0],
std::numeric_limits<int16_t>::max(), 0.1f);
}
TEST(AutomaticGainController2GainApplier, ClippingIsNotDone) {
constexpr float initial_signal_level = 30000.f;
constexpr float gain_factor = 10.f;
VectorFloatFrame fake_audio(1, 1, initial_signal_level);
GainApplier gain_applier(false, gain_factor);
gain_applier.ApplyGain(fake_audio.float_frame_view());
EXPECT_NEAR(fake_audio.float_frame_view().channel(0)[0],
initial_signal_level * gain_factor, 0.1f);
}
TEST(AutomaticGainController2GainApplier, RampingIsDone) {
constexpr float initial_signal_level = 30000.f;
constexpr float initial_gain_factor = 1.f;
constexpr float target_gain_factor = 0.5f;
constexpr int num_channels = 3;
constexpr int samples_per_channel = 4;
VectorFloatFrame fake_audio(num_channels, samples_per_channel,
initial_signal_level);
GainApplier gain_applier(false, initial_gain_factor);
gain_applier.SetGainFactor(target_gain_factor);
gain_applier.ApplyGain(fake_audio.float_frame_view());
// The maximal gain change should be close to that in linear interpolation.
for (size_t channel = 0; channel < num_channels; ++channel) {
float max_signal_change = 0.f;
float last_signal_level = initial_signal_level;
for (const auto sample : fake_audio.float_frame_view().channel(channel)) {
const float current_change = fabs(last_signal_level - sample);
max_signal_change =
std::max(max_signal_change, current_change);
last_signal_level = sample;
}
const float total_gain_change =
fabs((initial_gain_factor - target_gain_factor) * initial_signal_level);
EXPECT_NEAR(max_signal_change, total_gain_change / samples_per_channel,
0.1f);
}
// Next frame should have the desired level.
VectorFloatFrame next_fake_audio_frame(num_channels, samples_per_channel,
initial_signal_level);
gain_applier.ApplyGain(next_fake_audio_frame.float_frame_view());
// The last sample should have the new gain.
EXPECT_NEAR(next_fake_audio_frame.float_frame_view().channel(0)[0],
initial_signal_level * target_gain_factor, 0.1f);
}
} // namespace webrtc