From 60a189f1bfa5f822cb9e384b1fea514a66fa3a68 Mon Sep 17 00:00:00 2001 From: peah Date: Tue, 24 May 2016 20:54:40 -0700 Subject: [PATCH] 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} --- .../audio_processing_tests.gypi | 8 +- .../test/aec_dump_based_simulator.cc | 506 +++++++++++++++++ .../test/aec_dump_based_simulator.h | 62 ++ .../test/aec_dump_processor.h | 98 ++++ .../test/audio_file_processor.cc | 220 ------- .../test/audio_file_processor.h | 147 ----- .../test/audio_processing_simulator.cc | 334 +++++++++++ .../test/audio_processing_simulator.h | 175 ++++++ .../audio_processing/test/audioproc_float.cc | 536 +++++++++++++----- .../audio_processing/test/process_test.cc | 3 - .../test/wav_based_simulator.cc | 156 +++++ .../test/wav_based_simulator.h | 55 ++ 12 files changed, 1783 insertions(+), 517 deletions(-) create mode 100644 webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc create mode 100644 webrtc/modules/audio_processing/test/aec_dump_based_simulator.h create mode 100644 webrtc/modules/audio_processing/test/aec_dump_processor.h delete mode 100644 webrtc/modules/audio_processing/test/audio_file_processor.cc delete mode 100644 webrtc/modules/audio_processing/test/audio_file_processor.h create mode 100644 webrtc/modules/audio_processing/test/audio_processing_simulator.cc create mode 100644 webrtc/modules/audio_processing/test/audio_processing_simulator.h create mode 100644 webrtc/modules/audio_processing/test/wav_based_simulator.cc create mode 100644 webrtc/modules/audio_processing/test/wav_based_simulator.h diff --git a/webrtc/modules/audio_processing/audio_processing_tests.gypi b/webrtc/modules/audio_processing/audio_processing_tests.gypi index ad82f4bdad..78e203869e 100644 --- a/webrtc/modules/audio_processing/audio_processing_tests.gypi +++ b/webrtc/modules/audio_processing/audio_processing_tests.gypi @@ -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', ], }, diff --git a/webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc b/webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc new file mode 100644 index 0000000000..bb666cdce8 --- /dev/null +++ b/webrtc/modules/audio_processing/test/aec_dump_based_simulator.cc @@ -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 + +#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& out_buf) { + if (static_cast(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(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(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 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(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(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(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( + 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(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( + 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(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(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( + new RefinedAdaptiveFilter(*settings_.use_refined_adaptive_filter)); + } + + if (settings_.use_aec3) { + config.Set(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 diff --git a/webrtc/modules/audio_processing/test/aec_dump_based_simulator.h b/webrtc/modules/audio_processing/test/aec_dump_based_simulator.h new file mode 100644 index 0000000000..7c9bebcd1b --- /dev/null +++ b/webrtc/modules/audio_processing/test/aec_dump_based_simulator.h @@ -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_ diff --git a/webrtc/modules/audio_processing/test/aec_dump_processor.h b/webrtc/modules/audio_processing/test/aec_dump_processor.h new file mode 100644 index 0000000000..12b5878aad --- /dev/null +++ b/webrtc/modules/audio_processing/test/aec_dump_processor.h @@ -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 +#include +#include +#include +#include + +#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 ap, + FILE* dump_file, + std::string out_filename, + std::string reverse_out_filename, + rtc::Optional out_sample_rate_hz, + rtc::Optional out_num_channels, + rtc::Optional reverse_out_sample_rate_hz, + rtc::Optional 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 ap_; + FILE* dump_file_; + std::string out_filename_; + std::string reverse_out_filename_; + rtc::Optional out_sample_rate_hz_; + rtc::Optional out_num_channels_; + rtc::Optional reverse_out_sample_rate_hz_; + rtc::Optional reverse_out_num_channels_; + bool override_config_message_; + + std::unique_ptr> in_buf_; + std::unique_ptr> reverse_buf_; + std::unique_ptr> out_buf_; + std::unique_ptr> reverse_out_buf_; + std::unique_ptr out_file_; + std::unique_ptr reverse_out_file_; + StreamConfig input_config_; + StreamConfig reverse_config_; + StreamConfig output_config_; + StreamConfig reverse_output_config_; + std::unique_ptr buffer_writer_; + std::unique_ptr 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_ diff --git a/webrtc/modules/audio_processing/test/audio_file_processor.cc b/webrtc/modules/audio_processing/test/audio_file_processor.cc deleted file mode 100644 index 5f57917337..0000000000 --- a/webrtc/modules/audio_processing/test/audio_file_processor.cc +++ /dev/null @@ -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 -#include - -#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 GetChannelBuffer(const WavFile& file) { - return ChannelBuffer( - CheckedDivExact(file.sample_rate(), AudioFileProcessor::kChunksPerSecond), - file.num_channels()); -} - -} // namespace - -WavFileProcessor::WavFileProcessor(std::unique_ptr ap, - std::unique_ptr in_file, - std::unique_ptr out_file, - std::unique_ptr reverse_in_file, - std::unique_ptr 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(GetChannelBuffer(*reverse_in_file))); - reverse_out_buf_.reset( - new ChannelBuffer(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 ap, - FILE* dump_file, - std::unique_ptr 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( - 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( - 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(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(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 diff --git a/webrtc/modules/audio_processing/test/audio_file_processor.h b/webrtc/modules/audio_processing/test/audio_file_processor.h deleted file mode 100644 index 76d5e0edb8..0000000000 --- a/webrtc/modules/audio_processing/test/audio_file_processor.h +++ /dev/null @@ -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 -#include -#include -#include - -#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::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 ap, - std::unique_ptr in_file, - std::unique_ptr out_file, - std::unique_ptr reverse_in_file, - std::unique_ptr 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 ap_; - - ChannelBuffer in_buf_; - ChannelBuffer out_buf_; - const StreamConfig input_config_; - const StreamConfig output_config_; - ChannelBufferWavReader buffer_reader_; - ChannelBufferWavWriter buffer_writer_; - std::unique_ptr> reverse_in_buf_; - std::unique_ptr> reverse_out_buf_; - std::unique_ptr reverse_input_config_; - std::unique_ptr reverse_output_config_; - std::unique_ptr reverse_buffer_reader_; - std::unique_ptr 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 ap, - FILE* dump_file, - std::unique_ptr 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 ap_; - FILE* dump_file_; - - std::unique_ptr> in_buf_; - std::unique_ptr> reverse_buf_; - ChannelBuffer 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_ diff --git a/webrtc/modules/audio_processing/test/audio_processing_simulator.cc b/webrtc/modules/audio_processing/test/audio_processing_simulator.cc new file mode 100644 index 0000000000..6cb614db32 --- /dev/null +++ b/webrtc/modules/audio_processing/test/audio_processing_simulator.cc @@ -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 +#include +#include +#include +#include + +#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* 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 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& 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( + 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( + 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( + 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( + 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 out_file( + new WavWriter(filename, out_config_.sample_rate_hz(), + static_cast(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 reverse_out_file( + new WavWriter(filename, reverse_out_config_.sample_rate_hz(), + static_cast(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(new Beamforming( + true, ParseArrayGeometry(*settings_.microphone_positions), + SphericalPointf(DegreesToRadians(settings_.target_angle_degrees), 0.f, + 1.f))); + } + if (settings_.use_ts) { + config.Set(new ExperimentalNs(*settings_.use_ts)); + } + if (settings_.use_ie) { + config.Set(new Intelligibility(*settings_.use_ie)); + } + if (settings_.use_aec3) { + config.Set(new EchoCanceller3(*settings_.use_aec3)); + } + if (settings_.use_refined_adaptive_filter) { + config.Set( + new RefinedAdaptiveFilter(*settings_.use_refined_adaptive_filter)); + } + config.Set(new ExtendedFilter( + !settings_.use_extended_filter || *settings_.use_extended_filter)); + config.Set(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(*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( + *settings_.aec_suppression_level))); + } + + if (settings_.aecm_routing_mode) { + RTC_CHECK_EQ(AudioProcessing::kNoError, + ap_->echo_control_mobile()->set_routing_mode( + static_cast( + *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( + *settings_.vad_likelihood))); + } + if (settings_.ns_level) { + RTC_CHECK_EQ( + AudioProcessing::kNoError, + ap_->noise_suppression()->set_level( + static_cast(*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 diff --git a/webrtc/modules/audio_processing/test/audio_processing_simulator.h b/webrtc/modules/audio_processing/test/audio_processing_simulator.h new file mode 100644 index 0000000000..f60ab97512 --- /dev/null +++ b/webrtc/modules/audio_processing/test/audio_processing_simulator.h @@ -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 +#include +#include +#include + +#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 stream_delay; + rtc::Optional stream_drift_samples; + rtc::Optional output_sample_rate_hz; + rtc::Optional output_num_channels; + rtc::Optional reverse_output_sample_rate_hz; + rtc::Optional reverse_output_num_channels; + rtc::Optional microphone_positions; + int target_angle_degrees = 90; + rtc::Optional output_filename; + rtc::Optional reverse_output_filename; + rtc::Optional input_filename; + rtc::Optional reverse_input_filename; + rtc::Optional use_aec; + rtc::Optional use_aecm; + rtc::Optional use_agc; + rtc::Optional use_hpf; + rtc::Optional use_ns; + rtc::Optional use_ts; + rtc::Optional use_bf; + rtc::Optional use_ie; + rtc::Optional use_vad; + rtc::Optional use_le; + rtc::Optional use_all; + rtc::Optional aec_suppression_level; + rtc::Optional use_delay_agnostic; + rtc::Optional use_extended_filter; + rtc::Optional use_drift_compensation; + rtc::Optional use_aec3; + rtc::Optional aecm_routing_mode; + rtc::Optional use_aecm_comfort_noise; + rtc::Optional agc_mode; + rtc::Optional agc_target_level; + rtc::Optional use_agc_limiter; + rtc::Optional agc_compression_gain; + rtc::Optional vad_likelihood; + rtc::Optional ns_level; + rtc::Optional 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 aec_dump_input_filename; + rtc::Optional 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::max()) {} + int64_t sum; + int64_t max; + int64_t min; +}; + +// Copies samples present in a ChannelBuffer into an AudioFrame. +void CopyToAudioFrame(const ChannelBuffer& 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 ap_; + + std::unique_ptr> in_buf_; + std::unique_ptr> out_buf_; + std::unique_ptr> reverse_in_buf_; + std::unique_ptr> reverse_out_buf_; + StreamConfig in_config_; + StreamConfig out_config_; + StreamConfig reverse_in_config_; + StreamConfig reverse_out_config_; + std::unique_ptr buffer_reader_; + std::unique_ptr 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 buffer_writer_; + std::unique_ptr 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_ diff --git a/webrtc/modules/audio_processing/test/audioproc_float.cc b/webrtc/modules/audio_processing/test/audioproc_float.cc index 33790d837f..4df5e2ea5e 100644 --- a/webrtc/modules/audio_processing/test/audioproc_float.cc +++ b/webrtc/modules/audio_processing/test/audioproc_float.cc @@ -8,179 +8,425 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include - #include #include -#include -#include -#include + +#include #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 \n" + " or\n" + " audioproc_f [options] -dump_input \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* parameter) { + if (value.compare("") != 0) { + *parameter = rtc::Optional(value); + } +} + +void SetSettingIfSpecified(int value, rtc::Optional* parameter) { + if (value != kParameterNotSpecifiedValue) { + *parameter = rtc::Optional(value); + } +} + +void SetSettingIfFlagSet(int32_t flag, rtc::Optional* parameter) { + if (flag == 0) { + *parameter = rtc::Optional(false); + } else if (flag == 1) { + *parameter = rtc::Optional(true); + } +} + +SimulationSettings CreateSettings() { + SimulationSettings settings; + if (FLAGS_all_default) { + settings.use_le = rtc::Optional(true); + settings.use_vad = rtc::Optional(true); + settings.use_ie = rtc::Optional(false); + settings.use_bf = rtc::Optional(false); + settings.use_ts = rtc::Optional(true); + settings.use_ns = rtc::Optional(true); + settings.use_hpf = rtc::Optional(true); + settings.use_agc = rtc::Optional(true); + settings.use_aec = rtc::Optional(true); + settings.use_aecm = rtc::Optional(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(new Beamforming( - true, ParseArrayGeometry(FLAGS_mic_positions), - SphericalPointf(DegreesToRadians(FLAGS_target_angle_degrees), 0.f, - 1.f))); - } - config.Set(new ExperimentalNs(FLAGS_ts || FLAGS_all)); - config.Set(new Intelligibility(FLAGS_ie || FLAGS_all)); - - std::unique_ptr 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(FLAGS_ns_level))); - } - ap->set_stream_key_pressed(FLAGS_ts); - - std::unique_ptr processor; - auto out_file = std::unique_ptr(new WavWriter( - FLAGS_o, FLAGS_out_sample_rate, static_cast(FLAGS_out_channels))); - std::cout << FLAGS_o << ": " << out_file->FormatAsString() << std::endl; - if (FLAGS_dump.empty()) { - auto in_file = std::unique_ptr(new WavReader(FLAGS_i)); - std::cout << FLAGS_i << ": " << in_file->FormatAsString() << std::endl; - std::unique_ptr reverse_in_file; - std::unique_ptr 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(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 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); } diff --git a/webrtc/modules/audio_processing/test/process_test.cc b/webrtc/modules/audio_processing/test/process_test.cc index 527e0a1e3e..317ecd96bc 100644 --- a/webrtc/modules/audio_processing/test/process_test.cc +++ b/webrtc/modules/audio_processing/test/process_test.cc @@ -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 * diff --git a/webrtc/modules/audio_processing/test/wav_based_simulator.cc b/webrtc/modules/audio_processing/test/wav_based_simulator.cc new file mode 100644 index 0000000000..3fbca92434 --- /dev/null +++ b/webrtc/modules/audio_processing/test/wav_based_simulator.cc @@ -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::GetDefaultEventChain() const { + std::vector 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 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 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 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 diff --git a/webrtc/modules/audio_processing/test/wav_based_simulator.h b/webrtc/modules/audio_processing/test/wav_based_simulator.h new file mode 100644 index 0000000000..aeb9331221 --- /dev/null +++ b/webrtc/modules/audio_processing/test/wav_based_simulator.h @@ -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 + +#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 GetDefaultEventChain() const; + + std::vector 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_