webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc
Per Åhgren 6ee75fdfcb Allow setting the AGC2 fixed gain during runtime
This CL extends the supported runtime settings in
APM to also comprise the AGC2 fixed gain.
The CL was originally created by Adam Whiteside.

Bug: webrtc:10574
Change-Id: I79b3d6501f1e202b66a9b6018f8a493a56b01f62
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/134101
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27782}
2019-04-26 10:05:45 +00:00

602 lines
22 KiB
C++

/*
* Copyright (c) 2016 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/test/aec_dump_based_simulator.h"
#include <iostream>
#include "absl/memory/memory.h"
#include "modules/audio_processing/echo_cancellation_impl.h"
#include "modules/audio_processing/echo_control_mobile_impl.h"
#include "modules/audio_processing/test/protobuf_utils.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace test {
namespace {
// Verify output bitexactness for the fixed interface.
// TODO(peah): Check whether it would make sense to add a threshold
// to use for checking the bitexactness in a soft manner.
bool VerifyFixedBitExactness(const webrtc::audioproc::Stream& msg,
const AudioFrame& frame) {
if ((sizeof(int16_t) * frame.samples_per_channel_ * frame.num_channels_) !=
msg.output_data().size()) {
return false;
} else {
const int16_t* frame_data = frame.data();
for (size_t k = 0; k < frame.num_channels_ * frame.samples_per_channel_;
++k) {
if (msg.output_data().data()[k] != frame_data[k]) {
return false;
}
}
}
return true;
}
// Verify output bitexactness for the float interface.
bool VerifyFloatBitExactness(const webrtc::audioproc::Stream& msg,
const StreamConfig& out_config,
const ChannelBuffer<float>& out_buf) {
if (static_cast<size_t>(msg.output_channel_size()) !=
out_config.num_channels() ||
msg.output_channel(0).size() != out_config.num_frames()) {
return false;
} else {
for (int ch = 0; ch < msg.output_channel_size(); ++ch) {
for (size_t sample = 0; sample < out_config.num_frames(); ++sample) {
if (msg.output_channel(ch).data()[sample] !=
out_buf.channels()[ch][sample]) {
return false;
}
}
}
}
return true;
}
} // namespace
AecDumpBasedSimulator::AecDumpBasedSimulator(
const SimulationSettings& settings,
std::unique_ptr<AudioProcessingBuilder> ap_builder)
: AudioProcessingSimulator(settings, std::move(ap_builder)) {
MaybeOpenCallOrderFile();
}
AecDumpBasedSimulator::~AecDumpBasedSimulator() = default;
void AecDumpBasedSimulator::PrepareProcessStreamCall(
const webrtc::audioproc::Stream& msg) {
if (msg.has_input_data()) {
// Fixed interface processing.
// Verify interface invariance.
RTC_CHECK(interface_used_ == InterfaceType::kFixedInterface ||
interface_used_ == InterfaceType::kNotSpecified);
interface_used_ = InterfaceType::kFixedInterface;
// Populate input buffer.
RTC_CHECK_EQ(sizeof(*fwd_frame_.data()) * fwd_frame_.samples_per_channel_ *
fwd_frame_.num_channels_,
msg.input_data().size());
memcpy(fwd_frame_.mutable_data(), msg.input_data().data(),
msg.input_data().size());
} else {
// Float interface processing.
// Verify interface invariance.
RTC_CHECK(interface_used_ == InterfaceType::kFloatInterface ||
interface_used_ == InterfaceType::kNotSpecified);
interface_used_ = InterfaceType::kFloatInterface;
RTC_CHECK_EQ(in_buf_->num_channels(),
static_cast<size_t>(msg.input_channel_size()));
// Populate input buffer.
for (size_t i = 0; i < in_buf_->num_channels(); ++i) {
RTC_CHECK_EQ(in_buf_->num_frames() * sizeof(*in_buf_->channels()[i]),
msg.input_channel(i).size());
std::memcpy(in_buf_->channels()[i], msg.input_channel(i).data(),
msg.input_channel(i).size());
}
}
if (artificial_nearend_buffer_reader_) {
if (artificial_nearend_buffer_reader_->Read(
artificial_nearend_buf_.get())) {
if (msg.has_input_data()) {
int16_t* fwd_frame_data = fwd_frame_.mutable_data();
for (size_t k = 0; k < in_buf_->num_frames(); ++k) {
fwd_frame_data[k] = rtc::saturated_cast<int16_t>(
fwd_frame_data[k] +
static_cast<int16_t>(32767 *
artificial_nearend_buf_->channels()[0][k]));
}
} else {
for (int i = 0; i < msg.input_channel_size(); ++i) {
for (size_t k = 0; k < in_buf_->num_frames(); ++k) {
in_buf_->channels()[i][k] +=
artificial_nearend_buf_->channels()[0][k];
in_buf_->channels()[i][k] = std::min(
32767.f, std::max(-32768.f, in_buf_->channels()[i][k]));
}
}
}
} else {
if (!artificial_nearend_eof_reported_) {
std::cout << "The artificial nearend file ended before the recording.";
artificial_nearend_eof_reported_ = true;
}
}
}
if (!settings_.use_stream_delay || *settings_.use_stream_delay) {
if (!settings_.stream_delay) {
if (msg.has_delay()) {
RTC_CHECK_EQ(AudioProcessing::kNoError,
ap_->set_stream_delay_ms(msg.delay()));
}
} else {
RTC_CHECK_EQ(AudioProcessing::kNoError,
ap_->set_stream_delay_ms(*settings_.stream_delay));
}
}
if (!settings_.use_ts) {
if (msg.has_keypress()) {
ap_->set_stream_key_pressed(msg.keypress());
}
} else {
ap_->set_stream_key_pressed(*settings_.use_ts);
}
// Level is always logged in AEC dumps.
RTC_CHECK(msg.has_level());
aec_dump_mic_level_ = msg.level();
}
void AecDumpBasedSimulator::VerifyProcessStreamBitExactness(
const webrtc::audioproc::Stream& msg) {
if (bitexact_output_) {
if (interface_used_ == InterfaceType::kFixedInterface) {
bitexact_output_ = VerifyFixedBitExactness(msg, fwd_frame_);
} else {
bitexact_output_ = VerifyFloatBitExactness(msg, out_config_, *out_buf_);
}
}
}
void AecDumpBasedSimulator::PrepareReverseProcessStreamCall(
const webrtc::audioproc::ReverseStream& msg) {
if (msg.has_data()) {
// Fixed interface processing.
// Verify interface invariance.
RTC_CHECK(interface_used_ == InterfaceType::kFixedInterface ||
interface_used_ == InterfaceType::kNotSpecified);
interface_used_ = InterfaceType::kFixedInterface;
// Populate input buffer.
RTC_CHECK_EQ(sizeof(int16_t) * rev_frame_.samples_per_channel_ *
rev_frame_.num_channels_,
msg.data().size());
memcpy(rev_frame_.mutable_data(), msg.data().data(), msg.data().size());
} else {
// Float interface processing.
// Verify interface invariance.
RTC_CHECK(interface_used_ == InterfaceType::kFloatInterface ||
interface_used_ == InterfaceType::kNotSpecified);
interface_used_ = InterfaceType::kFloatInterface;
RTC_CHECK_EQ(reverse_in_buf_->num_channels(),
static_cast<size_t>(msg.channel_size()));
// Populate input buffer.
for (int i = 0; i < msg.channel_size(); ++i) {
RTC_CHECK_EQ(reverse_in_buf_->num_frames() *
sizeof(*reverse_in_buf_->channels()[i]),
msg.channel(i).size());
std::memcpy(reverse_in_buf_->channels()[i], msg.channel(i).data(),
msg.channel(i).size());
}
}
}
void AecDumpBasedSimulator::Process() {
CreateAudioProcessor();
dump_input_file_ = OpenFile(settings_.aec_dump_input_filename->c_str(), "rb");
if (settings_.artificial_nearend_filename) {
std::unique_ptr<WavReader> artificial_nearend_file(
new WavReader(settings_.artificial_nearend_filename->c_str()));
RTC_CHECK_EQ(1, artificial_nearend_file->num_channels())
<< "Only mono files for the artificial nearend are supported, "
"reverted to not using the artificial nearend file";
const int sample_rate_hz = artificial_nearend_file->sample_rate();
artificial_nearend_buffer_reader_.reset(
new ChannelBufferWavReader(std::move(artificial_nearend_file)));
artificial_nearend_buf_.reset(new ChannelBuffer<float>(
rtc::CheckedDivExact(sample_rate_hz, kChunksPerSecond), 1));
}
webrtc::audioproc::Event event_msg;
int num_forward_chunks_processed = 0;
while (ReadMessageFromFile(dump_input_file_, &event_msg)) {
switch (event_msg.type()) {
case webrtc::audioproc::Event::INIT:
RTC_CHECK(event_msg.has_init());
HandleMessage(event_msg.init());
break;
case webrtc::audioproc::Event::STREAM:
RTC_CHECK(event_msg.has_stream());
HandleMessage(event_msg.stream());
++num_forward_chunks_processed;
break;
case webrtc::audioproc::Event::REVERSE_STREAM:
RTC_CHECK(event_msg.has_reverse_stream());
HandleMessage(event_msg.reverse_stream());
break;
case webrtc::audioproc::Event::CONFIG:
RTC_CHECK(event_msg.has_config());
HandleMessage(event_msg.config());
break;
case webrtc::audioproc::Event::RUNTIME_SETTING:
HandleMessage(event_msg.runtime_setting());
break;
case webrtc::audioproc::Event::UNKNOWN_EVENT:
RTC_CHECK(false);
break;
}
}
fclose(dump_input_file_);
DestroyAudioProcessor();
}
void AecDumpBasedSimulator::HandleMessage(
const webrtc::audioproc::Config& msg) {
if (settings_.use_verbose_logging) {
std::cout << "Config at frame:" << std::endl;
std::cout << " Forward: " << get_num_process_stream_calls() << std::endl;
std::cout << " Reverse: " << get_num_reverse_process_stream_calls()
<< std::endl;
}
if (!settings_.discard_all_settings_in_aecdump) {
if (settings_.use_verbose_logging) {
std::cout << "Setting used in config:" << std::endl;
}
Config config;
AudioProcessing::Config apm_config = ap_->GetConfig();
if (msg.has_aec_enabled() || settings_.use_aec) {
bool enable = settings_.use_aec ? *settings_.use_aec : msg.aec_enabled();
apm_config.echo_canceller.enabled = enable;
if (settings_.use_verbose_logging) {
std::cout << " aec_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
if (msg.has_aec_delay_agnostic_enabled() || settings_.use_delay_agnostic) {
bool enable = settings_.use_delay_agnostic
? *settings_.use_delay_agnostic
: msg.aec_delay_agnostic_enabled();
config.Set<DelayAgnostic>(new DelayAgnostic(enable));
if (settings_.use_verbose_logging) {
std::cout << " aec_delay_agnostic_enabled: "
<< (enable ? "true" : "false") << std::endl;
}
}
if (msg.has_aec_drift_compensation_enabled() ||
settings_.use_drift_compensation) {
if (settings_.use_drift_compensation
? *settings_.use_drift_compensation
: msg.aec_drift_compensation_enabled()) {
RTC_LOG(LS_ERROR)
<< "Ignoring deprecated setting: AEC2 drift compensation";
}
}
if (msg.has_aec_extended_filter_enabled() ||
settings_.use_extended_filter) {
bool enable = settings_.use_extended_filter
? *settings_.use_extended_filter
: msg.aec_extended_filter_enabled();
config.Set<ExtendedFilter>(new ExtendedFilter(enable));
if (settings_.use_verbose_logging) {
std::cout << " aec_extended_filter_enabled: "
<< (enable ? "true" : "false") << std::endl;
}
}
if (msg.has_aec_suppression_level() || settings_.aec_suppression_level) {
auto level = static_cast<webrtc::EchoCancellationImpl::SuppressionLevel>(
settings_.aec_suppression_level ? *settings_.aec_suppression_level
: msg.aec_suppression_level());
if (level ==
webrtc::EchoCancellationImpl::SuppressionLevel::kLowSuppression) {
RTC_LOG(LS_ERROR)
<< "Ignoring deprecated setting: AEC2 low suppression";
} else {
apm_config.echo_canceller.legacy_moderate_suppression_level =
(level == webrtc::EchoCancellationImpl::SuppressionLevel::
kModerateSuppression);
if (settings_.use_verbose_logging) {
std::cout << " aec_suppression_level: " << level << std::endl;
}
}
}
if (msg.has_aecm_enabled() || settings_.use_aecm) {
bool enable =
settings_.use_aecm ? *settings_.use_aecm : msg.aecm_enabled();
apm_config.echo_canceller.enabled |= enable;
apm_config.echo_canceller.mobile_mode = enable;
if (settings_.use_verbose_logging) {
std::cout << " aecm_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
if (msg.has_aecm_comfort_noise_enabled() &&
msg.aecm_comfort_noise_enabled()) {
RTC_LOG(LS_ERROR) << "Ignoring deprecated setting: AECM comfort noise";
}
if (msg.has_aecm_routing_mode() &&
static_cast<webrtc::EchoControlMobileImpl::RoutingMode>(
msg.aecm_routing_mode()) != EchoControlMobileImpl::kSpeakerphone) {
RTC_LOG(LS_ERROR) << "Ignoring deprecated setting: AECM routing mode: "
<< msg.aecm_routing_mode();
}
if (msg.has_agc_enabled() || settings_.use_agc) {
bool enable = settings_.use_agc ? *settings_.use_agc : msg.agc_enabled();
RTC_CHECK_EQ(AudioProcessing::kNoError,
ap_->gain_control()->Enable(enable));
if (settings_.use_verbose_logging) {
std::cout << " agc_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
if (msg.has_agc_mode() || settings_.agc_mode) {
int mode = settings_.agc_mode ? *settings_.agc_mode : msg.agc_mode();
RTC_CHECK_EQ(AudioProcessing::kNoError,
ap_->gain_control()->set_mode(
static_cast<webrtc::GainControl::Mode>(mode)));
if (settings_.use_verbose_logging) {
std::cout << " agc_mode: " << mode << std::endl;
}
}
if (msg.has_agc_limiter_enabled() || settings_.use_agc_limiter) {
bool enable = settings_.use_agc_limiter ? *settings_.use_agc_limiter
: msg.agc_limiter_enabled();
RTC_CHECK_EQ(AudioProcessing::kNoError,
ap_->gain_control()->enable_limiter(enable));
if (settings_.use_verbose_logging) {
std::cout << " agc_limiter_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
if (settings_.use_agc2) {
bool enable = *settings_.use_agc2;
apm_config.gain_controller2.enabled = enable;
if (settings_.agc2_fixed_gain_db) {
apm_config.gain_controller2.fixed_digital.gain_db =
*settings_.agc2_fixed_gain_db;
}
if (settings_.use_verbose_logging) {
std::cout << " agc2_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
// TODO(peah): Add support for controlling the Experimental AGC from the
// command line.
if (msg.has_noise_robust_agc_enabled()) {
config.Set<ExperimentalAgc>(
new ExperimentalAgc(msg.noise_robust_agc_enabled()));
if (settings_.use_verbose_logging) {
std::cout << " noise_robust_agc_enabled: "
<< (msg.noise_robust_agc_enabled() ? "true" : "false")
<< std::endl;
}
}
if (msg.has_transient_suppression_enabled() || settings_.use_ts) {
bool enable = settings_.use_ts ? *settings_.use_ts
: msg.transient_suppression_enabled();
config.Set<ExperimentalNs>(new ExperimentalNs(enable));
if (settings_.use_verbose_logging) {
std::cout << " transient_suppression_enabled: "
<< (enable ? "true" : "false") << std::endl;
}
}
if (msg.has_hpf_enabled() || settings_.use_hpf) {
bool enable = settings_.use_hpf ? *settings_.use_hpf : msg.hpf_enabled();
apm_config.high_pass_filter.enabled = enable;
if (settings_.use_verbose_logging) {
std::cout << " hpf_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
if (msg.has_ns_enabled() || settings_.use_ns) {
bool enable = settings_.use_ns ? *settings_.use_ns : msg.ns_enabled();
apm_config.noise_suppression.enabled = enable;
if (settings_.use_verbose_logging) {
std::cout << " ns_enabled: " << (enable ? "true" : "false")
<< std::endl;
}
}
if (msg.has_ns_level() || settings_.ns_level) {
int level = settings_.ns_level ? *settings_.ns_level : msg.ns_level();
apm_config.noise_suppression.level =
static_cast<AudioProcessing::Config::NoiseSuppression::Level>(level);
if (settings_.use_verbose_logging) {
std::cout << " ns_level: " << level << std::endl;
}
}
if (msg.has_pre_amplifier_enabled() || settings_.use_pre_amplifier) {
const bool enable = settings_.use_pre_amplifier
? *settings_.use_pre_amplifier
: msg.pre_amplifier_enabled();
apm_config.pre_amplifier.enabled = enable;
}
if (msg.has_pre_amplifier_fixed_gain_factor() ||
settings_.pre_amplifier_gain_factor) {
const float gain = settings_.pre_amplifier_gain_factor
? *settings_.pre_amplifier_gain_factor
: msg.pre_amplifier_fixed_gain_factor();
apm_config.pre_amplifier.fixed_gain_factor = gain;
}
if (settings_.use_verbose_logging && msg.has_experiments_description() &&
!msg.experiments_description().empty()) {
std::cout << " experiments not included by default in the simulation: "
<< msg.experiments_description() << std::endl;
}
if (settings_.use_refined_adaptive_filter) {
config.Set<RefinedAdaptiveFilter>(
new RefinedAdaptiveFilter(*settings_.use_refined_adaptive_filter));
}
if (settings_.use_ed) {
apm_config.residual_echo_detector.enabled = *settings_.use_ed;
}
ap_->ApplyConfig(apm_config);
ap_->SetExtraOptions(config);
}
}
void AecDumpBasedSimulator::HandleMessage(const webrtc::audioproc::Init& msg) {
RTC_CHECK(msg.has_sample_rate());
RTC_CHECK(msg.has_num_input_channels());
RTC_CHECK(msg.has_num_reverse_channels());
RTC_CHECK(msg.has_reverse_sample_rate());
MaybeOpenCallOrderFile();
if (settings_.use_verbose_logging) {
std::cout << "Init at frame:" << std::endl;
std::cout << " Forward: " << get_num_process_stream_calls() << std::endl;
std::cout << " Reverse: " << get_num_reverse_process_stream_calls()
<< std::endl;
}
int num_output_channels;
if (settings_.output_num_channels) {
num_output_channels = *settings_.output_num_channels;
} else {
num_output_channels = msg.has_num_output_channels()
? msg.num_output_channels()
: msg.num_input_channels();
}
int output_sample_rate;
if (settings_.output_sample_rate_hz) {
output_sample_rate = *settings_.output_sample_rate_hz;
} else {
output_sample_rate = msg.has_output_sample_rate() ? msg.output_sample_rate()
: msg.sample_rate();
}
int num_reverse_output_channels;
if (settings_.reverse_output_num_channels) {
num_reverse_output_channels = *settings_.reverse_output_num_channels;
} else {
num_reverse_output_channels = msg.has_num_reverse_output_channels()
? msg.num_reverse_output_channels()
: msg.num_reverse_channels();
}
int reverse_output_sample_rate;
if (settings_.reverse_output_sample_rate_hz) {
reverse_output_sample_rate = *settings_.reverse_output_sample_rate_hz;
} else {
reverse_output_sample_rate = msg.has_reverse_output_sample_rate()
? msg.reverse_output_sample_rate()
: msg.reverse_sample_rate();
}
SetupBuffersConfigsOutputs(
msg.sample_rate(), output_sample_rate, msg.reverse_sample_rate(),
reverse_output_sample_rate, msg.num_input_channels(), num_output_channels,
msg.num_reverse_channels(), num_reverse_output_channels);
}
void AecDumpBasedSimulator::HandleMessage(
const webrtc::audioproc::Stream& msg) {
if (call_order_output_file_) {
*call_order_output_file_ << "c";
}
PrepareProcessStreamCall(msg);
ProcessStream(interface_used_ == InterfaceType::kFixedInterface);
VerifyProcessStreamBitExactness(msg);
}
void AecDumpBasedSimulator::HandleMessage(
const webrtc::audioproc::ReverseStream& msg) {
if (call_order_output_file_) {
*call_order_output_file_ << "r";
}
PrepareReverseProcessStreamCall(msg);
ProcessReverseStream(interface_used_ == InterfaceType::kFixedInterface);
}
void AecDumpBasedSimulator::HandleMessage(
const webrtc::audioproc::RuntimeSetting& msg) {
RTC_CHECK(ap_.get());
if (msg.has_capture_pre_gain()) {
// Handle capture pre-gain runtime setting only if not overridden.
if ((!settings_.use_pre_amplifier || *settings_.use_pre_amplifier) &&
!settings_.pre_amplifier_gain_factor) {
ap_->SetRuntimeSetting(
AudioProcessing::RuntimeSetting::CreateCapturePreGain(
msg.capture_pre_gain()));
}
} else if (msg.has_capture_fixed_post_gain()) {
// Handle capture fixed-post-gain runtime setting only if not overridden.
if ((!settings_.use_agc2 || *settings_.use_agc2) &&
!settings_.agc2_fixed_gain_db) {
ap_->SetRuntimeSetting(
AudioProcessing::RuntimeSetting::CreateCaptureFixedPostGain(
msg.capture_fixed_post_gain()));
}
}
}
void AecDumpBasedSimulator::MaybeOpenCallOrderFile() {
if (settings_.call_order_output_filename.has_value()) {
const std::string filename = settings_.store_intermediate_output
? *settings_.call_order_output_filename +
"_" +
std::to_string(output_reset_counter_)
: *settings_.call_order_output_filename;
call_order_output_file_ = absl::make_unique<std::ofstream>(filename);
}
}
} // namespace test
} // namespace webrtc