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}
diff --git a/webrtc/common_audio/wav_file.cc b/webrtc/common_audio/wav_file.cc
index 8dae7d6..b14b620 100644
--- a/webrtc/common_audio/wav_file.cc
+++ b/webrtc/common_audio/wav_file.cc
@@ -37,9 +37,17 @@
   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 @@
       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 2eadd3f..42b0618 100644
--- a/webrtc/common_audio/wav_file.h
+++ b/webrtc/common_audio/wav_file.h
@@ -29,6 +29,9 @@
   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 0314c69..b301b00 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 0000000..ca244d5
--- /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 <algorithm>
+
+#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<float> GetChannelBuffer(const WavFile& file) {
+  return ChannelBuffer<float>(
+      CheckedDivExact(file.sample_rate(), AudioFileProcessor::kChunksPerSecond),
+      file.num_channels());
+}
+
+}  // namespace
+
+WavFileProcessor::WavFileProcessor(scoped_ptr<AudioProcessing> ap,
+                                   scoped_ptr<WavReader> in_file,
+                                   scoped_ptr<WavWriter> 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<AudioProcessing> ap,
+                                           FILE* dump_file,
+                                           scoped_ptr<WavWriter> 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<float>(
+      CheckedDivExact(msg.sample_rate(), kChunksPerSecond),
+      msg.num_input_channels()));
+  const int reverse_sample_rate = msg.has_reverse_sample_rate()
+                                      ? msg.reverse_sample_rate()
+                                      : msg.sample_rate();
+  reverse_buf_.reset(new ChannelBuffer<float>(
+      CheckedDivExact(reverse_sample_rate, kChunksPerSecond),
+      msg.num_reverse_channels()));
+  input_config_ = StreamConfig(msg.sample_rate(), msg.num_input_channels());
+  reverse_config_ =
+      StreamConfig(reverse_sample_rate, msg.num_reverse_channels());
+
+  const ProcessingConfig config = {
+      {input_config_, output_config_, reverse_config_, reverse_config_}};
+  RTC_CHECK_EQ(kNoErr, ap_->Initialize(config));
+}
+
+void AecDumpFileProcessor::HandleMessage(const Stream& msg) {
+  RTC_CHECK(!msg.has_input_data());
+  RTC_CHECK_EQ(in_buf_->num_channels(), 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 0000000..a3153b2
--- /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 <algorithm>
+#include <limits>
+#include <vector>
+
+#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<int64_t>::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<AudioProcessing> ap,
+                   rtc::scoped_ptr<WavReader> in_file,
+                   rtc::scoped_ptr<WavWriter> out_file);
+  virtual ~WavFileProcessor() {}
+
+  // Processes one chunk from the WAV input and writes to the WAV output.
+  bool ProcessChunk() override;
+
+ private:
+  rtc::scoped_ptr<AudioProcessing> ap_;
+
+  ChannelBuffer<float> in_buf_;
+  ChannelBuffer<float> out_buf_;
+  const StreamConfig input_config_;
+  const StreamConfig output_config_;
+  ChannelBufferWavReader buffer_reader_;
+  ChannelBufferWavWriter buffer_writer_;
+};
+
+// 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<AudioProcessing> ap,
+                       FILE* dump_file,
+                       rtc::scoped_ptr<WavWriter> out_file);
+
+  virtual ~AecDumpFileProcessor();
+
+  // Processes messages from the aecdump file until the first Stream message is
+  // completed. Passes other data from the aecdump messages as appropriate.
+  bool ProcessChunk() override;
+
+ private:
+  void HandleMessage(const webrtc::audioproc::Init& msg);
+  void HandleMessage(const webrtc::audioproc::Stream& msg);
+  void HandleMessage(const webrtc::audioproc::ReverseStream& msg);
+
+  rtc::scoped_ptr<AudioProcessing> ap_;
+  FILE* dump_file_;
+
+  rtc::scoped_ptr<ChannelBuffer<float>> in_buf_;
+  rtc::scoped_ptr<ChannelBuffer<float>> reverse_buf_;
+  ChannelBuffer<float> out_buf_;
+  StreamConfig input_config_;
+  StreamConfig reverse_config_;
+  const StreamConfig output_config_;
+  ChannelBufferWavWriter buffer_writer_;
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_AUDIO_PROCESSING_TEST_AUDIO_FILE_PROCESSOR_H_
diff --git a/webrtc/modules/audio_processing/test/audioproc_float.cc b/webrtc/modules/audio_processing/test/audioproc_float.cc
index 811e907..27c704b 100644
--- a/webrtc/modules/audio_processing/test/audioproc_float.cc
+++ b/webrtc/modules/audio_processing/test/audioproc_float.cc
@@ -9,6 +9,7 @@
  */
 
 #include <stdio.h>
+#include <iostream>
 #include <sstream>
 #include <string>
 
@@ -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 @@
     "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 @@
             "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<ExperimentalNs>(new ExperimentalNs(FLAGS_ts || FLAGS_all));
