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:
parent
aaa85ae565
commit
8a3eaddc95
@ -31,6 +31,7 @@ rtc_source_set("adaptive_digital") {
|
||||
|
||||
deps = [
|
||||
":common",
|
||||
":gain_applier",
|
||||
":noise_level_estimator",
|
||||
"..:aec_core",
|
||||
"..:apm_logging",
|
||||
@ -108,6 +109,18 @@ rtc_source_set("noise_level_estimator") {
|
||||
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") {
|
||||
testonly = true
|
||||
visibility = [ ":*" ]
|
||||
@ -161,11 +174,13 @@ rtc_source_set("adaptive_digital_unittests") {
|
||||
sources = [
|
||||
"adaptive_digital_gain_applier_unittest.cc",
|
||||
"adaptive_mode_level_estimator_unittest.cc",
|
||||
"gain_applier_unittest.cc",
|
||||
"saturation_protector_unittest.cc",
|
||||
]
|
||||
deps = [
|
||||
":adaptive_digital",
|
||||
":common",
|
||||
":gain_applier",
|
||||
":test_utils",
|
||||
"..:apm_logging",
|
||||
"..:audio_frame_view",
|
||||
|
||||
@ -64,52 +64,13 @@ float ComputeGainChangeThisFrameDb(float target_gain_db,
|
||||
return rtc::SafeClamp(target_gain_difference_db, -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
|
||||
|
||||
AdaptiveDigitalGainApplier::AdaptiveDigitalGainApplier(
|
||||
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(
|
||||
float input_level_dbfs,
|
||||
@ -151,15 +112,13 @@ void AdaptiveDigitalGainApplier::Process(
|
||||
|
||||
// Optimization: avoid calling math functions if gain does not
|
||||
// change.
|
||||
const float gain_at_end_of_frame =
|
||||
gain_change_this_frame_db == 0.f
|
||||
? last_gain_linear_
|
||||
: DbToRatio(last_gain_db_ + gain_change_this_frame_db);
|
||||
|
||||
ApplyGainWithRamping(last_gain_linear_, gain_at_end_of_frame, float_frame);
|
||||
if (gain_change_this_frame_db != 0.f) {
|
||||
gain_applier_.SetGainFactor(
|
||||
DbToRatio(last_gain_db_ + gain_change_this_frame_db));
|
||||
}
|
||||
gain_applier_.ApplyGain(float_frame);
|
||||
|
||||
// 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;
|
||||
apm_data_dumper_->DumpRaw("agc2_applied_gain_db", last_gain_db_);
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#ifndef 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/vad/vad_with_level.h"
|
||||
|
||||
@ -29,11 +30,8 @@ class AdaptiveDigitalGainApplier {
|
||||
AudioFrameView<float> float_frame);
|
||||
|
||||
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;
|
||||
GainApplier gain_applier_;
|
||||
|
||||
// For some combinations of noise and speech probability, increasing
|
||||
// the level is not allowed. Since we may get VAD results in bursts,
|
||||
|
||||
@ -55,7 +55,7 @@ void FixedGainController::SetSampleRate(size_t sample_rate_hz) {
|
||||
}
|
||||
|
||||
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
|
||||
// case, the gain would be 1.0. Not doing the multiplications speeds
|
||||
// it up considerably. Hence the check.
|
||||
|
||||
101
modules/audio_processing/agc2/gain_applier.cc
Normal file
101
modules/audio_processing/agc2/gain_applier.cc
Normal 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
|
||||
41
modules/audio_processing/agc2/gain_applier.h
Normal file
41
modules/audio_processing/agc2/gain_applier.h
Normal 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_
|
||||
94
modules/audio_processing/agc2/gain_applier_unittest.cc
Normal file
94
modules/audio_processing/agc2/gain_applier_unittest.cc
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user