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 = [
|
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",
|
||||||
|
|||||||
@ -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_);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
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