This CL updates and extends the audioproc_f command line
tool to support all the functionality needed for simulating and analyzing the audio processing module behavior during calls. BUG= Review-Url: https://codereview.webrtc.org/1907223003 Cr-Commit-Position: refs/heads/master@{#12882}
This commit is contained in:
parent
13deaad1bd
commit
60a189f1bf
@ -129,8 +129,12 @@
|
||||
'<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
|
||||
],
|
||||
'sources': [
|
||||
'test/audio_file_processor.cc',
|
||||
'test/audio_file_processor.h',
|
||||
'test/audio_processing_simulator.cc',
|
||||
'test/audio_processing_simulator.h',
|
||||
'test/aec_dump_based_simulator.cc',
|
||||
'test/aec_dump_based_simulator.h',
|
||||
'test/wav_based_simulator.cc',
|
||||
'test/wav_based_simulator.h',
|
||||
'test/audioproc_float.cc',
|
||||
],
|
||||
},
|
||||
|
||||
506
webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc
Normal file
506
webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc
Normal file
@ -0,0 +1,506 @@
|
||||
/*
|
||||
* 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 <iostream>
|
||||
|
||||
#include "webrtc/modules/audio_processing/test/aec_dump_based_simulator.h"
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/modules/audio_processing/test/protobuf_utils.h"
|
||||
#include "webrtc/test/testsupport/trace_to_stderr.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 {
|
||||
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
|
||||
|
||||
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_[0]) * fwd_frame_.samples_per_channel_ *
|
||||
fwd_frame_.num_channels_,
|
||||
msg.input_data().size());
|
||||
memcpy(fwd_frame_.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 (int i = 0; i < msg.input_channel_size(); ++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 (!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_.stream_drift_samples) {
|
||||
if (msg.has_drift()) {
|
||||
ap_->echo_cancellation()->set_stream_drift_samples(msg.drift());
|
||||
}
|
||||
} else {
|
||||
ap_->echo_cancellation()->set_stream_drift_samples(
|
||||
*settings_.stream_drift_samples);
|
||||
}
|
||||
|
||||
if (!settings_.use_ts) {
|
||||
if (msg.has_keypress()) {
|
||||
ap_->set_stream_key_pressed(msg.keypress());
|
||||
}
|
||||
} else {
|
||||
ap_->set_stream_key_pressed(*settings_.use_ts);
|
||||
}
|
||||
|
||||
// TODO(peah): Add support for controlling the analog level via the
|
||||
// command-line.
|
||||
if (msg.has_level()) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->gain_control()->set_stream_analog_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_.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() {
|
||||
std::unique_ptr<test::TraceToStderr> trace_to_stderr;
|
||||
if (settings_.use_verbose_logging) {
|
||||
trace_to_stderr.reset(new test::TraceToStderr(true));
|
||||
}
|
||||
|
||||
CreateAudioProcessor();
|
||||
dump_input_file_ = OpenFile(settings_.aec_dump_input_filename->c_str(), "rb");
|
||||
|
||||
webrtc::audioproc::Event event_msg;
|
||||
int num_forward_chunks_processed = 0;
|
||||
const float kOneBykChunksPerSecond =
|
||||
1.f / AudioProcessingSimulator::kChunksPerSecond;
|
||||
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;
|
||||
default:
|
||||
RTC_CHECK(false);
|
||||
}
|
||||
if (trace_to_stderr) {
|
||||
trace_to_stderr->SetTimeSeconds(num_forward_chunks_processed *
|
||||
kOneBykChunksPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (msg.has_aec_enabled() || settings_.use_aec) {
|
||||
bool enable = settings_.use_aec ? *settings_.use_aec : msg.aec_enabled();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_cancellation()->Enable(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) {
|
||||
bool enable = settings_.use_drift_compensation
|
||||
? *settings_.use_drift_compensation
|
||||
: msg.aec_drift_compensation_enabled();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_cancellation()->enable_drift_compensation(enable));
|
||||
if (settings_.use_verbose_logging) {
|
||||
std::cout << " aec_drift_compensation_enabled: "
|
||||
<< (enable ? "true" : "false") << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
int level = settings_.aec_suppression_level
|
||||
? *settings_.aec_suppression_level
|
||||
: msg.aec_suppression_level();
|
||||
RTC_CHECK_EQ(
|
||||
AudioProcessing::kNoError,
|
||||
ap_->echo_cancellation()->set_suppression_level(
|
||||
static_cast<webrtc::EchoCancellation::SuppressionLevel>(level)));
|
||||
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();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_control_mobile()->Enable(enable));
|
||||
if (settings_.use_verbose_logging) {
|
||||
std::cout << " aecm_enabled: " << (enable ? "true" : "false")
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.has_aecm_comfort_noise_enabled() ||
|
||||
settings_.use_aecm_comfort_noise) {
|
||||
bool enable = settings_.use_aecm_comfort_noise
|
||||
? *settings_.use_aecm_comfort_noise
|
||||
: msg.aecm_comfort_noise_enabled();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_control_mobile()->enable_comfort_noise(enable));
|
||||
if (settings_.use_verbose_logging) {
|
||||
std::cout << " aecm_comfort_noise_enabled: "
|
||||
<< (enable ? "true" : "false") << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.has_aecm_routing_mode() || settings_.aecm_routing_mode) {
|
||||
int routing_mode = settings_.aecm_routing_mode
|
||||
? *settings_.aecm_routing_mode
|
||||
: msg.aecm_routing_mode();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_control_mobile()->set_routing_mode(
|
||||
static_cast<webrtc::EchoControlMobile::RoutingMode>(
|
||||
routing_mode)));
|
||||
if (settings_.use_verbose_logging) {
|
||||
std::cout << " aecm_routing_mode: " << routing_mode << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->high_pass_filter()->Enable(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();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->noise_suppression()->Enable(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();
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->noise_suppression()->set_level(
|
||||
static_cast<NoiseSuppression::Level>(level)));
|
||||
if (settings_.use_verbose_logging) {
|
||||
std::cout << " ns_level: " << level << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings_.use_verbose_logging && msg.has_experiments_description() &&
|
||||
msg.experiments_description().size() > 0) {
|
||||
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_aec3) {
|
||||
config.Set<EchoCanceller3>(new EchoCanceller3(*settings_.use_aec3));
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
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) {
|
||||
PrepareProcessStreamCall(msg);
|
||||
ProcessStream(interface_used_ == InterfaceType::kFixedInterface);
|
||||
VerifyProcessStreamBitExactness(msg);
|
||||
}
|
||||
|
||||
void AecDumpBasedSimulator::HandleMessage(
|
||||
const webrtc::audioproc::ReverseStream& msg) {
|
||||
PrepareReverseProcessStreamCall(msg);
|
||||
ProcessReverseStream(interface_used_ == InterfaceType::kFixedInterface);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AEC_DUMP_BASED_SIMULATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AEC_DUMP_BASED_SIMULATOR_H_
|
||||
|
||||
#include "webrtc/modules/audio_processing/test/audio_processing_simulator.h"
|
||||
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
|
||||
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
|
||||
#include "external/webrtc/webrtc/modules/audio_processing/debug.pb.h"
|
||||
#else
|
||||
#include "webrtc/modules/audio_processing/debug.pb.h"
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Used to perform an audio processing simulation from an aec dump.
|
||||
class AecDumpBasedSimulator final : public AudioProcessingSimulator {
|
||||
public:
|
||||
explicit AecDumpBasedSimulator(const SimulationSettings& settings)
|
||||
: AudioProcessingSimulator(settings) {}
|
||||
virtual ~AecDumpBasedSimulator() {}
|
||||
|
||||
// Processes the messages in the aecdump file.
|
||||
void Process() override;
|
||||
|
||||
private:
|
||||
void HandleMessage(const webrtc::audioproc::Init& msg);
|
||||
void HandleMessage(const webrtc::audioproc::Stream& msg);
|
||||
void HandleMessage(const webrtc::audioproc::ReverseStream& msg);
|
||||
void HandleMessage(const webrtc::audioproc::Config& msg);
|
||||
void PrepareProcessStreamCall(const webrtc::audioproc::Stream& msg);
|
||||
void PrepareReverseProcessStreamCall(
|
||||
const webrtc::audioproc::ReverseStream& msg);
|
||||
void VerifyProcessStreamBitExactness(const webrtc::audioproc::Stream& msg);
|
||||
|
||||
enum InterfaceType {
|
||||
kFixedInterface,
|
||||
kFloatInterface,
|
||||
kNotSpecified,
|
||||
};
|
||||
|
||||
FILE* dump_input_file_;
|
||||
InterfaceType interface_used_ = InterfaceType::kNotSpecified;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AecDumpBasedSimulator);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AEC_DUMP_BASED_SIMULATOR_H_
|
||||
98
webrtc/modules/audio_processing/test/aec_dump_processor.h
Normal file
98
webrtc/modules/audio_processing/test/aec_dump_processor.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AEC_DUMP_PROCESSOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AEC_DUMP_PROCESSOR_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/base/timeutils.h"
|
||||
#include "webrtc/base/optional.h"
|
||||
#include "webrtc/common_audio/channel_buffer.h"
|
||||
#include "webrtc/common_audio/wav_file.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/test/audio_file_processor.h"
|
||||
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
||||
|
||||
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
|
||||
#include "external/webrtc/webrtc/modules/audio_processing/debug.pb.h"
|
||||
#else
|
||||
#include "webrtc/modules/audio_processing/debug.pb.h"
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Used to read from an aecdump file and write to a WavWriter.
|
||||
class AecDumpFileProcessor final : public AudioFileProcessor {
|
||||
public:
|
||||
AecDumpFileProcessor(std::unique_ptr<AudioProcessing> ap,
|
||||
FILE* dump_file,
|
||||
std::string out_filename,
|
||||
std::string reverse_out_filename,
|
||||
rtc::Optional<int> out_sample_rate_hz,
|
||||
rtc::Optional<int> out_num_channels,
|
||||
rtc::Optional<int> reverse_out_sample_rate_hz,
|
||||
rtc::Optional<int> reverse_out_num_channels,
|
||||
bool override_config_message);
|
||||
|
||||
virtual ~AecDumpFileProcessor();
|
||||
|
||||
// Processes the messages in the aecdump file and returns
|
||||
// the number of forward stream chunks processed.
|
||||
size_t Process(bool verbose_logging) override;
|
||||
|
||||
private:
|
||||
void HandleMessage(const webrtc::audioproc::Init& msg);
|
||||
void HandleMessage(const webrtc::audioproc::Stream& msg);
|
||||
void HandleMessage(const webrtc::audioproc::ReverseStream& msg);
|
||||
void HandleMessage(const webrtc::audioproc::Config& msg);
|
||||
|
||||
enum InterfaceType {
|
||||
kIntInterface,
|
||||
kFloatInterface,
|
||||
kNotSpecified,
|
||||
};
|
||||
|
||||
std::unique_ptr<AudioProcessing> ap_;
|
||||
FILE* dump_file_;
|
||||
std::string out_filename_;
|
||||
std::string reverse_out_filename_;
|
||||
rtc::Optional<int> out_sample_rate_hz_;
|
||||
rtc::Optional<int> out_num_channels_;
|
||||
rtc::Optional<int> reverse_out_sample_rate_hz_;
|
||||
rtc::Optional<int> reverse_out_num_channels_;
|
||||
bool override_config_message_;
|
||||
|
||||
std::unique_ptr<ChannelBuffer<float>> in_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> out_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_out_buf_;
|
||||
std::unique_ptr<WavWriter> out_file_;
|
||||
std::unique_ptr<WavWriter> reverse_out_file_;
|
||||
StreamConfig input_config_;
|
||||
StreamConfig reverse_config_;
|
||||
StreamConfig output_config_;
|
||||
StreamConfig reverse_output_config_;
|
||||
std::unique_ptr<ChannelBufferWavWriter> buffer_writer_;
|
||||
std::unique_ptr<ChannelBufferWavWriter> reverse_buffer_writer_;
|
||||
AudioFrame far_frame_;
|
||||
AudioFrame near_frame_;
|
||||
InterfaceType interface_used_ = InterfaceType::kNotSpecified;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AEC_DUMP_PROCESSOR_H_
|
||||
@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015 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 "webrtc/modules/audio_processing/test/audio_file_processor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/modules/audio_processing/test/protobuf_utils.h"
|
||||
|
||||
using rtc::CheckedDivExact;
|
||||
using std::vector;
|
||||
using webrtc::audioproc::Event;
|
||||
using webrtc::audioproc::Init;
|
||||
using webrtc::audioproc::ReverseStream;
|
||||
using webrtc::audioproc::Stream;
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Returns a StreamConfig corresponding to file.
|
||||
StreamConfig GetStreamConfig(const WavFile& file) {
|
||||
return StreamConfig(file.sample_rate(), file.num_channels());
|
||||
}
|
||||
|
||||
// Returns a ChannelBuffer corresponding to file.
|
||||
ChannelBuffer<float> GetChannelBuffer(const WavFile& file) {
|
||||
return ChannelBuffer<float>(
|
||||
CheckedDivExact(file.sample_rate(), AudioFileProcessor::kChunksPerSecond),
|
||||
file.num_channels());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WavFileProcessor::WavFileProcessor(std::unique_ptr<AudioProcessing> ap,
|
||||
std::unique_ptr<WavReader> in_file,
|
||||
std::unique_ptr<WavWriter> out_file,
|
||||
std::unique_ptr<WavReader> reverse_in_file,
|
||||
std::unique_ptr<WavWriter> reverse_out_file)
|
||||
: ap_(std::move(ap)),
|
||||
in_buf_(GetChannelBuffer(*in_file)),
|
||||
out_buf_(GetChannelBuffer(*out_file)),
|
||||
input_config_(GetStreamConfig(*in_file)),
|
||||
output_config_(GetStreamConfig(*out_file)),
|
||||
buffer_reader_(std::move(in_file)),
|
||||
buffer_writer_(std::move(out_file)) {
|
||||
if (reverse_in_file) {
|
||||
const WavFile* reverse_out_config;
|
||||
if (reverse_out_file) {
|
||||
reverse_out_config = reverse_out_file.get();
|
||||
} else {
|
||||
reverse_out_config = reverse_in_file.get();
|
||||
}
|
||||
reverse_in_buf_.reset(
|
||||
new ChannelBuffer<float>(GetChannelBuffer(*reverse_in_file)));
|
||||
reverse_out_buf_.reset(
|
||||
new ChannelBuffer<float>(GetChannelBuffer(*reverse_out_config)));
|
||||
reverse_input_config_.reset(
|
||||
new StreamConfig(GetStreamConfig(*reverse_in_file)));
|
||||
reverse_output_config_.reset(
|
||||
new StreamConfig(GetStreamConfig(*reverse_out_config)));
|
||||
reverse_buffer_reader_.reset(
|
||||
new ChannelBufferWavReader(std::move(reverse_in_file)));
|
||||
if (reverse_out_file) {
|
||||
reverse_buffer_writer_.reset(
|
||||
new ChannelBufferWavWriter(std::move(reverse_out_file)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WavFileProcessor::ProcessChunk() {
|
||||
if (!buffer_reader_.Read(&in_buf_)) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(kNoErr,
|
||||
ap_->ProcessStream(in_buf_.channels(), input_config_,
|
||||
output_config_, out_buf_.channels()));
|
||||
}
|
||||
buffer_writer_.Write(out_buf_);
|
||||
if (reverse_buffer_reader_) {
|
||||
if (!reverse_buffer_reader_->Read(reverse_in_buf_.get())) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(kNoErr,
|
||||
ap_->ProcessReverseStream(reverse_in_buf_->channels(),
|
||||
*reverse_input_config_.get(),
|
||||
*reverse_output_config_.get(),
|
||||
reverse_out_buf_->channels()));
|
||||
}
|
||||
if (reverse_buffer_writer_) {
|
||||
reverse_buffer_writer_->Write(*reverse_out_buf_.get());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AecDumpFileProcessor::AecDumpFileProcessor(std::unique_ptr<AudioProcessing> ap,
|
||||
FILE* dump_file,
|
||||
std::unique_ptr<WavWriter> out_file)
|
||||
: ap_(std::move(ap)),
|
||||
dump_file_(dump_file),
|
||||
out_buf_(GetChannelBuffer(*out_file)),
|
||||
output_config_(GetStreamConfig(*out_file)),
|
||||
buffer_writer_(std::move(out_file)) {
|
||||
RTC_CHECK(dump_file_) << "Could not open dump file for reading.";
|
||||
}
|
||||
|
||||
AecDumpFileProcessor::~AecDumpFileProcessor() {
|
||||
fclose(dump_file_);
|
||||
}
|
||||
|
||||
bool AecDumpFileProcessor::ProcessChunk() {
|
||||
Event event_msg;
|
||||
|
||||
// Continue until we process our first Stream message.
|
||||
do {
|
||||
if (!ReadMessageFromFile(dump_file_, &event_msg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event_msg.type() == Event::INIT) {
|
||||
RTC_CHECK(event_msg.has_init());
|
||||
HandleMessage(event_msg.init());
|
||||
|
||||
} else if (event_msg.type() == Event::STREAM) {
|
||||
RTC_CHECK(event_msg.has_stream());
|
||||
HandleMessage(event_msg.stream());
|
||||
|
||||
} else if (event_msg.type() == Event::REVERSE_STREAM) {
|
||||
RTC_CHECK(event_msg.has_reverse_stream());
|
||||
HandleMessage(event_msg.reverse_stream());
|
||||
}
|
||||
} while (event_msg.type() != Event::STREAM);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AecDumpFileProcessor::HandleMessage(const Init& msg) {
|
||||
RTC_CHECK(msg.has_sample_rate());
|
||||
RTC_CHECK(msg.has_num_input_channels());
|
||||
RTC_CHECK(msg.has_num_reverse_channels());
|
||||
|
||||
in_buf_.reset(new ChannelBuffer<float>(
|
||||
CheckedDivExact(msg.sample_rate(), kChunksPerSecond),
|
||||
msg.num_input_channels()));
|
||||
const int reverse_sample_rate = msg.has_reverse_sample_rate()
|
||||
? msg.reverse_sample_rate()
|
||||
: msg.sample_rate();
|
||||
reverse_buf_.reset(new ChannelBuffer<float>(
|
||||
CheckedDivExact(reverse_sample_rate, kChunksPerSecond),
|
||||
msg.num_reverse_channels()));
|
||||
input_config_ = StreamConfig(msg.sample_rate(), msg.num_input_channels());
|
||||
reverse_config_ =
|
||||
StreamConfig(reverse_sample_rate, msg.num_reverse_channels());
|
||||
|
||||
const ProcessingConfig config = {
|
||||
{input_config_, output_config_, reverse_config_, reverse_config_}};
|
||||
RTC_CHECK_EQ(kNoErr, ap_->Initialize(config));
|
||||
}
|
||||
|
||||
void AecDumpFileProcessor::HandleMessage(const Stream& msg) {
|
||||
RTC_CHECK(!msg.has_input_data());
|
||||
RTC_CHECK_EQ(in_buf_->num_channels(),
|
||||
static_cast<size_t>(msg.input_channel_size()));
|
||||
|
||||
for (int i = 0; i < msg.input_channel_size(); ++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());
|
||||
}
|
||||
{
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(kNoErr, ap_->set_stream_delay_ms(msg.delay()));
|
||||
ap_->echo_cancellation()->set_stream_drift_samples(msg.drift());
|
||||
if (msg.has_keypress()) {
|
||||
ap_->set_stream_key_pressed(msg.keypress());
|
||||
}
|
||||
RTC_CHECK_EQ(kNoErr,
|
||||
ap_->ProcessStream(in_buf_->channels(), input_config_,
|
||||
output_config_, out_buf_.channels()));
|
||||
}
|
||||
|
||||
buffer_writer_.Write(out_buf_);
|
||||
}
|
||||
|
||||
void AecDumpFileProcessor::HandleMessage(const ReverseStream& msg) {
|
||||
RTC_CHECK(!msg.has_data());
|
||||
RTC_CHECK_EQ(reverse_buf_->num_channels(),
|
||||
static_cast<size_t>(msg.channel_size()));
|
||||
|
||||
for (int i = 0; i < msg.channel_size(); ++i) {
|
||||
RTC_CHECK_EQ(reverse_buf_->num_frames() * sizeof(*in_buf_->channels()[i]),
|
||||
msg.channel(i).size());
|
||||
std::memcpy(reverse_buf_->channels()[i], msg.channel(i).data(),
|
||||
msg.channel(i).size());
|
||||
}
|
||||
{
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
// TODO(ajm): This currently discards the processed output, which is needed
|
||||
// for e.g. intelligibility enhancement.
|
||||
RTC_CHECK_EQ(kNoErr, ap_->ProcessReverseStream(
|
||||
reverse_buf_->channels(), reverse_config_,
|
||||
reverse_config_, reverse_buf_->channels()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015 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 WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/base/timeutils.h"
|
||||
#include "webrtc/common_audio/channel_buffer.h"
|
||||
#include "webrtc/common_audio/wav_file.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
||||
|
||||
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
|
||||
#include "external/webrtc/webrtc/modules/audio_processing/debug.pb.h"
|
||||
#else
|
||||
#include "webrtc/modules/audio_processing/debug.pb.h"
|
||||
#endif
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Holds a few statistics about a series of TickIntervals.
|
||||
struct TickIntervalStats {
|
||||
TickIntervalStats() : min(std::numeric_limits<int64_t>::max()) {}
|
||||
int64_t sum;
|
||||
int64_t max;
|
||||
int64_t min;
|
||||
};
|
||||
|
||||
// Interface for processing an input file with an AudioProcessing instance and
|
||||
// dumping the results to an output file.
|
||||
class AudioFileProcessor {
|
||||
public:
|
||||
static const int kChunksPerSecond = 1000 / AudioProcessing::kChunkSizeMs;
|
||||
|
||||
virtual ~AudioFileProcessor() {}
|
||||
|
||||
// Processes one AudioProcessing::kChunkSizeMs of data from the input file and
|
||||
// writes to the output file.
|
||||
virtual bool ProcessChunk() = 0;
|
||||
|
||||
// Returns the execution time of all AudioProcessing calls.
|
||||
const TickIntervalStats& proc_time() const { return proc_time_; }
|
||||
|
||||
protected:
|
||||
// RAII class for execution time measurement. Updates the provided
|
||||
// TickIntervalStats based on the time between ScopedTimer creation and
|
||||
// leaving the enclosing scope.
|
||||
class ScopedTimer {
|
||||
public:
|
||||
explicit ScopedTimer(TickIntervalStats* proc_time)
|
||||
: proc_time_(proc_time), start_time_(rtc::TimeNanos()) {}
|
||||
|
||||
~ScopedTimer() {
|
||||
int64_t interval = rtc::TimeNanos() - start_time_;
|
||||
proc_time_->sum += interval;
|
||||
proc_time_->max = std::max(proc_time_->max, interval);
|
||||
proc_time_->min = std::min(proc_time_->min, interval);
|
||||
}
|
||||
|
||||
private:
|
||||
TickIntervalStats* const proc_time_;
|
||||
int64_t start_time_;
|
||||
};
|
||||
|
||||
TickIntervalStats* mutable_proc_time() { return &proc_time_; }
|
||||
|
||||
private:
|
||||
TickIntervalStats proc_time_;
|
||||
};
|
||||
|
||||
// Used to read from and write to WavFile objects.
|
||||
class WavFileProcessor final : public AudioFileProcessor {
|
||||
public:
|
||||
// Takes ownership of all parameters.
|
||||
WavFileProcessor(std::unique_ptr<AudioProcessing> ap,
|
||||
std::unique_ptr<WavReader> in_file,
|
||||
std::unique_ptr<WavWriter> out_file,
|
||||
std::unique_ptr<WavReader> reverse_in_file,
|
||||
std::unique_ptr<WavWriter> reverse_out_file);
|
||||
virtual ~WavFileProcessor() {}
|
||||
|
||||
// Processes one chunk from the WAV input and writes to the WAV output.
|
||||
bool ProcessChunk() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AudioProcessing> ap_;
|
||||
|
||||
ChannelBuffer<float> in_buf_;
|
||||
ChannelBuffer<float> out_buf_;
|
||||
const StreamConfig input_config_;
|
||||
const StreamConfig output_config_;
|
||||
ChannelBufferWavReader buffer_reader_;
|
||||
ChannelBufferWavWriter buffer_writer_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_in_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_out_buf_;
|
||||
std::unique_ptr<StreamConfig> reverse_input_config_;
|
||||
std::unique_ptr<StreamConfig> reverse_output_config_;
|
||||
std::unique_ptr<ChannelBufferWavReader> reverse_buffer_reader_;
|
||||
std::unique_ptr<ChannelBufferWavWriter> reverse_buffer_writer_;
|
||||
};
|
||||
|
||||
// Used to read from an aecdump file and write to a WavWriter.
|
||||
class AecDumpFileProcessor final : public AudioFileProcessor {
|
||||
public:
|
||||
// Takes ownership of all parameters.
|
||||
AecDumpFileProcessor(std::unique_ptr<AudioProcessing> ap,
|
||||
FILE* dump_file,
|
||||
std::unique_ptr<WavWriter> out_file);
|
||||
|
||||
virtual ~AecDumpFileProcessor();
|
||||
|
||||
// Processes messages from the aecdump file until the first Stream message is
|
||||
// completed. Passes other data from the aecdump messages as appropriate.
|
||||
bool ProcessChunk() override;
|
||||
|
||||
private:
|
||||
void HandleMessage(const webrtc::audioproc::Init& msg);
|
||||
void HandleMessage(const webrtc::audioproc::Stream& msg);
|
||||
void HandleMessage(const webrtc::audioproc::ReverseStream& msg);
|
||||
|
||||
std::unique_ptr<AudioProcessing> ap_;
|
||||
FILE* dump_file_;
|
||||
|
||||
std::unique_ptr<ChannelBuffer<float>> in_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_buf_;
|
||||
ChannelBuffer<float> out_buf_;
|
||||
StreamConfig input_config_;
|
||||
StreamConfig reverse_config_;
|
||||
const StreamConfig output_config_;
|
||||
ChannelBufferWavWriter buffer_writer_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
|
||||
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/test/audio_processing_simulator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/base/stringutils.h"
|
||||
#include "webrtc/common_audio/include/audio_util.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
void CopyFromAudioFrame(const AudioFrame& src, ChannelBuffer<float>* dest) {
|
||||
RTC_CHECK_EQ(src.num_channels_, dest->num_channels());
|
||||
RTC_CHECK_EQ(src.samples_per_channel_, dest->num_frames());
|
||||
// Copy the data from the input buffer.
|
||||
std::vector<float> tmp(src.samples_per_channel_ * src.num_channels_);
|
||||
S16ToFloat(src.data_, tmp.size(), tmp.data());
|
||||
Deinterleave(tmp.data(), src.samples_per_channel_, src.num_channels_,
|
||||
dest->channels());
|
||||
}
|
||||
|
||||
std::string GetIndexedOutputWavFilename(const std::string& wav_name,
|
||||
int counter) {
|
||||
std::stringstream ss;
|
||||
ss << wav_name.substr(0, wav_name.size() - 4) << "_" << counter
|
||||
<< wav_name.substr(wav_name.size() - 4);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CopyToAudioFrame(const ChannelBuffer<float>& src, AudioFrame* dest) {
|
||||
RTC_CHECK_EQ(src.num_channels(), dest->num_channels_);
|
||||
RTC_CHECK_EQ(src.num_frames(), dest->samples_per_channel_);
|
||||
for (size_t ch = 0; ch < dest->num_channels_; ++ch) {
|
||||
for (size_t sample = 0; sample < dest->samples_per_channel_; ++sample) {
|
||||
dest->data_[sample * dest->num_channels_ + ch] =
|
||||
src.channels()[ch][sample] * 32767;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioProcessingSimulator::ScopedTimer::~ScopedTimer() {
|
||||
int64_t interval = rtc::TimeNanos() - start_time_;
|
||||
proc_time_->sum += interval;
|
||||
proc_time_->max = std::max(proc_time_->max, interval);
|
||||
proc_time_->min = std::min(proc_time_->min, interval);
|
||||
}
|
||||
|
||||
void AudioProcessingSimulator::ProcessStream(bool fixed_interface) {
|
||||
if (fixed_interface) {
|
||||
{
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError, ap_->ProcessStream(&fwd_frame_));
|
||||
}
|
||||
CopyFromAudioFrame(fwd_frame_, out_buf_.get());
|
||||
} else {
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->ProcessStream(in_buf_->channels(), in_config_,
|
||||
out_config_, out_buf_->channels()));
|
||||
}
|
||||
|
||||
if (buffer_writer_) {
|
||||
buffer_writer_->Write(*out_buf_);
|
||||
}
|
||||
|
||||
++num_process_stream_calls_;
|
||||
}
|
||||
|
||||
void AudioProcessingSimulator::ProcessReverseStream(bool fixed_interface) {
|
||||
if (fixed_interface) {
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError, ap_->ProcessStream(&rev_frame_));
|
||||
CopyFromAudioFrame(rev_frame_, reverse_out_buf_.get());
|
||||
|
||||
} else {
|
||||
const auto st = ScopedTimer(mutable_proc_time());
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->ProcessReverseStream(
|
||||
reverse_in_buf_->channels(), reverse_in_config_,
|
||||
reverse_out_config_, reverse_out_buf_->channels()));
|
||||
}
|
||||
|
||||
if (reverse_buffer_writer_) {
|
||||
reverse_buffer_writer_->Write(*reverse_out_buf_);
|
||||
}
|
||||
|
||||
++num_reverse_process_stream_calls_;
|
||||
}
|
||||
|
||||
void AudioProcessingSimulator::SetupBuffersConfigsOutputs(
|
||||
int input_sample_rate_hz,
|
||||
int output_sample_rate_hz,
|
||||
int reverse_input_sample_rate_hz,
|
||||
int reverse_output_sample_rate_hz,
|
||||
int input_num_channels,
|
||||
int output_num_channels,
|
||||
int reverse_input_num_channels,
|
||||
int reverse_output_num_channels) {
|
||||
in_config_ = StreamConfig(input_sample_rate_hz, input_num_channels);
|
||||
in_buf_.reset(new ChannelBuffer<float>(
|
||||
rtc::CheckedDivExact(input_sample_rate_hz, kChunksPerSecond),
|
||||
input_num_channels));
|
||||
|
||||
reverse_in_config_ =
|
||||
StreamConfig(reverse_input_sample_rate_hz, reverse_input_num_channels);
|
||||
reverse_in_buf_.reset(new ChannelBuffer<float>(
|
||||
rtc::CheckedDivExact(reverse_input_sample_rate_hz, kChunksPerSecond),
|
||||
reverse_input_num_channels));
|
||||
|
||||
out_config_ = StreamConfig(output_sample_rate_hz, output_num_channels);
|
||||
out_buf_.reset(new ChannelBuffer<float>(
|
||||
rtc::CheckedDivExact(output_sample_rate_hz, kChunksPerSecond),
|
||||
output_num_channels));
|
||||
|
||||
reverse_out_config_ =
|
||||
StreamConfig(reverse_output_sample_rate_hz, reverse_output_num_channels);
|
||||
reverse_out_buf_.reset(new ChannelBuffer<float>(
|
||||
rtc::CheckedDivExact(reverse_output_sample_rate_hz, kChunksPerSecond),
|
||||
reverse_output_num_channels));
|
||||
|
||||
fwd_frame_.sample_rate_hz_ = input_sample_rate_hz;
|
||||
fwd_frame_.samples_per_channel_ =
|
||||
rtc::CheckedDivExact(fwd_frame_.sample_rate_hz_, kChunksPerSecond);
|
||||
fwd_frame_.num_channels_ = input_num_channels;
|
||||
|
||||
rev_frame_.sample_rate_hz_ = reverse_input_sample_rate_hz;
|
||||
rev_frame_.samples_per_channel_ =
|
||||
rtc::CheckedDivExact(rev_frame_.sample_rate_hz_, kChunksPerSecond);
|
||||
rev_frame_.num_channels_ = reverse_input_num_channels;
|
||||
|
||||
if (settings_.use_verbose_logging) {
|
||||
std::cout << "Sample rates:" << std::endl;
|
||||
std::cout << " Forward input: " << input_sample_rate_hz << std::endl;
|
||||
std::cout << " Forward output: " << output_sample_rate_hz << std::endl;
|
||||
std::cout << " Reverse input: " << reverse_input_sample_rate_hz
|
||||
<< std::endl;
|
||||
std::cout << " Reverse output: " << reverse_output_sample_rate_hz
|
||||
<< std::endl;
|
||||
std::cout << "Number of channels: " << std::endl;
|
||||
std::cout << " Forward input: " << input_num_channels << std::endl;
|
||||
std::cout << " Forward output: " << output_num_channels << std::endl;
|
||||
std::cout << " Reverse input: " << reverse_input_num_channels << std::endl;
|
||||
std::cout << " Reverse output: " << reverse_output_num_channels
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
SetupOutput();
|
||||
}
|
||||
|
||||
void AudioProcessingSimulator::SetupOutput() {
|
||||
if (settings_.output_filename) {
|
||||
std::string filename;
|
||||
if (settings_.store_intermediate_output) {
|
||||
filename = GetIndexedOutputWavFilename(*settings_.output_filename,
|
||||
output_reset_counter_);
|
||||
} else {
|
||||
filename = *settings_.output_filename;
|
||||
}
|
||||
|
||||
std::unique_ptr<WavWriter> out_file(
|
||||
new WavWriter(filename, out_config_.sample_rate_hz(),
|
||||
static_cast<size_t>(out_config_.num_channels())));
|
||||
buffer_writer_.reset(new ChannelBufferWavWriter(std::move(out_file)));
|
||||
}
|
||||
|
||||
if (settings_.reverse_output_filename) {
|
||||
std::string filename;
|
||||
if (settings_.store_intermediate_output) {
|
||||
filename = GetIndexedOutputWavFilename(*settings_.reverse_output_filename,
|
||||
output_reset_counter_);
|
||||
} else {
|
||||
filename = *settings_.reverse_output_filename;
|
||||
}
|
||||
|
||||
std::unique_ptr<WavWriter> reverse_out_file(
|
||||
new WavWriter(filename, reverse_out_config_.sample_rate_hz(),
|
||||
static_cast<size_t>(reverse_out_config_.num_channels())));
|
||||
reverse_buffer_writer_.reset(
|
||||
new ChannelBufferWavWriter(std::move(reverse_out_file)));
|
||||
}
|
||||
|
||||
++output_reset_counter_;
|
||||
}
|
||||
|
||||
void AudioProcessingSimulator::DestroyAudioProcessor() {
|
||||
if (settings_.aec_dump_output_filename) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError, ap_->StopDebugRecording());
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessingSimulator::CreateAudioProcessor() {
|
||||
Config config;
|
||||
if (settings_.use_bf && *settings_.use_bf) {
|
||||
config.Set<Beamforming>(new Beamforming(
|
||||
true, ParseArrayGeometry(*settings_.microphone_positions),
|
||||
SphericalPointf(DegreesToRadians(settings_.target_angle_degrees), 0.f,
|
||||
1.f)));
|
||||
}
|
||||
if (settings_.use_ts) {
|
||||
config.Set<ExperimentalNs>(new ExperimentalNs(*settings_.use_ts));
|
||||
}
|
||||
if (settings_.use_ie) {
|
||||
config.Set<Intelligibility>(new Intelligibility(*settings_.use_ie));
|
||||
}
|
||||
if (settings_.use_aec3) {
|
||||
config.Set<EchoCanceller3>(new EchoCanceller3(*settings_.use_aec3));
|
||||
}
|
||||
if (settings_.use_refined_adaptive_filter) {
|
||||
config.Set<RefinedAdaptiveFilter>(
|
||||
new RefinedAdaptiveFilter(*settings_.use_refined_adaptive_filter));
|
||||
}
|
||||
config.Set<ExtendedFilter>(new ExtendedFilter(
|
||||
!settings_.use_extended_filter || *settings_.use_extended_filter));
|
||||
config.Set<DelayAgnostic>(new DelayAgnostic(!settings_.use_delay_agnostic ||
|
||||
*settings_.use_delay_agnostic));
|
||||
|
||||
ap_.reset(AudioProcessing::Create(config));
|
||||
|
||||
if (settings_.use_aec) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_cancellation()->Enable(*settings_.use_aec));
|
||||
}
|
||||
if (settings_.use_aecm) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_control_mobile()->Enable(*settings_.use_aecm));
|
||||
}
|
||||
if (settings_.use_agc) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->gain_control()->Enable(*settings_.use_agc));
|
||||
}
|
||||
if (settings_.use_hpf) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->high_pass_filter()->Enable(*settings_.use_hpf));
|
||||
}
|
||||
if (settings_.use_ns) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->noise_suppression()->Enable(*settings_.use_ns));
|
||||
}
|
||||
if (settings_.use_le) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->level_estimator()->Enable(*settings_.use_le));
|
||||
}
|
||||
if (settings_.use_vad) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->voice_detection()->Enable(*settings_.use_vad));
|
||||
}
|
||||
if (settings_.use_agc_limiter) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError, ap_->gain_control()->enable_limiter(
|
||||
*settings_.use_agc_limiter));
|
||||
}
|
||||
if (settings_.agc_target_level) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->gain_control()->set_target_level_dbfs(
|
||||
*settings_.agc_target_level));
|
||||
}
|
||||
|
||||
if (settings_.agc_mode) {
|
||||
RTC_CHECK_EQ(
|
||||
AudioProcessing::kNoError,
|
||||
ap_->gain_control()->set_mode(
|
||||
static_cast<webrtc::GainControl::Mode>(*settings_.agc_mode)));
|
||||
}
|
||||
|
||||
if (settings_.use_drift_compensation) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_cancellation()->enable_drift_compensation(
|
||||
*settings_.use_drift_compensation));
|
||||
}
|
||||
|
||||
if (settings_.aec_suppression_level) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_cancellation()->set_suppression_level(
|
||||
static_cast<webrtc::EchoCancellation::SuppressionLevel>(
|
||||
*settings_.aec_suppression_level)));
|
||||
}
|
||||
|
||||
if (settings_.aecm_routing_mode) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_control_mobile()->set_routing_mode(
|
||||
static_cast<webrtc::EchoControlMobile::RoutingMode>(
|
||||
*settings_.aecm_routing_mode)));
|
||||
}
|
||||
|
||||
if (settings_.use_aecm_comfort_noise) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->echo_control_mobile()->enable_comfort_noise(
|
||||
*settings_.use_aecm_comfort_noise));
|
||||
}
|
||||
|
||||
if (settings_.vad_likelihood) {
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->voice_detection()->set_likelihood(
|
||||
static_cast<webrtc::VoiceDetection::Likelihood>(
|
||||
*settings_.vad_likelihood)));
|
||||
}
|
||||
if (settings_.ns_level) {
|
||||
RTC_CHECK_EQ(
|
||||
AudioProcessing::kNoError,
|
||||
ap_->noise_suppression()->set_level(
|
||||
static_cast<NoiseSuppression::Level>(*settings_.ns_level)));
|
||||
}
|
||||
|
||||
if (settings_.use_ts) {
|
||||
ap_->set_stream_key_pressed(*settings_.use_ts);
|
||||
}
|
||||
|
||||
if (settings_.aec_dump_output_filename) {
|
||||
size_t kMaxFilenameSize = AudioProcessing::kMaxFilenameSize;
|
||||
RTC_CHECK_LE(settings_.aec_dump_output_filename->size(), kMaxFilenameSize);
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->StartDebugRecording(
|
||||
settings_.aec_dump_output_filename->c_str(), -1));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_PROCESSING_SIMULATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_PROCESSING_SIMULATOR_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/base/timeutils.h"
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
#include "webrtc/base/optional.h"
|
||||
#include "webrtc/common_audio/channel_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Holds all the parameters available for controlling the simulation.
|
||||
struct SimulationSettings {
|
||||
rtc::Optional<int> stream_delay;
|
||||
rtc::Optional<int> stream_drift_samples;
|
||||
rtc::Optional<int> output_sample_rate_hz;
|
||||
rtc::Optional<int> output_num_channels;
|
||||
rtc::Optional<int> reverse_output_sample_rate_hz;
|
||||
rtc::Optional<int> reverse_output_num_channels;
|
||||
rtc::Optional<std::string> microphone_positions;
|
||||
int target_angle_degrees = 90;
|
||||
rtc::Optional<std::string> output_filename;
|
||||
rtc::Optional<std::string> reverse_output_filename;
|
||||
rtc::Optional<std::string> input_filename;
|
||||
rtc::Optional<std::string> reverse_input_filename;
|
||||
rtc::Optional<bool> use_aec;
|
||||
rtc::Optional<bool> use_aecm;
|
||||
rtc::Optional<bool> use_agc;
|
||||
rtc::Optional<bool> use_hpf;
|
||||
rtc::Optional<bool> use_ns;
|
||||
rtc::Optional<bool> use_ts;
|
||||
rtc::Optional<bool> use_bf;
|
||||
rtc::Optional<bool> use_ie;
|
||||
rtc::Optional<bool> use_vad;
|
||||
rtc::Optional<bool> use_le;
|
||||
rtc::Optional<bool> use_all;
|
||||
rtc::Optional<int> aec_suppression_level;
|
||||
rtc::Optional<bool> use_delay_agnostic;
|
||||
rtc::Optional<bool> use_extended_filter;
|
||||
rtc::Optional<bool> use_drift_compensation;
|
||||
rtc::Optional<bool> use_aec3;
|
||||
rtc::Optional<int> aecm_routing_mode;
|
||||
rtc::Optional<bool> use_aecm_comfort_noise;
|
||||
rtc::Optional<int> agc_mode;
|
||||
rtc::Optional<int> agc_target_level;
|
||||
rtc::Optional<bool> use_agc_limiter;
|
||||
rtc::Optional<int> agc_compression_gain;
|
||||
rtc::Optional<int> vad_likelihood;
|
||||
rtc::Optional<int> ns_level;
|
||||
rtc::Optional<bool> use_refined_adaptive_filter;
|
||||
bool report_performance = false;
|
||||
bool report_bitexactness = false;
|
||||
bool use_verbose_logging = false;
|
||||
bool discard_all_settings_in_aecdump = true;
|
||||
rtc::Optional<std::string> aec_dump_input_filename;
|
||||
rtc::Optional<std::string> aec_dump_output_filename;
|
||||
bool fixed_interface = false;
|
||||
bool store_intermediate_output = false;
|
||||
};
|
||||
|
||||
// Holds a few statistics about a series of TickIntervals.
|
||||
struct TickIntervalStats {
|
||||
TickIntervalStats() : min(std::numeric_limits<int64_t>::max()) {}
|
||||
int64_t sum;
|
||||
int64_t max;
|
||||
int64_t min;
|
||||
};
|
||||
|
||||
// Copies samples present in a ChannelBuffer into an AudioFrame.
|
||||
void CopyToAudioFrame(const ChannelBuffer<float>& src, AudioFrame* dest);
|
||||
|
||||
// Provides common functionality for performing audioprocessing simulations.
|
||||
class AudioProcessingSimulator {
|
||||
public:
|
||||
static const int kChunksPerSecond = 1000 / AudioProcessing::kChunkSizeMs;
|
||||
|
||||
explicit AudioProcessingSimulator(const SimulationSettings& settings)
|
||||
: settings_(settings) {}
|
||||
virtual ~AudioProcessingSimulator() {}
|
||||
|
||||
// Processes the data in the input.
|
||||
virtual void Process() = 0;
|
||||
|
||||
// Returns the execution time of all AudioProcessing calls.
|
||||
const TickIntervalStats& proc_time() const { return proc_time_; }
|
||||
|
||||
// Reports whether the processed recording was bitexact.
|
||||
bool OutputWasBitexact() { return bitexact_output_; }
|
||||
|
||||
size_t get_num_process_stream_calls() { return num_process_stream_calls_; }
|
||||
size_t get_num_reverse_process_stream_calls() {
|
||||
return num_reverse_process_stream_calls_;
|
||||
}
|
||||
|
||||
protected:
|
||||
// RAII class for execution time measurement. Updates the provided
|
||||
// TickIntervalStats based on the time between ScopedTimer creation and
|
||||
// leaving the enclosing scope.
|
||||
class ScopedTimer {
|
||||
public:
|
||||
explicit ScopedTimer(TickIntervalStats* proc_time)
|
||||
: proc_time_(proc_time), start_time_(rtc::TimeNanos()) {}
|
||||
|
||||
~ScopedTimer();
|
||||
|
||||
private:
|
||||
TickIntervalStats* const proc_time_;
|
||||
int64_t start_time_;
|
||||
};
|
||||
|
||||
TickIntervalStats* mutable_proc_time() { return &proc_time_; }
|
||||
void ProcessStream(bool fixed_interface);
|
||||
void ProcessReverseStream(bool fixed_interface);
|
||||
void CreateAudioProcessor();
|
||||
void DestroyAudioProcessor();
|
||||
void SetupBuffersConfigsOutputs(int input_sample_rate_hz,
|
||||
int output_sample_rate_hz,
|
||||
int reverse_input_sample_rate_hz,
|
||||
int reverse_output_sample_rate_hz,
|
||||
int input_num_channels,
|
||||
int output_num_channels,
|
||||
int reverse_input_num_channels,
|
||||
int reverse_output_num_channels);
|
||||
|
||||
const SimulationSettings settings_;
|
||||
std::unique_ptr<AudioProcessing> ap_;
|
||||
|
||||
std::unique_ptr<ChannelBuffer<float>> in_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> out_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_in_buf_;
|
||||
std::unique_ptr<ChannelBuffer<float>> reverse_out_buf_;
|
||||
StreamConfig in_config_;
|
||||
StreamConfig out_config_;
|
||||
StreamConfig reverse_in_config_;
|
||||
StreamConfig reverse_out_config_;
|
||||
std::unique_ptr<ChannelBufferWavReader> buffer_reader_;
|
||||
std::unique_ptr<ChannelBufferWavReader> reverse_buffer_reader_;
|
||||
AudioFrame rev_frame_;
|
||||
AudioFrame fwd_frame_;
|
||||
bool bitexact_output_ = true;
|
||||
|
||||
private:
|
||||
void SetupOutput();
|
||||
|
||||
size_t num_process_stream_calls_ = 0;
|
||||
size_t num_reverse_process_stream_calls_ = 0;
|
||||
size_t output_reset_counter_ = 0;
|
||||
std::unique_ptr<ChannelBufferWavWriter> buffer_writer_;
|
||||
std::unique_ptr<ChannelBufferWavWriter> reverse_buffer_writer_;
|
||||
TickIntervalStats proc_time_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioProcessingSimulator);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_PROCESSING_SIMULATOR_H_
|
||||
@ -8,179 +8,425 @@
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/base/format_macros.h"
|
||||
#include "webrtc/common_audio/channel_buffer.h"
|
||||
#include "webrtc/common_audio/wav_file.h"
|
||||
#include "webrtc/modules/audio_processing/include/audio_processing.h"
|
||||
#include "webrtc/modules/audio_processing/test/audio_file_processor.h"
|
||||
#include "webrtc/modules/audio_processing/test/protobuf_utils.h"
|
||||
#include "webrtc/modules/audio_processing/test/test_utils.h"
|
||||
#include "webrtc/test/testsupport/trace_to_stderr.h"
|
||||
#include "webrtc/modules/audio_processing/test/aec_dump_based_simulator.h"
|
||||
#include "webrtc/modules/audio_processing/test/audio_processing_simulator.h"
|
||||
#include "webrtc/modules/audio_processing/test/wav_based_simulator.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
bool ValidateOutChannels(const char* flagname, int32_t value) {
|
||||
return value >= 0;
|
||||
const int kParameterNotSpecifiedValue = -10000;
|
||||
|
||||
const char kUsageDescription[] =
|
||||
"Usage: audioproc_f [options] -i <input.wav>\n"
|
||||
" or\n"
|
||||
" audioproc_f [options] -dump_input <aec_dump>\n"
|
||||
"\n\n"
|
||||
"Command-line tool to simulate a call using the audio "
|
||||
"processing module, either based on wav files or "
|
||||
"protobuf debug dump recordings.";
|
||||
|
||||
DEFINE_string(dump_input, "", "Aec dump input filename");
|
||||
DEFINE_string(dump_output, "", "Aec dump output filename");
|
||||
DEFINE_string(i, "", "Forward stream input wav filename");
|
||||
DEFINE_string(o, "", "Forward stream output wav filename");
|
||||
DEFINE_string(ri, "", "Reverse stream input wav filename");
|
||||
DEFINE_string(ro, "", "Reverse stream output wav filename");
|
||||
DEFINE_int32(output_num_channels,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Number of forward stream output channels");
|
||||
DEFINE_int32(reverse_output_num_channels,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Number of Reverse stream output channels");
|
||||
DEFINE_int32(output_sample_rate_hz,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Forward stream output sample rate in Hz");
|
||||
DEFINE_int32(reverse_output_sample_rate_hz,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Reverse stream output sample rate in Hz");
|
||||
DEFINE_string(mic_positions,
|
||||
"",
|
||||
"Space delimited cartesian coordinates of microphones in "
|
||||
"meters. The coordinates of each point are contiguous. For a "
|
||||
"two element array: \"x1 y1 z1 x2 y2 z2\"");
|
||||
DEFINE_int32(target_angle_degrees,
|
||||
90,
|
||||
"The azimuth of the target in degrees (0-359). Only applies to "
|
||||
"beamforming.");
|
||||
DEFINE_bool(fixed_interface,
|
||||
false,
|
||||
"Use the fixed interface when operating on wav files");
|
||||
DEFINE_int32(aec,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the echo canceller");
|
||||
DEFINE_int32(aecm,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the mobile echo controller");
|
||||
DEFINE_int32(agc,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the AGC");
|
||||
DEFINE_int32(hpf,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the high-pass filter");
|
||||
DEFINE_int32(ns,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the noise suppressor");
|
||||
DEFINE_int32(ts,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the transient suppressor");
|
||||
DEFINE_int32(bf,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the beamformer");
|
||||
DEFINE_int32(ie,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the intelligibility enhancer");
|
||||
DEFINE_int32(vad,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the voice activity detector");
|
||||
DEFINE_int32(le,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the level estimator");
|
||||
DEFINE_bool(all_default,
|
||||
false,
|
||||
"Activate all of the default components (will be overridden by any "
|
||||
"other settings)");
|
||||
DEFINE_int32(aec_suppression_level,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Set the aec suppression level (0-2)");
|
||||
DEFINE_int32(delay_agnostic,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the AEC delay agnostic mode");
|
||||
DEFINE_int32(extended_filter,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the AEC extended filter mode");
|
||||
DEFINE_int32(drift_compensation,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the drift compensation");
|
||||
DEFINE_int32(aec3,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the experimental AEC mode AEC3");
|
||||
DEFINE_int32(
|
||||
refined_adaptive_filter,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the refined adaptive filter functionality");
|
||||
DEFINE_int32(aecm_routing_mode,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the AECM routing mode (0-4)");
|
||||
DEFINE_int32(aecm_comfort_noise,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the AECM comfort noise");
|
||||
DEFINE_int32(agc_mode,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the AGC mode (0-2)");
|
||||
DEFINE_int32(agc_target_level,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the AGC target level (0-31)");
|
||||
DEFINE_int32(agc_limiter,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Activate (1) or deactivate(0) the level estimator");
|
||||
DEFINE_int32(agc_compression_gain,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the AGC compression gain (0-90)");
|
||||
DEFINE_int32(vad_likelihood,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the VAD likelihood (0-3)");
|
||||
DEFINE_int32(ns_level,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the NS level (0-3)");
|
||||
DEFINE_int32(stream_delay,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the stream delay in ms to use");
|
||||
DEFINE_int32(stream_drift_samples,
|
||||
kParameterNotSpecifiedValue,
|
||||
"Specify the number of stream drift samples to use");
|
||||
DEFINE_bool(performance_report, false, "Report the APM performance ");
|
||||
DEFINE_bool(verbose, false, "Produce verbose output");
|
||||
DEFINE_bool(bitexactness_report,
|
||||
false,
|
||||
"Report bitexactness for aec dump result reproduction");
|
||||
DEFINE_bool(discard_settings_in_aecdump,
|
||||
false,
|
||||
"Discard any config settings specified in the aec dump");
|
||||
DEFINE_bool(store_intermediate_output,
|
||||
false,
|
||||
"Creates new output files after each init");
|
||||
|
||||
void SetSettingIfSpecified(const std::string value,
|
||||
rtc::Optional<std::string>* parameter) {
|
||||
if (value.compare("") != 0) {
|
||||
*parameter = rtc::Optional<std::string>(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSettingIfSpecified(int value, rtc::Optional<int>* parameter) {
|
||||
if (value != kParameterNotSpecifiedValue) {
|
||||
*parameter = rtc::Optional<int>(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSettingIfFlagSet(int32_t flag, rtc::Optional<bool>* parameter) {
|
||||
if (flag == 0) {
|
||||
*parameter = rtc::Optional<bool>(false);
|
||||
} else if (flag == 1) {
|
||||
*parameter = rtc::Optional<bool>(true);
|
||||
}
|
||||
}
|
||||
|
||||
SimulationSettings CreateSettings() {
|
||||
SimulationSettings settings;
|
||||
if (FLAGS_all_default) {
|
||||
settings.use_le = rtc::Optional<bool>(true);
|
||||
settings.use_vad = rtc::Optional<bool>(true);
|
||||
settings.use_ie = rtc::Optional<bool>(false);
|
||||
settings.use_bf = rtc::Optional<bool>(false);
|
||||
settings.use_ts = rtc::Optional<bool>(true);
|
||||
settings.use_ns = rtc::Optional<bool>(true);
|
||||
settings.use_hpf = rtc::Optional<bool>(true);
|
||||
settings.use_agc = rtc::Optional<bool>(true);
|
||||
settings.use_aec = rtc::Optional<bool>(true);
|
||||
settings.use_aecm = rtc::Optional<bool>(false);
|
||||
}
|
||||
SetSettingIfSpecified(FLAGS_dump_input, &settings.aec_dump_input_filename);
|
||||
SetSettingIfSpecified(FLAGS_dump_output, &settings.aec_dump_output_filename);
|
||||
SetSettingIfSpecified(FLAGS_i, &settings.input_filename);
|
||||
SetSettingIfSpecified(FLAGS_o, &settings.output_filename);
|
||||
SetSettingIfSpecified(FLAGS_ri, &settings.reverse_input_filename);
|
||||
SetSettingIfSpecified(FLAGS_ro, &settings.reverse_output_filename);
|
||||
SetSettingIfSpecified(FLAGS_output_num_channels,
|
||||
&settings.output_num_channels);
|
||||
SetSettingIfSpecified(FLAGS_reverse_output_num_channels,
|
||||
&settings.reverse_output_num_channels);
|
||||
SetSettingIfSpecified(FLAGS_output_sample_rate_hz,
|
||||
&settings.output_sample_rate_hz);
|
||||
SetSettingIfSpecified(FLAGS_reverse_output_sample_rate_hz,
|
||||
&settings.reverse_output_sample_rate_hz);
|
||||
SetSettingIfSpecified(FLAGS_mic_positions, &settings.microphone_positions);
|
||||
settings.target_angle_degrees = FLAGS_target_angle_degrees;
|
||||
SetSettingIfFlagSet(FLAGS_aec, &settings.use_aec);
|
||||
SetSettingIfFlagSet(FLAGS_aecm, &settings.use_aecm);
|
||||
SetSettingIfFlagSet(FLAGS_agc, &settings.use_agc);
|
||||
SetSettingIfFlagSet(FLAGS_hpf, &settings.use_hpf);
|
||||
SetSettingIfFlagSet(FLAGS_ns, &settings.use_ns);
|
||||
SetSettingIfFlagSet(FLAGS_ts, &settings.use_ts);
|
||||
SetSettingIfFlagSet(FLAGS_bf, &settings.use_bf);
|
||||
SetSettingIfFlagSet(FLAGS_ie, &settings.use_ie);
|
||||
SetSettingIfFlagSet(FLAGS_vad, &settings.use_vad);
|
||||
SetSettingIfFlagSet(FLAGS_le, &settings.use_le);
|
||||
SetSettingIfSpecified(FLAGS_aec_suppression_level,
|
||||
&settings.aec_suppression_level);
|
||||
SetSettingIfFlagSet(FLAGS_delay_agnostic, &settings.use_delay_agnostic);
|
||||
SetSettingIfFlagSet(FLAGS_extended_filter, &settings.use_extended_filter);
|
||||
SetSettingIfFlagSet(FLAGS_drift_compensation,
|
||||
&settings.use_drift_compensation);
|
||||
SetSettingIfFlagSet(FLAGS_refined_adaptive_filter,
|
||||
&settings.use_refined_adaptive_filter);
|
||||
|
||||
SetSettingIfFlagSet(FLAGS_aec3, &settings.use_aec3);
|
||||
SetSettingIfSpecified(FLAGS_aecm_routing_mode, &settings.aecm_routing_mode);
|
||||
SetSettingIfFlagSet(FLAGS_aecm_comfort_noise,
|
||||
&settings.use_aecm_comfort_noise);
|
||||
SetSettingIfSpecified(FLAGS_agc_mode, &settings.agc_mode);
|
||||
SetSettingIfSpecified(FLAGS_agc_target_level, &settings.agc_target_level);
|
||||
SetSettingIfFlagSet(FLAGS_agc_limiter, &settings.use_agc_limiter);
|
||||
SetSettingIfSpecified(FLAGS_agc_compression_gain,
|
||||
&settings.agc_compression_gain);
|
||||
SetSettingIfSpecified(FLAGS_vad_likelihood, &settings.vad_likelihood);
|
||||
SetSettingIfSpecified(FLAGS_ns_level, &settings.ns_level);
|
||||
SetSettingIfSpecified(FLAGS_stream_delay, &settings.stream_delay);
|
||||
SetSettingIfSpecified(FLAGS_stream_drift_samples,
|
||||
&settings.stream_drift_samples);
|
||||
settings.report_performance = FLAGS_performance_report;
|
||||
settings.use_verbose_logging = FLAGS_verbose;
|
||||
settings.report_bitexactness = FLAGS_bitexactness_report;
|
||||
settings.discard_all_settings_in_aecdump = FLAGS_discard_settings_in_aecdump;
|
||||
settings.fixed_interface = FLAGS_fixed_interface;
|
||||
settings.store_intermediate_output = FLAGS_store_intermediate_output;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
void ReportConditionalErrorAndExit(bool condition, std::string message) {
|
||||
if (condition) {
|
||||
std::cerr << message << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void PerformBasicParameterSanityChecks(const SimulationSettings& settings) {
|
||||
if (settings.input_filename || settings.reverse_input_filename) {
|
||||
ReportConditionalErrorAndExit(!!settings.aec_dump_input_filename,
|
||||
"Error: The aec dump cannot be specified "
|
||||
"together with input wav files!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(!settings.input_filename,
|
||||
"Error: When operating at wav files, the "
|
||||
"input wav filename must be "
|
||||
"specified!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.reverse_output_filename && !settings.reverse_input_filename,
|
||||
"Error: When operating at wav files, the reverse input wav filename "
|
||||
"must be specified if the reverse output wav filename is specified!\n");
|
||||
} else {
|
||||
ReportConditionalErrorAndExit(!settings.aec_dump_input_filename,
|
||||
"Error: Either the aec dump or the wav "
|
||||
"input files must be specified!\n");
|
||||
}
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.use_aec && *settings.use_aec && settings.use_aecm &&
|
||||
*settings.use_aecm,
|
||||
"Error: The AEC and the AECM cannot be activated at the same time!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.output_sample_rate_hz && *settings.output_sample_rate_hz <= 0,
|
||||
"Error: --output_sample_rate_hz must be positive!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.reverse_output_sample_rate_hz &&
|
||||
settings.output_sample_rate_hz &&
|
||||
*settings.output_sample_rate_hz <= 0,
|
||||
"Error: --reverse_output_sample_rate_hz must be positive!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.output_num_channels && *settings.output_num_channels <= 0,
|
||||
"Error: --output_num_channels must be positive!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.reverse_output_num_channels &&
|
||||
*settings.reverse_output_num_channels <= 0,
|
||||
"Error: --reverse_output_num_channels must be positive!\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.use_bf && *settings.use_bf && !settings.microphone_positions,
|
||||
"Error: --mic_positions must be specified when the beamformer is "
|
||||
"activated.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.target_angle_degrees < 0 || settings.target_angle_degrees > 359,
|
||||
"Error: -target_angle_degrees must be specified between 0 and 359.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.aec_suppression_level &&
|
||||
((*settings.aec_suppression_level) < 0 ||
|
||||
(*settings.aec_suppression_level) > 2),
|
||||
"Error: --aec_suppression_level must be specified between 0 and 2.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.aecm_routing_mode && ((*settings.aecm_routing_mode) < 0 ||
|
||||
(*settings.aecm_routing_mode) > 4),
|
||||
"Error: --aecm_routing_mode must be specified between 0 and 4.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.agc_target_level && ((*settings.agc_target_level) < 0 ||
|
||||
(*settings.agc_target_level) > 31),
|
||||
"Error: --agc_target_level must be specified between 0 and 31.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.agc_compression_gain && ((*settings.agc_compression_gain) < 0 ||
|
||||
(*settings.agc_compression_gain) > 90),
|
||||
"Error: --agc_compression_gain must be specified between 0 and 90.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.vad_likelihood &&
|
||||
((*settings.vad_likelihood) < 0 || (*settings.vad_likelihood) > 3),
|
||||
"Error: --vad_likelihood must be specified between 0 and 3.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.ns_level &&
|
||||
((*settings.ns_level) < 0 || (*settings.ns_level) > 3),
|
||||
"Error: --ns_level must be specified between 0 and 3.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.report_bitexactness && !settings.aec_dump_input_filename,
|
||||
"Error: --bitexactness_report can only be used when operating on an "
|
||||
"aecdump\n");
|
||||
|
||||
auto valid_wav_name = [](const std::string& wav_file_name) {
|
||||
if (wav_file_name.size() < 5) {
|
||||
return false;
|
||||
}
|
||||
if ((wav_file_name.compare(wav_file_name.size() - 4, 4, ".wav") == 0) ||
|
||||
(wav_file_name.compare(wav_file_name.size() - 4, 4, ".WAV") == 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.input_filename && (!valid_wav_name(*settings.input_filename)),
|
||||
"Error: --i must be a valid .wav file name.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.output_filename && (!valid_wav_name(*settings.output_filename)),
|
||||
"Error: --o must be a valid .wav file name.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.reverse_input_filename &&
|
||||
(!valid_wav_name(*settings.reverse_input_filename)),
|
||||
"Error: --ri must be a valid .wav file name.\n");
|
||||
|
||||
ReportConditionalErrorAndExit(
|
||||
settings.reverse_output_filename &&
|
||||
(!valid_wav_name(*settings.reverse_output_filename)),
|
||||
"Error: --ro must be a valid .wav file name.\n");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DEFINE_string(dump, "", "Name of the aecdump debug file to read from.");
|
||||
DEFINE_string(i, "", "Name of the capture input stream file to read from.");
|
||||
DEFINE_string(
|
||||
o,
|
||||
"out.wav",
|
||||
"Name of the output file to write the processed capture stream to.");
|
||||
DEFINE_string(ri, "", "Name of the render input stream file to read from.");
|
||||
DEFINE_string(
|
||||
ro,
|
||||
"out_reverse.wav",
|
||||
"Name of the output file to write the processed render stream to.");
|
||||
DEFINE_int32(out_channels, 1, "Number of output channels.");
|
||||
const bool out_channels_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_out_channels, &ValidateOutChannels);
|
||||
DEFINE_int32(rev_out_channels, 1, "Number of reverse output channels.");
|
||||
const bool rev_out_channels_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_rev_out_channels,
|
||||
&ValidateOutChannels);
|
||||
DEFINE_int32(out_sample_rate, 48000, "Output sample rate in Hz.");
|
||||
DEFINE_int32(rev_out_sample_rate, 48000, "Reverse output sample rate in Hz.");
|
||||
DEFINE_string(mic_positions, "",
|
||||
"Space delimited cartesian coordinates of microphones in meters. "
|
||||
"The coordinates of each point are contiguous. "
|
||||
"For a two element array: \"x1 y1 z1 x2 y2 z2\"");
|
||||
DEFINE_double(
|
||||
target_angle_degrees,
|
||||
90,
|
||||
"The azimuth of the target in degrees. Only applies to beamforming.");
|
||||
|
||||
DEFINE_bool(aec, false, "Enable echo cancellation.");
|
||||
DEFINE_bool(agc, false, "Enable automatic gain control.");
|
||||
DEFINE_bool(hpf, false, "Enable high-pass filtering.");
|
||||
DEFINE_bool(ns, false, "Enable noise suppression.");
|
||||
DEFINE_bool(ts, false, "Enable transient suppression.");
|
||||
DEFINE_bool(bf, false, "Enable beamforming.");
|
||||
DEFINE_bool(ie, false, "Enable intelligibility enhancer.");
|
||||
DEFINE_bool(all, false, "Enable all components.");
|
||||
|
||||
DEFINE_int32(ns_level, -1, "Noise suppression level [0 - 3].");
|
||||
|
||||
DEFINE_bool(perf, false, "Enable performance tests.");
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
const int kChunksPerSecond = 100;
|
||||
const char kUsage[] =
|
||||
"Command-line tool to run audio processing on WAV files. Accepts either\n"
|
||||
"an input capture WAV file or protobuf debug dump and writes to an output\n"
|
||||
"WAV file.\n"
|
||||
"\n"
|
||||
"All components are disabled by default.";
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
google::SetUsageMessage(kUsage);
|
||||
google::SetUsageMessage(kUsageDescription);
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (!((FLAGS_i.empty()) ^ (FLAGS_dump.empty()))) {
|
||||
fprintf(stderr,
|
||||
"An input file must be specified with either -i or -dump.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
test::TraceToStderr trace_to_stderr(true);
|
||||
Config config;
|
||||
if (FLAGS_bf || FLAGS_all) {
|
||||
if (FLAGS_mic_positions.empty()) {
|
||||
fprintf(stderr, "-mic_positions must be specified when -bf is used.\n");
|
||||
return 1;
|
||||
}
|
||||
config.Set<Beamforming>(new Beamforming(
|
||||
true, ParseArrayGeometry(FLAGS_mic_positions),
|
||||
SphericalPointf(DegreesToRadians(FLAGS_target_angle_degrees), 0.f,
|
||||
1.f)));
|
||||
}
|
||||
config.Set<ExperimentalNs>(new ExperimentalNs(FLAGS_ts || FLAGS_all));
|
||||
config.Set<Intelligibility>(new Intelligibility(FLAGS_ie || FLAGS_all));
|
||||
|
||||
std::unique_ptr<AudioProcessing> ap(AudioProcessing::Create(config));
|
||||
RTC_CHECK_EQ(kNoErr, ap->echo_cancellation()->Enable(FLAGS_aec || FLAGS_all));
|
||||
RTC_CHECK_EQ(kNoErr, ap->gain_control()->Enable(FLAGS_agc || FLAGS_all));
|
||||
RTC_CHECK_EQ(kNoErr, ap->high_pass_filter()->Enable(FLAGS_hpf || FLAGS_all));
|
||||
RTC_CHECK_EQ(kNoErr, ap->noise_suppression()->Enable(FLAGS_ns || FLAGS_all));
|
||||
if (FLAGS_ns_level != -1) {
|
||||
RTC_CHECK_EQ(kNoErr,
|
||||
ap->noise_suppression()->set_level(
|
||||
static_cast<NoiseSuppression::Level>(FLAGS_ns_level)));
|
||||
}
|
||||
ap->set_stream_key_pressed(FLAGS_ts);
|
||||
|
||||
std::unique_ptr<AudioFileProcessor> processor;
|
||||
auto out_file = std::unique_ptr<WavWriter>(new WavWriter(
|
||||
FLAGS_o, FLAGS_out_sample_rate, static_cast<size_t>(FLAGS_out_channels)));
|
||||
std::cout << FLAGS_o << ": " << out_file->FormatAsString() << std::endl;
|
||||
if (FLAGS_dump.empty()) {
|
||||
auto in_file = std::unique_ptr<WavReader>(new WavReader(FLAGS_i));
|
||||
std::cout << FLAGS_i << ": " << in_file->FormatAsString() << std::endl;
|
||||
std::unique_ptr<WavReader> reverse_in_file;
|
||||
std::unique_ptr<WavWriter> reverse_out_file;
|
||||
if (!FLAGS_ri.empty()) {
|
||||
reverse_in_file.reset(new WavReader(FLAGS_ri));
|
||||
reverse_out_file.reset(new WavWriter(
|
||||
FLAGS_ro,
|
||||
FLAGS_rev_out_sample_rate,
|
||||
static_cast<size_t>(FLAGS_rev_out_channels)));
|
||||
std::cout << FLAGS_ri << ": "
|
||||
<< reverse_in_file->FormatAsString() << std::endl;
|
||||
std::cout << FLAGS_ro << ": "
|
||||
<< reverse_out_file->FormatAsString() << std::endl;
|
||||
}
|
||||
processor.reset(new WavFileProcessor(std::move(ap),
|
||||
std::move(in_file),
|
||||
std::move(out_file),
|
||||
std::move(reverse_in_file),
|
||||
std::move(reverse_out_file)));
|
||||
SimulationSettings settings = CreateSettings();
|
||||
PerformBasicParameterSanityChecks(settings);
|
||||
std::unique_ptr<AudioProcessingSimulator> processor;
|
||||
|
||||
if (settings.aec_dump_input_filename) {
|
||||
processor.reset(new AecDumpBasedSimulator(settings));
|
||||
} else {
|
||||
processor.reset(new AecDumpFileProcessor(
|
||||
std::move(ap), fopen(FLAGS_dump.c_str(), "rb"), std::move(out_file)));
|
||||
processor.reset(new WavBasedSimulator(settings));
|
||||
}
|
||||
|
||||
int num_chunks = 0;
|
||||
while (processor->ProcessChunk()) {
|
||||
trace_to_stderr.SetTimeSeconds(num_chunks * 1.f / kChunksPerSecond);
|
||||
++num_chunks;
|
||||
}
|
||||
processor->Process();
|
||||
|
||||
if (FLAGS_perf) {
|
||||
if (settings.report_performance) {
|
||||
const auto& proc_time = processor->proc_time();
|
||||
int64_t exec_time_us = proc_time.sum / rtc::kNumNanosecsPerMicrosec;
|
||||
printf(
|
||||
"\nExecution time: %.3f s, File time: %.2f s\n"
|
||||
"Time per chunk (mean, max, min):\n%.0f us, %.0f us, %.0f us\n",
|
||||
exec_time_us * 1e-6, num_chunks * 1.f / kChunksPerSecond,
|
||||
exec_time_us * 1.f / num_chunks,
|
||||
1.f * proc_time.max / rtc::kNumNanosecsPerMicrosec,
|
||||
1.f * proc_time.min / rtc::kNumNanosecsPerMicrosec);
|
||||
std::cout << std::endl
|
||||
<< "Execution time: " << exec_time_us * 1e-6 << " s, File time: "
|
||||
<< processor->get_num_process_stream_calls() * 1.f /
|
||||
AudioProcessingSimulator::kChunksPerSecond
|
||||
<< std::endl
|
||||
<< "Time per fwd stream chunk (mean, max, min): " << std::endl
|
||||
<< exec_time_us * 1.f / processor->get_num_process_stream_calls()
|
||||
<< " us, " << 1.f * proc_time.max / rtc::kNumNanosecsPerMicrosec
|
||||
<< " us, " << 1.f * proc_time.min / rtc::kNumNanosecsPerMicrosec
|
||||
<< " us" << std::endl;
|
||||
}
|
||||
|
||||
if (settings.report_bitexactness && settings.aec_dump_input_filename) {
|
||||
if (processor->OutputWasBitexact()) {
|
||||
std::cout << "The processing was bitexact.";
|
||||
} else {
|
||||
std::cout << "The processing was not bitexact.";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return webrtc::main(argc, argv);
|
||||
return webrtc::test::main(argc, argv);
|
||||
}
|
||||
|
||||
@ -709,9 +709,6 @@ void void_main(int argc, char* argv[]) {
|
||||
const Stream msg = event_msg.stream();
|
||||
primary_count++;
|
||||
|
||||
// ProcessStream could have changed this for the output frame.
|
||||
near_frame.num_channels_ = apm->num_input_channels();
|
||||
|
||||
ASSERT_TRUE(msg.has_input_data() ^ (msg.input_channel_size() > 0));
|
||||
if (msg.has_input_data()) {
|
||||
ASSERT_EQ(sizeof(int16_t) * samples_per_channel *
|
||||
|
||||
156
webrtc/modules/audio_processing/test/wav_based_simulator.cc
Normal file
156
webrtc/modules/audio_processing/test/wav_based_simulator.cc
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/audio_processing/test/wav_based_simulator.h"
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/test/testsupport/trace_to_stderr.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
std::vector<WavBasedSimulator::SimulationEventType>
|
||||
WavBasedSimulator::GetDefaultEventChain() const {
|
||||
std::vector<WavBasedSimulator::SimulationEventType> call_chain(2);
|
||||
call_chain[0] = SimulationEventType::kProcessStream;
|
||||
call_chain[1] = SimulationEventType::kProcessReverseStream;
|
||||
return call_chain;
|
||||
}
|
||||
|
||||
void WavBasedSimulator::PrepareProcessStreamCall() {
|
||||
if (settings_.fixed_interface) {
|
||||
CopyToAudioFrame(*in_buf_, &fwd_frame_);
|
||||
}
|
||||
ap_->set_stream_key_pressed(settings_.use_ts && (*settings_.use_ts));
|
||||
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->set_stream_delay_ms(
|
||||
settings_.stream_delay ? *settings_.stream_delay : 0));
|
||||
|
||||
ap_->echo_cancellation()->set_stream_drift_samples(
|
||||
settings_.stream_drift_samples ? *settings_.stream_drift_samples : 0);
|
||||
|
||||
RTC_CHECK_EQ(AudioProcessing::kNoError,
|
||||
ap_->gain_control()->set_stream_analog_level(
|
||||
last_specified_microphone_level_));
|
||||
}
|
||||
|
||||
void WavBasedSimulator::PrepareReverseProcessStreamCall() {
|
||||
if (settings_.fixed_interface) {
|
||||
CopyToAudioFrame(*reverse_in_buf_, &rev_frame_);
|
||||
}
|
||||
}
|
||||
|
||||
void WavBasedSimulator::Process() {
|
||||
std::unique_ptr<test::TraceToStderr> trace_to_stderr;
|
||||
if (settings_.use_verbose_logging) {
|
||||
trace_to_stderr.reset(new test::TraceToStderr(true));
|
||||
}
|
||||
|
||||
call_chain_ = GetDefaultEventChain();
|
||||
CreateAudioProcessor();
|
||||
|
||||
Initialize();
|
||||
|
||||
bool samples_left_to_process = true;
|
||||
int call_chain_index = 0;
|
||||
int num_forward_chunks_processed = 0;
|
||||
const int kOneBykChunksPerSecond =
|
||||
1.f / AudioProcessingSimulator::kChunksPerSecond;
|
||||
while (samples_left_to_process) {
|
||||
switch (call_chain_[call_chain_index]) {
|
||||
case SimulationEventType::kProcessStream:
|
||||
samples_left_to_process = HandleProcessStreamCall();
|
||||
++num_forward_chunks_processed;
|
||||
break;
|
||||
case SimulationEventType::kProcessReverseStream:
|
||||
if (settings_.reverse_input_filename) {
|
||||
samples_left_to_process = HandleProcessReverseStreamCall();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RTC_CHECK(false);
|
||||
}
|
||||
|
||||
call_chain_index = (call_chain_index + 1) % call_chain_.size();
|
||||
|
||||
if (trace_to_stderr) {
|
||||
trace_to_stderr->SetTimeSeconds(num_forward_chunks_processed *
|
||||
kOneBykChunksPerSecond);
|
||||
}
|
||||
}
|
||||
|
||||
DestroyAudioProcessor();
|
||||
}
|
||||
|
||||
bool WavBasedSimulator::HandleProcessStreamCall() {
|
||||
bool samples_left_to_process = buffer_reader_->Read(in_buf_.get());
|
||||
if (samples_left_to_process) {
|
||||
PrepareProcessStreamCall();
|
||||
ProcessStream(settings_.fixed_interface);
|
||||
last_specified_microphone_level_ =
|
||||
ap_->gain_control()->stream_analog_level();
|
||||
}
|
||||
return samples_left_to_process;
|
||||
}
|
||||
|
||||
bool WavBasedSimulator::HandleProcessReverseStreamCall() {
|
||||
bool samples_left_to_process =
|
||||
reverse_buffer_reader_->Read(reverse_in_buf_.get());
|
||||
if (samples_left_to_process) {
|
||||
PrepareReverseProcessStreamCall();
|
||||
ProcessReverseStream(settings_.fixed_interface);
|
||||
}
|
||||
return samples_left_to_process;
|
||||
}
|
||||
|
||||
void WavBasedSimulator::Initialize() {
|
||||
std::unique_ptr<WavReader> in_file(
|
||||
new WavReader(settings_.input_filename->c_str()));
|
||||
int input_sample_rate_hz = in_file->sample_rate();
|
||||
int input_num_channels = in_file->num_channels();
|
||||
buffer_reader_.reset(new ChannelBufferWavReader(std::move(in_file)));
|
||||
|
||||
int output_sample_rate_hz = settings_.output_sample_rate_hz
|
||||
? *settings_.output_sample_rate_hz
|
||||
: input_sample_rate_hz;
|
||||
int output_num_channels = settings_.output_num_channels
|
||||
? *settings_.output_num_channels
|
||||
: input_num_channels;
|
||||
|
||||
int reverse_sample_rate_hz = 48000;
|
||||
int reverse_num_channels = 1;
|
||||
int reverse_output_sample_rate_hz = 48000;
|
||||
int reverse_output_num_channels = 1;
|
||||
if (settings_.reverse_input_filename) {
|
||||
std::unique_ptr<WavReader> reverse_in_file(
|
||||
new WavReader(settings_.reverse_input_filename->c_str()));
|
||||
reverse_sample_rate_hz = reverse_in_file->sample_rate();
|
||||
reverse_num_channels = reverse_in_file->num_channels();
|
||||
reverse_buffer_reader_.reset(
|
||||
new ChannelBufferWavReader(std::move(reverse_in_file)));
|
||||
|
||||
reverse_output_sample_rate_hz =
|
||||
settings_.reverse_output_sample_rate_hz
|
||||
? *settings_.reverse_output_sample_rate_hz
|
||||
: reverse_sample_rate_hz;
|
||||
reverse_output_num_channels = settings_.reverse_output_num_channels
|
||||
? *settings_.reverse_output_num_channels
|
||||
: reverse_num_channels;
|
||||
}
|
||||
|
||||
SetupBuffersConfigsOutputs(
|
||||
input_sample_rate_hz, output_sample_rate_hz, reverse_sample_rate_hz,
|
||||
reverse_output_sample_rate_hz, input_num_channels, output_num_channels,
|
||||
reverse_num_channels, reverse_output_num_channels);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
55
webrtc/modules/audio_processing/test/wav_based_simulator.h
Normal file
55
webrtc/modules/audio_processing/test/wav_based_simulator.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_TEST_WAV_BASED_SIMULATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_TEST_WAV_BASED_SIMULATOR_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/test/audio_processing_simulator.h"
|
||||
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
// Used to perform an audio processing simulation from wav files.
|
||||
class WavBasedSimulator final : public AudioProcessingSimulator {
|
||||
public:
|
||||
explicit WavBasedSimulator(const SimulationSettings& settings)
|
||||
: AudioProcessingSimulator(settings) {}
|
||||
virtual ~WavBasedSimulator() {}
|
||||
|
||||
// Processes the WAV input.
|
||||
void Process() override;
|
||||
|
||||
private:
|
||||
enum SimulationEventType {
|
||||
kProcessStream,
|
||||
kProcessReverseStream,
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
bool HandleProcessStreamCall();
|
||||
bool HandleProcessReverseStreamCall();
|
||||
void PrepareProcessStreamCall();
|
||||
void PrepareReverseProcessStreamCall();
|
||||
std::vector<SimulationEventType> GetDefaultEventChain() const;
|
||||
|
||||
std::vector<SimulationEventType> call_chain_;
|
||||
int last_specified_microphone_level_ = 100;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WavBasedSimulator);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_WAV_BASED_SIMULATOR_H_
|
||||
Loading…
x
Reference in New Issue
Block a user