AGC: Route clipping parameter from webrtc::Config to AGC
This change enables experimentation with the clipping minimum level parameter in the gain control. BUG=webrtc:6622 CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:linux_chromium_rel_ng Review-Url: https://codereview.webrtc.org/2543753006 Cr-Commit-Position: refs/heads/master@{#15426}
This commit is contained in:
committed by
Commit bot
parent
db752f9b37
commit
bd681b9758
@ -27,8 +27,6 @@ namespace webrtc {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Lowest the microphone level can be lowered due to clipping.
|
|
||||||
const int kClippedLevelMin = 170;
|
|
||||||
// Amount the microphone level is lowered with every clipping event.
|
// Amount the microphone level is lowered with every clipping event.
|
||||||
const int kClippedLevelStep = 15;
|
const int kClippedLevelStep = 15;
|
||||||
// Proportion of clipped samples required to declare a clipping event.
|
// Proportion of clipped samples required to declare a clipping event.
|
||||||
@ -115,7 +113,8 @@ class DebugFile {
|
|||||||
|
|
||||||
AgcManagerDirect::AgcManagerDirect(GainControl* gctrl,
|
AgcManagerDirect::AgcManagerDirect(GainControl* gctrl,
|
||||||
VolumeCallbacks* volume_callbacks,
|
VolumeCallbacks* volume_callbacks,
|
||||||
int startup_min_level)
|
int startup_min_level,
|
||||||
|
int clipped_level_min)
|
||||||
: agc_(new Agc()),
|
: agc_(new Agc()),
|
||||||
gctrl_(gctrl),
|
gctrl_(gctrl),
|
||||||
volume_callbacks_(volume_callbacks),
|
volume_callbacks_(volume_callbacks),
|
||||||
@ -130,14 +129,15 @@ AgcManagerDirect::AgcManagerDirect(GainControl* gctrl,
|
|||||||
check_volume_on_next_process_(true), // Check at startup.
|
check_volume_on_next_process_(true), // Check at startup.
|
||||||
startup_(true),
|
startup_(true),
|
||||||
startup_min_level_(ClampLevel(startup_min_level)),
|
startup_min_level_(ClampLevel(startup_min_level)),
|
||||||
|
clipped_level_min_(clipped_level_min),
|
||||||
file_preproc_(new DebugFile("agc_preproc.pcm")),
|
file_preproc_(new DebugFile("agc_preproc.pcm")),
|
||||||
file_postproc_(new DebugFile("agc_postproc.pcm")) {
|
file_postproc_(new DebugFile("agc_postproc.pcm")) {}
|
||||||
}
|
|
||||||
|
|
||||||
AgcManagerDirect::AgcManagerDirect(Agc* agc,
|
AgcManagerDirect::AgcManagerDirect(Agc* agc,
|
||||||
GainControl* gctrl,
|
GainControl* gctrl,
|
||||||
VolumeCallbacks* volume_callbacks,
|
VolumeCallbacks* volume_callbacks,
|
||||||
int startup_min_level)
|
int startup_min_level,
|
||||||
|
int clipped_level_min)
|
||||||
: agc_(agc),
|
: agc_(agc),
|
||||||
gctrl_(gctrl),
|
gctrl_(gctrl),
|
||||||
volume_callbacks_(volume_callbacks),
|
volume_callbacks_(volume_callbacks),
|
||||||
@ -152,9 +152,9 @@ AgcManagerDirect::AgcManagerDirect(Agc* agc,
|
|||||||
check_volume_on_next_process_(true), // Check at startup.
|
check_volume_on_next_process_(true), // Check at startup.
|
||||||
startup_(true),
|
startup_(true),
|
||||||
startup_min_level_(ClampLevel(startup_min_level)),
|
startup_min_level_(ClampLevel(startup_min_level)),
|
||||||
|
clipped_level_min_(clipped_level_min),
|
||||||
file_preproc_(new DebugFile("agc_preproc.pcm")),
|
file_preproc_(new DebugFile("agc_preproc.pcm")),
|
||||||
file_postproc_(new DebugFile("agc_postproc.pcm")) {
|
file_postproc_(new DebugFile("agc_postproc.pcm")) {}
|
||||||
}
|
|
||||||
|
|
||||||
AgcManagerDirect::~AgcManagerDirect() {}
|
AgcManagerDirect::~AgcManagerDirect() {}
|
||||||
|
|
||||||
@ -218,14 +218,14 @@ void AgcManagerDirect::AnalyzePreProcess(int16_t* audio,
|
|||||||
<< clipped_ratio;
|
<< clipped_ratio;
|
||||||
// Always decrease the maximum level, even if the current level is below
|
// Always decrease the maximum level, even if the current level is below
|
||||||
// threshold.
|
// threshold.
|
||||||
SetMaxLevel(std::max(kClippedLevelMin, max_level_ - kClippedLevelStep));
|
SetMaxLevel(std::max(clipped_level_min_, max_level_ - kClippedLevelStep));
|
||||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
|
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
|
||||||
level_ - kClippedLevelStep >= kClippedLevelMin);
|
level_ - kClippedLevelStep >= clipped_level_min_);
|
||||||
if (level_ > kClippedLevelMin) {
|
if (level_ > clipped_level_min_) {
|
||||||
// Don't try to adjust the level if we're already below the limit. As
|
// Don't try to adjust the level if we're already below the limit. As
|
||||||
// a consequence, if the user has brought the level above the limit, we
|
// a consequence, if the user has brought the level above the limit, we
|
||||||
// will still not react until the postproc updates the level.
|
// will still not react until the postproc updates the level.
|
||||||
SetLevel(std::max(kClippedLevelMin, level_ - kClippedLevelStep));
|
SetLevel(std::max(clipped_level_min_, level_ - kClippedLevelStep));
|
||||||
// Reset the AGC since the level has changed.
|
// Reset the AGC since the level has changed.
|
||||||
agc_->Reset();
|
agc_->Reset();
|
||||||
}
|
}
|
||||||
@ -301,13 +301,15 @@ void AgcManagerDirect::SetLevel(int new_level) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AgcManagerDirect::SetMaxLevel(int level) {
|
void AgcManagerDirect::SetMaxLevel(int level) {
|
||||||
RTC_DCHECK_GE(level, kClippedLevelMin);
|
RTC_DCHECK_GE(level, clipped_level_min_);
|
||||||
max_level_ = level;
|
max_level_ = level;
|
||||||
// Scale the |kSurplusCompressionGain| linearly across the restricted
|
// Scale the |kSurplusCompressionGain| linearly across the restricted
|
||||||
// level range.
|
// level range.
|
||||||
max_compression_gain_ = kMaxCompressionGain + std::floor(
|
max_compression_gain_ =
|
||||||
(1.f * kMaxMicLevel - max_level_) / (kMaxMicLevel - kClippedLevelMin) *
|
kMaxCompressionGain + std::floor((1.f * kMaxMicLevel - max_level_) /
|
||||||
kSurplusCompressionGain + 0.5f);
|
(kMaxMicLevel - clipped_level_min_) *
|
||||||
|
kSurplusCompressionGain +
|
||||||
|
0.5f);
|
||||||
LOG(LS_INFO) << "[agc] max_level_=" << max_level_
|
LOG(LS_INFO) << "[agc] max_level_=" << max_level_
|
||||||
<< ", max_compression_gain_=" << max_compression_gain_;
|
<< ", max_compression_gain_=" << max_compression_gain_;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,13 +46,15 @@ class AgcManagerDirect final {
|
|||||||
// outside that range will be clamped.
|
// outside that range will be clamped.
|
||||||
AgcManagerDirect(GainControl* gctrl,
|
AgcManagerDirect(GainControl* gctrl,
|
||||||
VolumeCallbacks* volume_callbacks,
|
VolumeCallbacks* volume_callbacks,
|
||||||
int startup_min_level);
|
int startup_min_level,
|
||||||
|
int clipped_level_min);
|
||||||
// Dependency injection for testing. Don't delete |agc| as the memory is owned
|
// Dependency injection for testing. Don't delete |agc| as the memory is owned
|
||||||
// by the manager.
|
// by the manager.
|
||||||
AgcManagerDirect(Agc* agc,
|
AgcManagerDirect(Agc* agc,
|
||||||
GainControl* gctrl,
|
GainControl* gctrl,
|
||||||
VolumeCallbacks* volume_callbacks,
|
VolumeCallbacks* volume_callbacks,
|
||||||
int startup_min_level);
|
int startup_min_level,
|
||||||
|
int clipped_level_min);
|
||||||
~AgcManagerDirect();
|
~AgcManagerDirect();
|
||||||
|
|
||||||
int Initialize();
|
int Initialize();
|
||||||
@ -98,6 +100,7 @@ class AgcManagerDirect final {
|
|||||||
bool check_volume_on_next_process_;
|
bool check_volume_on_next_process_;
|
||||||
bool startup_;
|
bool startup_;
|
||||||
int startup_min_level_;
|
int startup_min_level_;
|
||||||
|
const int clipped_level_min_;
|
||||||
|
|
||||||
std::unique_ptr<DebugFile> file_preproc_;
|
std::unique_ptr<DebugFile> file_preproc_;
|
||||||
std::unique_ptr<DebugFile> file_postproc_;
|
std::unique_ptr<DebugFile> file_postproc_;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ const int kSampleRateHz = 32000;
|
|||||||
const int kNumChannels = 1;
|
const int kNumChannels = 1;
|
||||||
const int kSamplesPerChannel = kSampleRateHz / 100;
|
const int kSamplesPerChannel = kSampleRateHz / 100;
|
||||||
const int kInitialVolume = 128;
|
const int kInitialVolume = 128;
|
||||||
|
constexpr int kClippedMin = 165; // Arbitrary, but different from the default.
|
||||||
const float kAboveClippedThreshold = 0.2f;
|
const float kAboveClippedThreshold = 0.2f;
|
||||||
|
|
||||||
class TestVolumeCallbacks : public VolumeCallbacks {
|
class TestVolumeCallbacks : public VolumeCallbacks {
|
||||||
@ -50,7 +51,8 @@ class TestVolumeCallbacks : public VolumeCallbacks {
|
|||||||
class AgcManagerDirectTest : public ::testing::Test {
|
class AgcManagerDirectTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
AgcManagerDirectTest()
|
AgcManagerDirectTest()
|
||||||
: agc_(new MockAgc), manager_(agc_, &gctrl_, &volume_, kInitialVolume) {
|
: agc_(new MockAgc),
|
||||||
|
manager_(agc_, &gctrl_, &volume_, kInitialVolume, kClippedMin) {
|
||||||
ExpectInitialize();
|
ExpectInitialize();
|
||||||
manager_.Initialize();
|
manager_.Initialize();
|
||||||
}
|
}
|
||||||
@ -505,13 +507,13 @@ TEST_F(AgcManagerDirectTest, ClippingLoweringIsLimited) {
|
|||||||
.WillOnce(Return(kAboveClippedThreshold));
|
.WillOnce(Return(kAboveClippedThreshold));
|
||||||
EXPECT_CALL(*agc_, Reset()).Times(1);
|
EXPECT_CALL(*agc_, Reset()).Times(1);
|
||||||
CallPreProc(1);
|
CallPreProc(1);
|
||||||
EXPECT_EQ(170, volume_.GetMicVolume());
|
EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
|
||||||
|
|
||||||
EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
|
EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
|
||||||
.WillRepeatedly(Return(kAboveClippedThreshold));
|
.WillRepeatedly(Return(kAboveClippedThreshold));
|
||||||
EXPECT_CALL(*agc_, Reset()).Times(0);
|
EXPECT_CALL(*agc_, Reset()).Times(0);
|
||||||
CallPreProc(1000);
|
CallPreProc(1000);
|
||||||
EXPECT_EQ(170, volume_.GetMicVolume());
|
EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(AgcManagerDirectTest, ClippingMaxIsRespectedWhenEqualToLevel) {
|
TEST_F(AgcManagerDirectTest, ClippingMaxIsRespectedWhenEqualToLevel) {
|
||||||
@ -589,7 +591,7 @@ TEST_F(AgcManagerDirectTest, MaxCompressionIsIncreasedAfterClipping) {
|
|||||||
.WillOnce(Return(kAboveClippedThreshold));
|
.WillOnce(Return(kAboveClippedThreshold));
|
||||||
EXPECT_CALL(*agc_, Reset()).Times(1);
|
EXPECT_CALL(*agc_, Reset()).Times(1);
|
||||||
CallPreProc(1);
|
CallPreProc(1);
|
||||||
EXPECT_EQ(170, volume_.GetMicVolume());
|
EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
|
||||||
|
|
||||||
// Current level is now at the minimum, but the maximum allowed level still
|
// Current level is now at the minimum, but the maximum allowed level still
|
||||||
// has more to decrease.
|
// has more to decrease.
|
||||||
|
|||||||
@ -302,6 +302,7 @@ AudioProcessingImpl::AudioProcessingImpl(const webrtc::Config& config,
|
|||||||
public_submodules_(new ApmPublicSubmodules()),
|
public_submodules_(new ApmPublicSubmodules()),
|
||||||
private_submodules_(new ApmPrivateSubmodules(beamformer)),
|
private_submodules_(new ApmPrivateSubmodules(beamformer)),
|
||||||
constants_(config.Get<ExperimentalAgc>().startup_min_volume,
|
constants_(config.Get<ExperimentalAgc>().startup_min_volume,
|
||||||
|
config.Get<ExperimentalAgc>().clipped_level_min,
|
||||||
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
|
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS)
|
||||||
false),
|
false),
|
||||||
#else
|
#else
|
||||||
@ -488,7 +489,7 @@ int AudioProcessingImpl::InitializeLocked() {
|
|||||||
private_submodules_->agc_manager.reset(new AgcManagerDirect(
|
private_submodules_->agc_manager.reset(new AgcManagerDirect(
|
||||||
public_submodules_->gain_control.get(),
|
public_submodules_->gain_control.get(),
|
||||||
public_submodules_->gain_control_for_experimental_agc.get(),
|
public_submodules_->gain_control_for_experimental_agc.get(),
|
||||||
constants_.agc_startup_min_volume));
|
constants_.agc_startup_min_volume, constants_.agc_clipped_level_min));
|
||||||
}
|
}
|
||||||
private_submodules_->agc_manager->Initialize();
|
private_submodules_->agc_manager->Initialize();
|
||||||
private_submodules_->agc_manager->SetCaptureMuted(
|
private_submodules_->agc_manager->SetCaptureMuted(
|
||||||
@ -1853,6 +1854,9 @@ int AudioProcessingImpl::WriteConfigMessage(bool forced) {
|
|||||||
if (capture_nonlocked_.level_controller_enabled) {
|
if (capture_nonlocked_.level_controller_enabled) {
|
||||||
experiments_description += "LevelController;";
|
experiments_description += "LevelController;";
|
||||||
}
|
}
|
||||||
|
if (constants_.agc_clipped_level_min != kClippedLevelMin) {
|
||||||
|
experiments_description += "AgcClippingLevelExperiment;";
|
||||||
|
}
|
||||||
config.set_experiments_description(experiments_description);
|
config.set_experiments_description(experiments_description);
|
||||||
|
|
||||||
std::string serialized_config = config.SerializeAsString();
|
std::string serialized_config = config.SerializeAsString();
|
||||||
|
|||||||
@ -329,11 +329,15 @@ class AudioProcessingImpl : public AudioProcessing {
|
|||||||
|
|
||||||
// APM constants.
|
// APM constants.
|
||||||
const struct ApmConstants {
|
const struct ApmConstants {
|
||||||
ApmConstants(int agc_startup_min_volume, bool use_experimental_agc)
|
ApmConstants(int agc_startup_min_volume,
|
||||||
|
int agc_clipped_level_min,
|
||||||
|
bool use_experimental_agc)
|
||||||
: // Format of processing streams at input/output call sites.
|
: // Format of processing streams at input/output call sites.
|
||||||
agc_startup_min_volume(agc_startup_min_volume),
|
agc_startup_min_volume(agc_startup_min_volume),
|
||||||
|
agc_clipped_level_min(agc_clipped_level_min),
|
||||||
use_experimental_agc(use_experimental_agc) {}
|
use_experimental_agc(use_experimental_agc) {}
|
||||||
int agc_startup_min_volume;
|
int agc_startup_min_volume;
|
||||||
|
int agc_clipped_level_min;
|
||||||
bool use_experimental_agc;
|
bool use_experimental_agc;
|
||||||
} constants_;
|
} constants_;
|
||||||
|
|
||||||
|
|||||||
@ -114,15 +114,21 @@ static const int kAgcStartupMinVolume = 85;
|
|||||||
#else
|
#else
|
||||||
static const int kAgcStartupMinVolume = 0;
|
static const int kAgcStartupMinVolume = 0;
|
||||||
#endif // defined(WEBRTC_CHROMIUM_BUILD)
|
#endif // defined(WEBRTC_CHROMIUM_BUILD)
|
||||||
|
static constexpr int kClippedLevelMin = 170;
|
||||||
struct ExperimentalAgc {
|
struct ExperimentalAgc {
|
||||||
ExperimentalAgc() : enabled(true), startup_min_volume(kAgcStartupMinVolume) {}
|
ExperimentalAgc() = default;
|
||||||
explicit ExperimentalAgc(bool enabled)
|
explicit ExperimentalAgc(bool enabled) : enabled(enabled) {}
|
||||||
: enabled(enabled), startup_min_volume(kAgcStartupMinVolume) {}
|
|
||||||
ExperimentalAgc(bool enabled, int startup_min_volume)
|
ExperimentalAgc(bool enabled, int startup_min_volume)
|
||||||
: enabled(enabled), startup_min_volume(startup_min_volume) {}
|
: enabled(enabled), startup_min_volume(startup_min_volume) {}
|
||||||
|
ExperimentalAgc(bool enabled, int startup_min_volume, int clipped_level_min)
|
||||||
|
: enabled(enabled),
|
||||||
|
startup_min_volume(startup_min_volume),
|
||||||
|
clipped_level_min(clipped_level_min) {}
|
||||||
static const ConfigOptionID identifier = ConfigOptionID::kExperimentalAgc;
|
static const ConfigOptionID identifier = ConfigOptionID::kExperimentalAgc;
|
||||||
bool enabled;
|
bool enabled = true;
|
||||||
int startup_min_volume;
|
int startup_min_volume = kAgcStartupMinVolume;
|
||||||
|
// Lowest microphone level that will be applied in response to clipping.
|
||||||
|
int clipped_level_min = kClippedLevelMin;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use to enable experimental noise suppression. It can be set in the
|
// Use to enable experimental noise suppression. It can be set in the
|
||||||
|
|||||||
@ -379,6 +379,8 @@ TEST_F(DebugDumpTest, VerifyCombinedExperimentalStringInclusive) {
|
|||||||
Config config;
|
Config config;
|
||||||
config.Set<RefinedAdaptiveFilter>(new RefinedAdaptiveFilter(true));
|
config.Set<RefinedAdaptiveFilter>(new RefinedAdaptiveFilter(true));
|
||||||
config.Set<EchoCanceller3>(new EchoCanceller3(true));
|
config.Set<EchoCanceller3>(new EchoCanceller3(true));
|
||||||
|
// Arbitrarily set clipping gain to 17, which will never be the default.
|
||||||
|
config.Set<ExperimentalAgc>(new ExperimentalAgc(true, 0, 17));
|
||||||
DebugDumpGenerator generator(config, AudioProcessing::Config());
|
DebugDumpGenerator generator(config, AudioProcessing::Config());
|
||||||
generator.StartRecording();
|
generator.StartRecording();
|
||||||
generator.Process(100);
|
generator.Process(100);
|
||||||
@ -398,6 +400,8 @@ TEST_F(DebugDumpTest, VerifyCombinedExperimentalStringInclusive) {
|
|||||||
msg->experiments_description().c_str());
|
msg->experiments_description().c_str());
|
||||||
EXPECT_PRED_FORMAT2(testing::IsSubstring, "AEC3",
|
EXPECT_PRED_FORMAT2(testing::IsSubstring, "AEC3",
|
||||||
msg->experiments_description().c_str());
|
msg->experiments_description().c_str());
|
||||||
|
EXPECT_PRED_FORMAT2(testing::IsSubstring, "AgcClippingLevelExperiment",
|
||||||
|
msg->experiments_description().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,6 +428,8 @@ TEST_F(DebugDumpTest, VerifyCombinedExperimentalStringExclusive) {
|
|||||||
msg->experiments_description().c_str());
|
msg->experiments_description().c_str());
|
||||||
EXPECT_PRED_FORMAT2(testing::IsNotSubstring, "AEC3",
|
EXPECT_PRED_FORMAT2(testing::IsNotSubstring, "AEC3",
|
||||||
msg->experiments_description().c_str());
|
msg->experiments_description().c_str());
|
||||||
|
EXPECT_PRED_FORMAT2(testing::IsNotSubstring, "AgcClippingLevelExperiment",
|
||||||
|
msg->experiments_description().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,6 +483,31 @@ TEST_F(DebugDumpTest, VerifyLevelControllerExperimentalString) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DebugDumpTest, VerifyAgcClippingLevelExperimentalString) {
|
||||||
|
Config config;
|
||||||
|
// Arbitrarily set clipping gain to 17, which will never be the default.
|
||||||
|
config.Set<ExperimentalAgc>(new ExperimentalAgc(true, 0, 17));
|
||||||
|
DebugDumpGenerator generator(config, AudioProcessing::Config());
|
||||||
|
generator.StartRecording();
|
||||||
|
generator.Process(100);
|
||||||
|
generator.StopRecording();
|
||||||
|
|
||||||
|
DebugDumpReplayer debug_dump_replayer_;
|
||||||
|
|
||||||
|
ASSERT_TRUE(debug_dump_replayer_.SetDumpFile(generator.dump_file_name()));
|
||||||
|
|
||||||
|
while (const rtc::Optional<audioproc::Event> event =
|
||||||
|
debug_dump_replayer_.GetNextEvent()) {
|
||||||
|
debug_dump_replayer_.RunNextEvent();
|
||||||
|
if (event->type() == audioproc::Event::CONFIG) {
|
||||||
|
const audioproc::Config* msg = &event->config();
|
||||||
|
ASSERT_TRUE(msg->has_experiments_description());
|
||||||
|
EXPECT_PRED_FORMAT2(testing::IsSubstring, "AgcClippingLevelExperiment",
|
||||||
|
msg->experiments_description().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(DebugDumpTest, VerifyEmptyExperimentalString) {
|
TEST_F(DebugDumpTest, VerifyEmptyExperimentalString) {
|
||||||
Config config;
|
Config config;
|
||||||
DebugDumpGenerator generator(config, AudioProcessing::Config());
|
DebugDumpGenerator generator(config, AudioProcessing::Config());
|
||||||
|
|||||||
Reference in New Issue
Block a user