-  config.Set<Intelligibility>(new Intelligibility(FLAGS_ie || FLAGS_all));
-
   if (FLAGS_bf || FLAGS_all) {
-    const size_t num_mics = in_file.num_channels();
-    const std::vector<Point> 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<Beamforming>(new Beamforming(
-        true, array_geometry,
+        true, ParseArrayGeometry(FLAGS_mic_positions),
         SphericalPointf(DegreesToRadians(FLAGS_target_angle_degrees), 0.f,
                         1.f)));
   }
+  config.Set<ExperimentalNs>(new ExperimentalNs(FLAGS_ts || FLAGS_all));
+  config.Set<Intelligibility>(new Intelligibility(FLAGS_ie || FLAGS_all));
 
   rtc::scoped_ptr<AudioProcessing> 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<NoiseSuppression::Level>(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<float> in_buf(
-      rtc::CheckedDivExact(in_file.sample_rate(), kChunksPerSecond),
-      in_file.num_channels());
-  ChannelBuffer<float> out_buf(
-      rtc::CheckedDivExact(out_file.sample_rate(), kChunksPerSecond),
-      out_file.num_channels());
-
-  std::vector<float> in_interleaved(in_buf.size());
-  std::vector<float> out_interleaved(out_buf.size());
-
-  rtc::scoped_ptr<WavReader> in_rev_file;
-  rtc::scoped_ptr<WavWriter> out_rev_file;
-  rtc::scoped_ptr<ChannelBuffer<float>> in_rev_buf;
-  rtc::scoped_ptr<ChannelBuffer<float>> out_rev_buf;
-  std::vector<float> in_rev_interleaved;
-  std::vector<float> 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<float>(
-        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<float>(
-        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<AudioFileProcessor> 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 4316540..555d9d6 100644
--- a/webrtc/modules/audio_processing/test/process_test.cc
+++ b/webrtc/modules/audio_processing/test/process_test.cc
@@ -636,8 +636,8 @@
         }
 
         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 1b9ac3c..47bd314 100644
--- a/webrtc/modules/audio_processing/test/test_utils.cc
+++ b/webrtc/modules/audio_processing/test/test_utils.cc
@@ -31,6 +31,35 @@
   fwrite(samples, sizeof(*samples), num_samples, file_handle_);
 }
 
+ChannelBufferWavReader::ChannelBufferWavReader(rtc::scoped_ptr<WavReader> file)
+    : file_(file.Pass()) {}
+
+bool ChannelBufferWavReader::Read(ChannelBuffer<float>* 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<WavWriter> file)
+    : file_(file.Pass()) {}
+
+void ChannelBufferWavWriter::Write(const ChannelBuffer<float>& 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 @@
     case 2:
       return AudioProcessing::kStereo;
     default:
-      assert(false);
+      RTC_CHECK(false);
       return AudioProcessing::kMono;
   }
 }
 
-std::vector<Point> ParseArrayGeometry(const std::string& mic_positions,
-                                      size_t num_mics) {
+std::vector<Point> ParseArrayGeometry(const std::string& mic_positions) {
   const std::vector<float> values = ParseList<float>(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<size_t>(3));
+  RTC_CHECK_GT(num_mics, 0u) << "mic_positions is not large enough.";
 
   std::vector<Point> 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<Point> ParseArrayGeometry(const std::string& mic_positions,
+                                      size_t num_mics) {
+  std::vector<Point> 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 8dd380b..f53d8ac 100644
--- a/webrtc/modules/audio_processing/test/test_utils.h
+++ b/webrtc/modules/audio_processing/test/test_utils.h
@@ -43,6 +43,35 @@
   RTC_DISALLOW_COPY_AND_ASSIGN(RawFile);
 };
 
+// Reads ChannelBuffers from a provided WavReader.
+class ChannelBufferWavReader final {
+ public:
+  explicit ChannelBufferWavReader(rtc::scoped_ptr<WavReader> 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<float>* buffer);
+
+ private:
+  rtc::scoped_ptr<WavReader> file_;
+  std::vector<float> interleaved_;
+
+  RTC_DISALLOW_COPY_AND_ASSIGN(ChannelBufferWavReader);
+};
+
+// Writes ChannelBuffers to a provided WavWriter.
+class ChannelBufferWavWriter final {
+ public:
+  explicit ChannelBufferWavWriter(rtc::scoped_ptr<WavWriter> file);
+  void Write(const ChannelBuffer<float>& buffer);
+
+ private:
+  rtc::scoped_ptr<WavWriter> file_;
+  std::vector<float> 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<Point> 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<Point> 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 0b7890e..e0f5861 100644
--- a/webrtc/system_wrappers/include/tick_util.h
+++ b/webrtc/system_wrappers/include/tick_util.h
@@ -83,6 +83,7 @@
 class TickInterval {
  public:
   TickInterval();
+  explicit TickInterval(int64_t interval);
 
   int64_t Milliseconds() const;
   int64_t Microseconds() const;
@@ -103,8 +104,6 @@
   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);