Add aecdump support to audioproc_f

Originally landed here: https://codereview.webrtc.org/1409943002/
The transient suppression fix landed here: https://codereview.webrtc.org/1411423010/

TBR=mflodman

Review URL: https://codereview.webrtc.org/1432843002

Cr-Commit-Position: refs/heads/master@{#10722}
diff --git a/webrtc/common_audio/wav_file.cc b/webrtc/common_audio/wav_file.cc
index 8dae7d6..ac11bcd 100644
--- a/webrtc/common_audio/wav_file.cc
+++ b/webrtc/common_audio/wav_file.cc
@@ -13,6 +13,7 @@
 #include <algorithm>
 #include <cstdio>
 #include <limits>
+#include <sstream>
 
 #include "webrtc/base/checks.h"
 #include "webrtc/base/safe_conversions.h"
@@ -37,9 +38,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 +105,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..523602b 100644
--- a/webrtc/modules/audio_processing/audio_processing_tests.gypi
+++ b/webrtc/modules/audio_processing/audio_processing_tests.gypi
@@ -128,7 +128,11 @@
             '<(webrtc_root)/test/test.gyp:test_support',
             '<(DEPTH)/third_party/gflags/gflags.gyp:gflags',
           ],
-          'sources': [ 'test/audioproc_float.cc', ],
+          'sources': [
+            'test/audio_file_processor.cc',
+            'test/audio_file_processor.h',
+            'test/audioproc_float.cc',
+          ],
         },
         {
           'target_name': 'unpack_aecdump',
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 2697e51..3f1dc37 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,48 +78,34 @@
             "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) {
@@ -135,109 +115,38 @@
   }
   ap->set_stream_key_pressed(FLAGS_ts);
 
-  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());
+  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()));
 
-  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());
+  } else {
+    processor.reset(new AecDumpFileProcessor(
+        ap.Pass(), fopen(FLAGS_dump.c_str(), "rb"), out_file.Pass()));
   }
 
-  TickTime processing_start_time;
-  TickInterval accumulated_time;
   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 d07db92..ae6b4dc 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 75e4239..93a0138 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 6e3b05e..f8a5ef7 100644
--- a/webrtc/system_wrappers/include/tick_util.h
+++ b/webrtc/system_wrappers/include/tick_util.h
@@ -85,6 +85,7 @@
 class TickInterval {
  public:
   TickInterval();
+  explicit TickInterval(int64_t interval);
 
   int64_t Milliseconds() const;
   int64_t Microseconds() const;
@@ -105,8 +106,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);