From bdafe31b86e9819b0adb9041f87e6194b7422b08 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 29 Oct 2015 23:42:54 -0700 Subject: [PATCH] Add aecdump support to audioproc_f. Add a new interface to abstract away file operations. This CL temporarily removes support for dumping the output of reverse streams. It will be easy to restore in the new framework, although we may decide to only allow it with the aecdump format. We also now require the user to specify the output format, rather than defaulting to the input format. TEST=Bit-exact output to the previous audioproc_f version using an input wav file, and to the legacy audioproc using an aecdump file. Review URL: https://codereview.webrtc.org/1409943002 Cr-Commit-Position: refs/heads/master@{#10460} --- webrtc/common_audio/wav_file.cc | 12 +- webrtc/common_audio/wav_file.h | 3 + .../audio_processing_tests.gypi | 3 + .../test/audio_file_processor.cc | 177 +++++++++++++++ .../test/audio_file_processor.h | 139 ++++++++++++ .../audio_processing/test/audioproc_float.cc | 204 +++++------------- .../audio_processing/test/process_test.cc | 4 +- .../audio_processing/test/test_utils.cc | 51 ++++- .../audio_processing/test/test_utils.h | 32 +++ webrtc/system_wrappers/include/tick_util.h | 3 +- 10 files changed, 466 insertions(+), 162 deletions(-) create mode 100644 webrtc/modules/audio_processing/test/audio_file_processor.cc create mode 100644 webrtc/modules/audio_processing/test/audio_file_processor.h diff --git a/webrtc/common_audio/wav_file.cc b/webrtc/common_audio/wav_file.cc index 8dae7d6e98..b14b6208a4 100644 --- a/webrtc/common_audio/wav_file.cc +++ b/webrtc/common_audio/wav_file.cc @@ -37,9 +37,17 @@ class ReadableWavFile : public ReadableWav { FILE* file_; }; +std::string WavFile::FormatAsString() const { + std::ostringstream s; + s << "Sample rate: " << sample_rate() << " Hz, Channels: " << num_channels() + << ", Duration: " + << (1.f * num_samples()) / (num_channels() * sample_rate()) << " s"; + return s.str(); +} + WavReader::WavReader(const std::string& filename) : file_handle_(fopen(filename.c_str(), "rb")) { - RTC_CHECK(file_handle_ && "Could not open wav file for reading."); + RTC_CHECK(file_handle_) << "Could not open wav file for reading."; ReadableWavFile readable(file_handle_); WavFormat format; @@ -96,7 +104,7 @@ WavWriter::WavWriter(const std::string& filename, int sample_rate, num_channels_(num_channels), num_samples_(0), file_handle_(fopen(filename.c_str(), "wb")) { - RTC_CHECK(file_handle_ && "Could not open wav file for writing."); + RTC_CHECK(file_handle_) << "Could not open wav file for writing."; RTC_CHECK(CheckWavParameters(num_channels_, sample_rate_, kWavFormat, kBytesPerSample, num_samples_)); diff --git a/webrtc/common_audio/wav_file.h b/webrtc/common_audio/wav_file.h index 2eadd3f775..42b0618e9c 100644 --- a/webrtc/common_audio/wav_file.h +++ b/webrtc/common_audio/wav_file.h @@ -29,6 +29,9 @@ class WavFile { virtual int sample_rate() const = 0; virtual int num_channels() const = 0; virtual uint32_t num_samples() const = 0; + + // Returns a human-readable string containing the audio format. + std::string FormatAsString() const; }; // Simple C++ class for writing 16-bit PCM WAV files. All error handling is diff --git a/webrtc/modules/audio_processing/audio_processing_tests.gypi b/webrtc/modules/audio_processing/audio_processing_tests.gypi index 0314c69b04..b301b00e4c 100644 --- a/webrtc/modules/audio_processing/audio_processing_tests.gypi +++ b/webrtc/modules/audio_processing/audio_processing_tests.gypi @@ -12,10 +12,13 @@ 'target_name': 'audioproc_test_utils', 'type': 'static_library', 'dependencies': [ + 'audioproc_debug_proto', '<(webrtc_root)/base/base.gyp:rtc_base_approved', '<(webrtc_root)/common_audio/common_audio.gyp:common_audio', ], 'sources': [ + 'test/audio_file_processor.cc', + 'test/audio_file_processor.h', 'test/test_utils.cc', 'test/test_utils.h', ], diff --git a/webrtc/modules/audio_processing/test/audio_file_processor.cc b/webrtc/modules/audio_processing/test/audio_file_processor.cc new file mode 100644 index 0000000000..ca244d550f --- /dev/null +++ b/webrtc/modules/audio_processing/test/audio_file_processor.cc @@ -0,0 +1,177 @@ +/* + * 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 "webrtc/base/checks.h" +#include "webrtc/modules/audio_processing/test/protobuf_utils.h" + +using rtc::scoped_ptr; +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(scoped_ptr ap, + scoped_ptr in_file, + scoped_ptr out_file) + : ap_(ap.Pass()), + in_buf_(GetChannelBuffer(*in_file)), + out_buf_(GetChannelBuffer(*out_file)), + input_config_(GetStreamConfig(*in_file)), + output_config_(GetStreamConfig(*out_file)), + buffer_reader_(in_file.Pass()), + buffer_writer_(out_file.Pass()) {} + +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_); + return true; +} + +AecDumpFileProcessor::AecDumpFileProcessor(scoped_ptr ap, + FILE* dump_file, + scoped_ptr out_file) + : ap_(ap.Pass()), + dump_file_(dump_file), + out_buf_(GetChannelBuffer(*out_file)), + output_config_(GetStreamConfig(*out_file)), + buffer_writer_(out_file.Pass()) { + 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(), 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(), 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 new file mode 100644 index 0000000000..a3153b2244 --- /dev/null +++ b/webrtc/modules/audio_processing/test/audio_file_processor.h @@ -0,0 +1,139 @@ +/* + * 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 "webrtc/base/scoped_ptr.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" +#include "webrtc/system_wrappers/include/tick_util.h" + +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/modules/audio_processing/debug.pb.h" +#else +#include "webrtc/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()) {} + TickInterval sum; + TickInterval max; + TickInterval 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_(TickTime::Now()) {} + + ~ScopedTimer() { + TickInterval interval = TickTime::Now() - 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_; + TickTime 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(rtc::scoped_ptr ap, + rtc::scoped_ptr in_file, + rtc::scoped_ptr out_file); + virtual ~WavFileProcessor() {} + + // Processes one chunk from the WAV input and writes to the WAV output. + bool ProcessChunk() override; + + private: + rtc::scoped_ptr ap_; + + ChannelBuffer in_buf_; + ChannelBuffer out_buf_; + const StreamConfig input_config_; + const StreamConfig output_config_; + ChannelBufferWavReader buffer_reader_; + ChannelBufferWavWriter 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(rtc::scoped_ptr ap, + FILE* dump_file, + rtc::scoped_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); + + rtc::scoped_ptr ap_; + FILE* dump_file_; + + rtc::scoped_ptr> in_buf_; + rtc::scoped_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/audioproc_float.cc b/webrtc/modules/audio_processing/test/audioproc_float.cc index 811e9070fa..27c704bd20 100644 --- a/webrtc/modules/audio_processing/test/audioproc_float.cc +++ b/webrtc/modules/audio_processing/test/audioproc_float.cc @@ -9,6 +9,7 @@ */ #include +#include #include #include @@ -18,26 +19,28 @@ #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/system_wrappers/include/tick_util.h" #include "webrtc/test/testsupport/trace_to_stderr.h" -DEFINE_string(dump, "", "The name of the debug dump file to read from."); -DEFINE_string(i, "", "The name of the input file to read from."); -DEFINE_string(i_rev, "", "The name of the reverse input file to read from."); -DEFINE_string(o, "out.wav", "Name of the output file to write to."); -DEFINE_string(o_rev, - "out_rev.wav", - "Name of the reverse output file to write to."); -DEFINE_int32(out_channels, 0, "Number of output channels. Defaults to input."); -DEFINE_int32(out_sample_rate, 0, - "Output sample rate in Hz. Defaults to input."); +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_int32(out_channels, 1, "Number of output channels."); +DEFINE_int32(out_sample_rate, 48000, "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 radians"); +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."); @@ -64,15 +67,6 @@ const char kUsage[] = "All components are disabled by default. If any bi-directional components\n" "are enabled, only debug dump files are permitted."; -// Returns a StreamConfig corresponding to wav_file if it's non-nullptr. -// Otherwise returns a default initialized StreamConfig. -StreamConfig MakeStreamConfig(const WavFile* wav_file) { - if (wav_file) { - return {wav_file->sample_rate(), wav_file->num_channels()}; - } - return {}; -} - } // namespace int main(int argc, char* argv[]) { @@ -84,158 +78,74 @@ int main(int argc, char* argv[]) { "An input file must be specified with either -i or -dump.\n"); return 1; } - if (!FLAGS_dump.empty()) { - fprintf(stderr, "FIXME: the -dump option is not yet implemented.\n"); + if (FLAGS_dump.empty() && (FLAGS_aec || FLAGS_ie)) { + fprintf(stderr, "-aec and -ie require a -dump file.\n"); + return 1; + } + if (FLAGS_ie) { + fprintf(stderr, + "FIXME(ajm): The intelligibility enhancer output is not dumped.\n"); return 1; } test::TraceToStderr trace_to_stderr(true); - WavReader in_file(FLAGS_i); - // If the output format is uninitialized, use the input format. - const int out_channels = - FLAGS_out_channels ? FLAGS_out_channels : in_file.num_channels(); - const int out_sample_rate = - FLAGS_out_sample_rate ? FLAGS_out_sample_rate : in_file.sample_rate(); - WavWriter out_file(FLAGS_o, out_sample_rate, out_channels); - Config config; - config.Set(new ExperimentalNs(FLAGS_ts || FLAGS_all)); - config.Set(new Intelligibility(FLAGS_ie || FLAGS_all)); - if (FLAGS_bf || FLAGS_all) { - const size_t num_mics = in_file.num_channels(); - const std::vector array_geometry = - ParseArrayGeometry(FLAGS_mic_positions, num_mics); - RTC_CHECK_EQ(array_geometry.size(), num_mics); - + if (FLAGS_mic_positions.empty()) { + fprintf(stderr, "-mic_positions must be specified when -bf is used.\n"); + return 1; + } config.Set(new Beamforming( - true, array_geometry, + 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)); rtc::scoped_ptr ap(AudioProcessing::Create(config)); - if (!FLAGS_dump.empty()) { - RTC_CHECK_EQ(kNoErr, - ap->echo_cancellation()->Enable(FLAGS_aec || FLAGS_all)); - } else if (FLAGS_aec) { - fprintf(stderr, "-aec requires a -dump file.\n"); - return -1; - } - bool process_reverse = !FLAGS_i_rev.empty(); + 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->gain_control()->set_mode(GainControl::kFixedDigital)); 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) + if (FLAGS_ns_level != -1) { RTC_CHECK_EQ(kNoErr, ap->noise_suppression()->set_level( static_cast(FLAGS_ns_level))); - - printf("Input file: %s\nChannels: %d, Sample rate: %d Hz\n\n", - FLAGS_i.c_str(), in_file.num_channels(), in_file.sample_rate()); - printf("Output file: %s\nChannels: %d, Sample rate: %d Hz\n\n", - FLAGS_o.c_str(), out_file.num_channels(), out_file.sample_rate()); - - ChannelBuffer in_buf( - rtc::CheckedDivExact(in_file.sample_rate(), kChunksPerSecond), - in_file.num_channels()); - ChannelBuffer out_buf( - rtc::CheckedDivExact(out_file.sample_rate(), kChunksPerSecond), - out_file.num_channels()); - - std::vector in_interleaved(in_buf.size()); - std::vector out_interleaved(out_buf.size()); - - rtc::scoped_ptr in_rev_file; - rtc::scoped_ptr out_rev_file; - rtc::scoped_ptr> in_rev_buf; - rtc::scoped_ptr> out_rev_buf; - std::vector in_rev_interleaved; - std::vector out_rev_interleaved; - if (process_reverse) { - in_rev_file.reset(new WavReader(FLAGS_i_rev)); - out_rev_file.reset(new WavWriter(FLAGS_o_rev, in_rev_file->sample_rate(), - in_rev_file->num_channels())); - printf("In rev file: %s\nChannels: %d, Sample rate: %d Hz\n\n", - FLAGS_i_rev.c_str(), in_rev_file->num_channels(), - in_rev_file->sample_rate()); - printf("Out rev file: %s\nChannels: %d, Sample rate: %d Hz\n\n", - FLAGS_o_rev.c_str(), out_rev_file->num_channels(), - out_rev_file->sample_rate()); - in_rev_buf.reset(new ChannelBuffer( - rtc::CheckedDivExact(in_rev_file->sample_rate(), kChunksPerSecond), - in_rev_file->num_channels())); - in_rev_interleaved.resize(in_rev_buf->size()); - out_rev_buf.reset(new ChannelBuffer( - rtc::CheckedDivExact(out_rev_file->sample_rate(), kChunksPerSecond), - out_rev_file->num_channels())); - out_rev_interleaved.resize(out_rev_buf->size()); } - TickTime processing_start_time; - TickInterval accumulated_time; + rtc::scoped_ptr processor; + auto out_file = rtc_make_scoped_ptr( + new WavWriter(FLAGS_o, FLAGS_out_sample_rate, FLAGS_out_channels)); + std::cout << FLAGS_o << ": " << out_file->FormatAsString() << std::endl; + if (FLAGS_dump.empty()) { + auto in_file = rtc_make_scoped_ptr(new WavReader(FLAGS_i)); + std::cout << FLAGS_i << ": " << in_file->FormatAsString() << std::endl; + processor.reset( + new WavFileProcessor(ap.Pass(), in_file.Pass(), out_file.Pass())); + + } else { + processor.reset(new AecDumpFileProcessor( + ap.Pass(), fopen(FLAGS_dump.c_str(), "rb"), out_file.Pass())); + } + int num_chunks = 0; - - const auto input_config = MakeStreamConfig(&in_file); - const auto output_config = MakeStreamConfig(&out_file); - const auto reverse_input_config = MakeStreamConfig(in_rev_file.get()); - const auto reverse_output_config = MakeStreamConfig(out_rev_file.get()); - - while (in_file.ReadSamples(in_interleaved.size(), - &in_interleaved[0]) == in_interleaved.size()) { - // Have logs display the file time rather than wallclock time. + while (processor->ProcessChunk()) { trace_to_stderr.SetTimeSeconds(num_chunks * 1.f / kChunksPerSecond); - FloatS16ToFloat(&in_interleaved[0], in_interleaved.size(), - &in_interleaved[0]); - Deinterleave(&in_interleaved[0], in_buf.num_frames(), - in_buf.num_channels(), in_buf.channels()); - if (process_reverse) { - in_rev_file->ReadSamples(in_rev_interleaved.size(), - in_rev_interleaved.data()); - FloatS16ToFloat(in_rev_interleaved.data(), in_rev_interleaved.size(), - in_rev_interleaved.data()); - Deinterleave(in_rev_interleaved.data(), in_rev_buf->num_frames(), - in_rev_buf->num_channels(), in_rev_buf->channels()); - } - - if (FLAGS_perf) { - processing_start_time = TickTime::Now(); - } - RTC_CHECK_EQ(kNoErr, ap->ProcessStream(in_buf.channels(), input_config, - output_config, out_buf.channels())); - if (process_reverse) { - RTC_CHECK_EQ(kNoErr, ap->ProcessReverseStream( - in_rev_buf->channels(), reverse_input_config, - reverse_output_config, out_rev_buf->channels())); - } - if (FLAGS_perf) { - accumulated_time += TickTime::Now() - processing_start_time; - } - - Interleave(out_buf.channels(), out_buf.num_frames(), - out_buf.num_channels(), &out_interleaved[0]); - FloatToFloatS16(&out_interleaved[0], out_interleaved.size(), - &out_interleaved[0]); - out_file.WriteSamples(&out_interleaved[0], out_interleaved.size()); - if (process_reverse) { - Interleave(out_rev_buf->channels(), out_rev_buf->num_frames(), - out_rev_buf->num_channels(), out_rev_interleaved.data()); - FloatToFloatS16(out_rev_interleaved.data(), out_rev_interleaved.size(), - out_rev_interleaved.data()); - out_rev_file->WriteSamples(out_rev_interleaved.data(), - out_rev_interleaved.size()); - } - num_chunks++; + ++num_chunks; } + if (FLAGS_perf) { - int64_t execution_time_ms = accumulated_time.Milliseconds(); - printf("\nExecution time: %.3f s\nFile time: %.2f s\n" - "Time per chunk: %.3f ms\n", - execution_time_ms * 0.001f, num_chunks * 1.f / kChunksPerSecond, - execution_time_ms * 1.f / num_chunks); + const auto& proc_time = processor->proc_time(); + int64_t exec_time_us = proc_time.sum.Microseconds(); + 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.Microseconds(), + 1.f * proc_time.min.Microseconds()); } + return 0; } diff --git a/webrtc/modules/audio_processing/test/process_test.cc b/webrtc/modules/audio_processing/test/process_test.cc index 43165404c8..555d9d6133 100644 --- a/webrtc/modules/audio_processing/test/process_test.cc +++ b/webrtc/modules/audio_processing/test/process_test.cc @@ -636,8 +636,8 @@ void void_main(int argc, char* argv[]) { } if (!raw_output) { - // The WAV file needs to be reset every time, because it cant change - // it's sample rate or number of channels. + // The WAV file needs to be reset every time, because it can't change + // its sample rate or number of channels. output_wav_file.reset(new WavWriter(out_filename + ".wav", output_sample_rate, msg.num_output_channels())); diff --git a/webrtc/modules/audio_processing/test/test_utils.cc b/webrtc/modules/audio_processing/test/test_utils.cc index 1b9ac3ce4c..47bd3144cc 100644 --- a/webrtc/modules/audio_processing/test/test_utils.cc +++ b/webrtc/modules/audio_processing/test/test_utils.cc @@ -31,6 +31,35 @@ void RawFile::WriteSamples(const float* samples, size_t num_samples) { fwrite(samples, sizeof(*samples), num_samples, file_handle_); } +ChannelBufferWavReader::ChannelBufferWavReader(rtc::scoped_ptr file) + : file_(file.Pass()) {} + +bool ChannelBufferWavReader::Read(ChannelBuffer* buffer) { + RTC_CHECK_EQ(file_->num_channels(), buffer->num_channels()); + interleaved_.resize(buffer->size()); + if (file_->ReadSamples(interleaved_.size(), &interleaved_[0]) != + interleaved_.size()) { + return false; + } + + FloatS16ToFloat(&interleaved_[0], interleaved_.size(), &interleaved_[0]); + Deinterleave(&interleaved_[0], buffer->num_frames(), buffer->num_channels(), + buffer->channels()); + return true; +} + +ChannelBufferWavWriter::ChannelBufferWavWriter(rtc::scoped_ptr file) + : file_(file.Pass()) {} + +void ChannelBufferWavWriter::Write(const ChannelBuffer& buffer) { + RTC_CHECK_EQ(file_->num_channels(), buffer.num_channels()); + interleaved_.resize(buffer.size()); + Interleave(buffer.channels(), buffer.num_frames(), buffer.num_channels(), + &interleaved_[0]); + FloatToFloatS16(&interleaved_[0], interleaved_.size(), &interleaved_[0]); + file_->WriteSamples(&interleaved_[0], interleaved_.size()); +} + void WriteIntData(const int16_t* data, size_t length, WavWriter* wav_file, @@ -92,28 +121,32 @@ AudioProcessing::ChannelLayout LayoutFromChannels(int num_channels) { case 2: return AudioProcessing::kStereo; default: - assert(false); + RTC_CHECK(false); return AudioProcessing::kMono; } } -std::vector ParseArrayGeometry(const std::string& mic_positions, - size_t num_mics) { +std::vector ParseArrayGeometry(const std::string& mic_positions) { const std::vector values = ParseList(mic_positions); - RTC_CHECK_EQ(values.size(), 3 * num_mics) - << "Could not parse mic_positions or incorrect number of points."; + const size_t num_mics = + rtc::CheckedDivExact(values.size(), static_cast(3)); + RTC_CHECK_GT(num_mics, 0u) << "mic_positions is not large enough."; std::vector result; result.reserve(num_mics); for (size_t i = 0; i < values.size(); i += 3) { - double x = values[i + 0]; - double y = values[i + 1]; - double z = values[i + 2]; - result.push_back(Point(x, y, z)); + result.push_back(Point(values[i + 0], values[i + 1], values[i + 2])); } return result; } +std::vector ParseArrayGeometry(const std::string& mic_positions, + size_t num_mics) { + std::vector result = ParseArrayGeometry(mic_positions); + RTC_CHECK_EQ(result.size(), num_mics) + << "Could not parse mic_positions or incorrect number of points."; + return result; +} } // namespace webrtc diff --git a/webrtc/modules/audio_processing/test/test_utils.h b/webrtc/modules/audio_processing/test/test_utils.h index 8dd380b15d..f53d8ac81c 100644 --- a/webrtc/modules/audio_processing/test/test_utils.h +++ b/webrtc/modules/audio_processing/test/test_utils.h @@ -43,6 +43,35 @@ class RawFile final { RTC_DISALLOW_COPY_AND_ASSIGN(RawFile); }; +// Reads ChannelBuffers from a provided WavReader. +class ChannelBufferWavReader final { + public: + explicit ChannelBufferWavReader(rtc::scoped_ptr file); + + // Reads data from the file according to the |buffer| format. Returns false if + // a full buffer can't be read from the file. + bool Read(ChannelBuffer* buffer); + + private: + rtc::scoped_ptr file_; + std::vector interleaved_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ChannelBufferWavReader); +}; + +// Writes ChannelBuffers to a provided WavWriter. +class ChannelBufferWavWriter final { + public: + explicit ChannelBufferWavWriter(rtc::scoped_ptr file); + void Write(const ChannelBuffer& buffer); + + private: + rtc::scoped_ptr file_; + std::vector interleaved_; + + RTC_DISALLOW_COPY_AND_ASSIGN(ChannelBufferWavWriter); +}; + void WriteIntData(const int16_t* data, size_t length, WavWriter* wav_file, @@ -118,6 +147,9 @@ std::vector ParseList(const std::string& to_parse) { std::vector ParseArrayGeometry(const std::string& mic_positions, size_t num_mics); +// Same as above, but without the num_mics check for when it isn't available. +std::vector ParseArrayGeometry(const std::string& mic_positions); + } // namespace webrtc #endif // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_TEST_UTILS_H_ diff --git a/webrtc/system_wrappers/include/tick_util.h b/webrtc/system_wrappers/include/tick_util.h index 0b7890e7c8..e0f5861fa7 100644 --- a/webrtc/system_wrappers/include/tick_util.h +++ b/webrtc/system_wrappers/include/tick_util.h @@ -83,6 +83,7 @@ class TickTime { class TickInterval { public: TickInterval(); + explicit TickInterval(int64_t interval); int64_t Milliseconds() const; int64_t Microseconds() const; @@ -103,8 +104,6 @@ class TickInterval { friend bool operator>=(const TickInterval& lhs, const TickInterval& rhs); private: - explicit TickInterval(int64_t interval); - friend class TickTime; friend TickInterval operator-(const TickTime& lhs, const TickTime& rhs);