Reland of Add optional visualization file writers to VideoProcessor tests. (patchset #1 id:1 of https://codereview.webrtc.org/2708103002/ )

Reason for revert:
Necessary calls were "protected" by RTC_DCHECKs, that were optimized away in some release builds.
Replacing the RTC_DCHECKs with EXPECTs.

Original issue's description:
> Revert of Add optional visualization file writers to VideoProcessor tests. (patchset #4 id:220001 of https://codereview.webrtc.org/2700493006/ )
>
> Reason for revert:
> Breaks downstream project.
>
> Original issue's description:
> > Add optional visualization file writers to VideoProcessor tests.
> >
> > The purpose of this visualization CL is to add the ability to record
> > video at the source, after encode, and after decode, in the VideoProcessor
> > tests. These output files can then be replayed and used as a subjective
> > complement to the objective metric plots given by the existing Python
> > plotting script.
> >
> > BUG=webrtc:6634
> >
> > Review-Url: https://codereview.webrtc.org/2700493006
> > Cr-Commit-Position: refs/heads/master@{#16738}
> > Committed: https://chromium.googlesource.com/external/webrtc/+/872104ac41d7764f8676c9ea55555210bea4605c
>
> TBR=asapersson@webrtc.org,sprang@webrtc.org,kjellander@webrtc.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=webrtc:6634
>
> Review-Url: https://codereview.webrtc.org/2708103002
> Cr-Commit-Position: refs/heads/master@{#16745}
> Committed: https://chromium.googlesource.com/external/webrtc/+/2a8135a1741761bd6de52163c0dc35f6eff7c8eb

TBR=asapersson@webrtc.org,sprang@webrtc.org,kjellander@webrtc.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
BUG=webrtc:6634

Review-Url: https://codereview.webrtc.org/2706123003
Cr-Commit-Position: refs/heads/master@{#16769}
diff --git a/webrtc/modules/video_coding/BUILD.gn b/webrtc/modules/video_coding/BUILD.gn
index e8ac671..da6343e 100644
--- a/webrtc/modules/video_coding/BUILD.gn
+++ b/webrtc/modules/video_coding/BUILD.gn
@@ -351,6 +351,7 @@
     deps = [
       ":video_codecs_test_framework",
       ":video_coding",
+      ":video_coding_utility",
       ":webrtc_h264",
       ":webrtc_vp8",
       ":webrtc_vp9",
diff --git a/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc b/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc
index ffc22c4..d82169b 100644
--- a/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc
+++ b/webrtc/modules/video_coding/codecs/test/plot_videoprocessor_integrationtest.cc
@@ -12,6 +12,7 @@
 
 namespace webrtc {
 namespace test {
+
 namespace {
 // Codec settings.
 const int kBitrates[] = {30, 50, 100, 200, 300, 500, 1000};
@@ -27,6 +28,12 @@
 // Packet loss probability [0.0, 1.0].
 const float kPacketLoss = 0.0f;
 
+const VisualizationParams kVisualizationParams = {
+    false,  // save_source_y4m
+    false,  // save_encoded_ivf
+    false,  // save_decoded_y4m
+};
+
 const bool kVerboseLogging = true;
 }  // namespace
 
@@ -75,7 +82,7 @@
                           0,    // num_spatial_resizes
                           1);   // num_key_frames
     ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                           rc_metrics);
+                           rc_metrics, &kVisualizationParams);
   }
   const int bitrate_;
   const int framerate_;
diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor.cc
index 99a34fc..c7c152d 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor.cc
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor.cc
@@ -29,6 +29,10 @@
 namespace webrtc {
 namespace test {
 
+namespace {
+const int k90khzTimestampFrameDiff = 3000;  // Assuming 30 fps.
+}  // namespace
+
 const char* ExcludeFrameTypesToStr(ExcludeFrameTypes e) {
   switch (e) {
     case kExcludeOnlyFirstKeyFrame:
@@ -60,18 +64,24 @@
 
 VideoProcessorImpl::VideoProcessorImpl(webrtc::VideoEncoder* encoder,
                                        webrtc::VideoDecoder* decoder,
-                                       FrameReader* frame_reader,
-                                       FrameWriter* frame_writer,
+                                       FrameReader* analysis_frame_reader,
+                                       FrameWriter* analysis_frame_writer,
                                        PacketManipulator* packet_manipulator,
                                        const TestConfig& config,
-                                       Stats* stats)
+                                       Stats* stats,
+                                       FrameWriter* source_frame_writer,
+                                       IvfFileWriter* encoded_frame_writer,
+                                       FrameWriter* decoded_frame_writer)
     : encoder_(encoder),
       decoder_(decoder),
-      frame_reader_(frame_reader),
-      frame_writer_(frame_writer),
+      analysis_frame_reader_(analysis_frame_reader),
+      analysis_frame_writer_(analysis_frame_writer),
       packet_manipulator_(packet_manipulator),
       config_(config),
       stats_(stats),
+      source_frame_writer_(source_frame_writer),
+      encoded_frame_writer_(encoded_frame_writer),
+      decoded_frame_writer_(decoded_frame_writer),
       first_key_frame_has_been_excluded_(false),
       last_frame_missing_(false),
       initialized_(false),
@@ -94,8 +104,8 @@
       *config.codec_settings, std::move(tl_factory));
   RTC_DCHECK(encoder);
   RTC_DCHECK(decoder);
-  RTC_DCHECK(frame_reader);
-  RTC_DCHECK(frame_writer);
+  RTC_DCHECK(analysis_frame_reader);
+  RTC_DCHECK(analysis_frame_writer);
   RTC_DCHECK(packet_manipulator);
   RTC_DCHECK(stats);
 }
@@ -105,7 +115,7 @@
   bit_rate_factor_ = config_.codec_settings->maxFramerate * 0.001 * 8;  // bits
 
   // Initialize data structures used by the encoder/decoder APIs.
-  size_t frame_length_in_bytes = frame_reader_->FrameLength();
+  size_t frame_length_in_bytes = analysis_frame_reader_->FrameLength();
   last_successful_frame_buffer_.reset(new uint8_t[frame_length_in_bytes]);
 
   // Set fixed properties common for all frames.
@@ -139,7 +149,8 @@
   if (config_.verbose) {
     printf("Video Processor:\n");
     printf("  #CPU cores used  : %d\n", num_cores);
-    printf("  Total # of frames: %d\n", frame_reader_->NumberOfFrames());
+    printf("  Total # of frames: %d\n",
+           analysis_frame_reader_->NumberOfFrames());
     printf("  Codec settings:\n");
     printf("    Start bitrate    : %d kbps\n",
            config_.codec_settings->startBitrate);
@@ -202,18 +213,29 @@
 }
 
 bool VideoProcessorImpl::ProcessFrame(int frame_number) {
-  RTC_DCHECK_GE(frame_number, 0);
+  RTC_CHECK_GE(frame_number, 0);
   RTC_CHECK(initialized_) << "Attempting to use uninitialized VideoProcessor";
 
-  // |prev_time_stamp_| is used for getting number of dropped frames.
-  if (frame_number == 0) {
-    prev_time_stamp_ = -1;
-  }
-
-  rtc::scoped_refptr<VideoFrameBuffer> buffer(frame_reader_->ReadFrame());
+  rtc::scoped_refptr<VideoFrameBuffer> buffer(
+      analysis_frame_reader_->ReadFrame());
   if (buffer) {
-    // Use the frame number as "timestamp" to identify frames.
-    VideoFrame source_frame(buffer, frame_number, 0, webrtc::kVideoRotation_0);
+    if (source_frame_writer_) {
+      // TODO(brandtr): Introduce temp buffer as data member, to avoid
+      // allocating for every frame.
+      size_t length = CalcBufferSize(kI420, buffer->width(), buffer->height());
+      std::unique_ptr<uint8_t[]> extracted_buffer(new uint8_t[length]);
+      int extracted_length =
+          ExtractBuffer(buffer, length, extracted_buffer.get());
+      RTC_CHECK_EQ(extracted_length, source_frame_writer_->FrameLength());
+      source_frame_writer_->WriteFrame(extracted_buffer.get());
+    }
+
+    // Use the frame number as basis for timestamp to identify frames. Let the
+    // first timestamp be non-zero, to not make the IvfFileWriter believe that
+    // we want to use capture timestamps in the IVF files.
+    VideoFrame source_frame(buffer,
+                            (frame_number + 1) * k90khzTimestampFrameDiff, 0,
+                            webrtc::kVideoRotation_0);
 
     // Ensure we have a new statistics data object we can fill.
     FrameStatistic& stat = stats_->NewFrame(frame_number);
@@ -256,30 +278,41 @@
   // time recordings should wrap the Encode call as tightly as possible.
   int64_t encode_stop_ns = rtc::TimeNanos();
 
-  // Timestamp is frame number, so this gives us #dropped frames.
+  if (encoded_frame_writer_) {
+    RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec));
+  }
+
+  // Timestamp is proportional to frame number, so this gives us number of
+  // dropped frames.
   int num_dropped_from_prev_encode =
-      encoded_image._timeStamp - prev_time_stamp_ - 1;
+      (encoded_image._timeStamp - prev_time_stamp_) / k90khzTimestampFrameDiff -
+      1;
   num_dropped_frames_ += num_dropped_from_prev_encode;
   prev_time_stamp_ = encoded_image._timeStamp;
   if (num_dropped_from_prev_encode > 0) {
     // For dropped frames, we write out the last decoded frame to avoid getting
     // out of sync for the computation of PSNR and SSIM.
     for (int i = 0; i < num_dropped_from_prev_encode; i++) {
-      frame_writer_->WriteFrame(last_successful_frame_buffer_.get());
+      RTC_CHECK(analysis_frame_writer_->WriteFrame(
+          last_successful_frame_buffer_.get()));
+      if (decoded_frame_writer_) {
+        RTC_CHECK(decoded_frame_writer_->WriteFrame(
+            last_successful_frame_buffer_.get()));
+      }
     }
   }
+
   // Frame is not dropped, so update the encoded frame size
   // (encoder callback is only called for non-zero length frames).
   encoded_frame_size_ = encoded_image._length;
   encoded_frame_type_ = encoded_image._frameType;
-  int frame_number = encoded_image._timeStamp;
-
+  int frame_number = encoded_image._timeStamp / k90khzTimestampFrameDiff - 1;
   FrameStatistic& stat = stats_->stats_[frame_number];
   stat.encode_time_in_us =
       GetElapsedTimeMicroseconds(encode_start_ns_, encode_stop_ns);
   stat.encoding_successful = true;
   stat.encoded_frame_length_in_bytes = encoded_image._length;
-  stat.frame_number = encoded_image._timeStamp;
+  stat.frame_number = frame_number;
   stat.frame_type = encoded_image._frameType;
   stat.bit_rate_in_kbps = encoded_image._length * bit_rate_factor_;
   stat.total_packets =
@@ -336,7 +369,12 @@
   if (decode_result != WEBRTC_VIDEO_CODEC_OK) {
     // Write the last successful frame the output file to avoid getting it out
     // of sync with the source file for SSIM and PSNR comparisons.
-    frame_writer_->WriteFrame(last_successful_frame_buffer_.get());
+    RTC_CHECK(analysis_frame_writer_->WriteFrame(
+        last_successful_frame_buffer_.get()));
+    if (decoded_frame_writer_) {
+      RTC_CHECK(decoded_frame_writer_->WriteFrame(
+          last_successful_frame_buffer_.get()));
+    }
   }
 
   // Save status for losses so we can inform the decoder for the next frame.
@@ -349,7 +387,7 @@
   int64_t decode_stop_ns = rtc::TimeNanos();
 
   // Report stats.
-  int frame_number = image.timestamp();
+  int frame_number = image.timestamp() / k90khzTimestampFrameDiff - 1;
   FrameStatistic& stat = stats_->stats_[frame_number];
   stat.decode_time_in_us =
       GetElapsedTimeMicroseconds(decode_start_ns_, decode_stop_ns);
@@ -383,14 +421,14 @@
         CalcBufferSize(kI420, up_image->width(), up_image->height());
     std::unique_ptr<uint8_t[]> image_buffer(new uint8_t[length]);
     int extracted_length = ExtractBuffer(up_image, length, image_buffer.get());
-    RTC_DCHECK_GT(extracted_length, 0);
+    RTC_CHECK_GT(extracted_length, 0);
     // Update our copy of the last successful frame.
     memcpy(last_successful_frame_buffer_.get(), image_buffer.get(),
            extracted_length);
-    bool write_success = frame_writer_->WriteFrame(image_buffer.get());
-    RTC_DCHECK(write_success);
-    if (!write_success) {
-      fprintf(stderr, "Failed to write frame %d to disk!", frame_number);
+
+    RTC_CHECK(analysis_frame_writer_->WriteFrame(image_buffer.get()));
+    if (decoded_frame_writer_) {
+      RTC_CHECK(decoded_frame_writer_->WriteFrame(image_buffer.get()));
     }
   } else {  // No resize.
     // Update our copy of the last successful frame.
@@ -406,14 +444,13 @@
       extracted_length =
           ExtractBuffer(image.video_frame_buffer(), length, image_buffer.get());
     }
-    RTC_DCHECK_GT(extracted_length, 0);
+    RTC_CHECK_GT(extracted_length, 0);
     memcpy(last_successful_frame_buffer_.get(), image_buffer.get(),
            extracted_length);
 
-    bool write_success = frame_writer_->WriteFrame(image_buffer.get());
-    RTC_DCHECK(write_success);
-    if (!write_success) {
-      fprintf(stderr, "Failed to write frame %d to disk!", frame_number);
+    RTC_CHECK(analysis_frame_writer_->WriteFrame(image_buffer.get()));
+    if (decoded_frame_writer_) {
+      RTC_CHECK(decoded_frame_writer_->WriteFrame(image_buffer.get()));
     }
   }
 }
diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor.h b/webrtc/modules/video_coding/codecs/test/videoprocessor.h
index de20020..c2e26b3 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor.h
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor.h
@@ -20,6 +20,7 @@
 #include "webrtc/modules/video_coding/include/video_codec_interface.h"
 #include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
 #include "webrtc/modules/video_coding/codecs/test/stats.h"
+#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
 #include "webrtc/test/testsupport/frame_reader.h"
 #include "webrtc/test/testsupport/frame_writer.h"
 
@@ -161,11 +162,14 @@
  public:
   VideoProcessorImpl(webrtc::VideoEncoder* encoder,
                      webrtc::VideoDecoder* decoder,
-                     FrameReader* frame_reader,
-                     FrameWriter* frame_writer,
+                     FrameReader* analysis_frame_reader,
+                     FrameWriter* analysis_frame_writer,
                      PacketManipulator* packet_manipulator,
                      const TestConfig& config,
-                     Stats* stats);
+                     Stats* stats,
+                     FrameWriter* source_frame_writer,
+                     IvfFileWriter* encoded_frame_writer,
+                     FrameWriter* decoded_frame_writer);
   virtual ~VideoProcessorImpl();
   bool Init() override;
   bool ProcessFrame(int frame_number) override;
@@ -248,12 +252,24 @@
   webrtc::VideoEncoder* const encoder_;
   webrtc::VideoDecoder* const decoder_;
   std::unique_ptr<VideoBitrateAllocator> bitrate_allocator_;
-  FrameReader* const frame_reader_;
-  FrameWriter* const frame_writer_;
+  // These (mandatory) file manipulators are used for, e.g., objective PSNR and
+  // SSIM calculations at the end of a test run.
+  FrameReader* const analysis_frame_reader_;
+  FrameWriter* const analysis_frame_writer_;
   PacketManipulator* const packet_manipulator_;
   const TestConfig& config_;
   Stats* stats_;
+  // These (optional) file writers are used for persistently storing the output
+  // of the coding pipeline at different stages: pre encode (source), post
+  // encode (encoded), and post decode (decoded). The purpose is to give the
+  // experimenter an option to subjectively evaluate the quality of the
+  // encoding, given the test settings. Each frame writer is enabled by being
+  // non-null.
+  FrameWriter* const source_frame_writer_;
+  IvfFileWriter* const encoded_frame_writer_;
+  FrameWriter* const decoded_frame_writer_;
 
+  // Adapters for the codec callbacks.
   std::unique_ptr<EncodedImageCallback> encode_callback_;
   std::unique_ptr<DecodedImageCallback> decode_callback_;
 
diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
index a3718a0..b818fa5 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
@@ -46,10 +46,8 @@
   // Metrics for rate control.
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 2, 60, 20, 10, 20, 0, 1);
-  ProcessFramesAndVerify(quality_metrics,
-                         rate_profile,
-                         process_settings,
-                         rc_metrics);
+  ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 #endif  // defined(WEBRTC_VIDEOPROCESSOR_H264_TESTS)
@@ -78,7 +76,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 20, 0, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP9: Run with 5% packet loss and fixed bitrate. Quality should be a bit
@@ -100,7 +98,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 20, 0, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP9: Run with no packet loss, with varying bitrate (3 rate updates):
@@ -128,7 +126,7 @@
   SetRateControlMetrics(rc_metrics, 1, 2, 0, 20, 20, 60, 0, 0);
   SetRateControlMetrics(rc_metrics, 2, 0, 0, 25, 20, 40, 0, 0);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP9: Run with no packet loss, with an update (decrease) in frame rate.
@@ -161,7 +159,7 @@
   SetRateControlMetrics(rc_metrics, 1, 10, 0, 40, 10, 30, 0, 0);
   SetRateControlMetrics(rc_metrics, 2, 5, 0, 30, 5, 20, 0, 0);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP9: Run with no packet loss and denoiser on. One key frame (first frame).
@@ -182,7 +180,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 20, 0, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // Run with no packet loss, at low bitrate.
@@ -207,7 +205,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 228, 70, 160, 15, 80, 1, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // TODO(marpan): Add temporal layer test for VP9, once changes are in
@@ -235,7 +233,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP8: Run with 5% packet loss and fixed bitrate. Quality should be a bit
@@ -257,7 +255,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP8: Run with 10% packet loss and fixed bitrate. Quality should be lower.
@@ -279,7 +277,7 @@
   RateControlMetrics rc_metrics[1];
   SetRateControlMetrics(rc_metrics, 0, 0, 40, 20, 10, 15, 0, 1);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 #endif  // !defined(WEBRTC_IOS)
@@ -325,7 +323,7 @@
   SetRateControlMetrics(rc_metrics, 1, 0, 0, 25, 20, 10, 0, 0);
   SetRateControlMetrics(rc_metrics, 2, 0, 0, 25, 15, 10, 0, 0);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP8: Run with no packet loss, with an update (decrease) in frame rate.
@@ -366,7 +364,7 @@
   SetRateControlMetrics(rc_metrics, 1, 10, 0, 25, 10, 35, 0, 0);
   SetRateControlMetrics(rc_metrics, 2, 0, 0, 20, 10, 15, 0, 0);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 
 // VP8: Run with no packet loss, with 3 temporal layers, with a rate update in
@@ -401,7 +399,7 @@
   SetRateControlMetrics(rc_metrics, 0, 0, 20, 30, 10, 10, 0, 1);
   SetRateControlMetrics(rc_metrics, 1, 0, 0, 30, 15, 10, 0, 0);
   ProcessFramesAndVerify(quality_metrics, rate_profile, process_settings,
-                         rc_metrics);
+                         rc_metrics, nullptr /* visualization_params */);
 }
 }  // namespace test
 }  // namespace webrtc
diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h
index 669ae30..ddd3438 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.h
@@ -15,6 +15,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #if defined(WEBRTC_ANDROID)
 #include "webrtc/modules/video_coding/codecs/test/android_test_initializer.h"
@@ -26,6 +27,7 @@
 #endif
 
 #include "webrtc/base/checks.h"
+#include "webrtc/base/file.h"
 #include "webrtc/media/engine/webrtcvideodecoderfactory.h"
 #include "webrtc/media/engine/webrtcvideoencoderfactory.h"
 #include "webrtc/modules/video_coding/codecs/h264/include/h264.h"
@@ -33,10 +35,10 @@
 #include "webrtc/modules/video_coding/codecs/test/videoprocessor.h"
 #include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
 #include "webrtc/modules/video_coding/codecs/vp8/include/vp8_common_types.h"
-#include "webrtc/modules/video_coding/codecs/vp8/temporal_layers.h"
 #include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h"
 #include "webrtc/modules/video_coding/include/video_codec_interface.h"
 #include "webrtc/modules/video_coding/include/video_coding.h"
+#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
 #include "webrtc/test/gtest.h"
 #include "webrtc/test/testsupport/fileutils.h"
 #include "webrtc/test/testsupport/frame_reader.h"
@@ -110,6 +112,13 @@
   int num_key_frames;
 };
 
+// Should video files be saved persistently to disk for post-run visualization?
+struct VisualizationParams {
+  bool save_source_y4m;
+  bool save_encoded_ivf;
+  bool save_decoded_y4m;
+};
+
 #if !defined(WEBRTC_IOS)
 const int kNumFramesShort = 100;
 #endif
@@ -143,7 +152,8 @@
   }
   virtual ~VideoProcessorIntegrationTest() = default;
 
-  void SetUpCodecConfig(const CodecConfigPars& process) {
+  void SetUpCodecConfig(const CodecConfigPars& process,
+                        const VisualizationParams* visualization_params) {
     if (process.hw_codec) {
 #if defined(WEBRTC_VIDEOPROCESSOR_INTEGRATIONTEST_HW_CODECS_ENABLED)
 #if defined(WEBRTC_ANDROID)
@@ -175,7 +185,7 @@
           break;
       }
 #elif defined(WEBRTC_IOS)
-      RTC_DCHECK_EQ(kVideoCodecH264, process.codec_type)
+      ASSERT_EQ(kVideoCodecH264, process.codec_type)
           << "iOS HW codecs only support H264.";
       encoder_.reset(new H264VideoToolboxEncoder(
           cricket::VideoCodec(cricket::kH264CodecName)));
@@ -184,8 +194,8 @@
       RTC_NOTREACHED() << "Only support HW codecs on Android and iOS.";
 #endif
 #endif  // WEBRTC_VIDEOPROCESSOR_INTEGRATIONTEST_HW_CODECS_ENABLED
-      RTC_DCHECK(encoder_) << "HW encoder not successfully created.";
-      RTC_DCHECK(decoder_) << "HW decoder not successfully created.";
+      RTC_CHECK(encoder_) << "HW encoder not successfully created.";
+      RTC_CHECK(decoder_) << "HW decoder not successfully created.";
     } else {
       // SW codecs.
       switch (process.codec_type) {
@@ -265,19 +275,54 @@
         RTC_NOTREACHED();
         break;
     }
-    frame_reader_.reset(new test::FrameReaderImpl(
+
+    // Create file objects for quality analysis.
+    analysis_frame_reader_.reset(new test::YuvFrameReaderImpl(
         config_.input_filename, config_.codec_settings->width,
         config_.codec_settings->height));
-    frame_writer_.reset(new test::FrameWriterImpl(
-        config_.output_filename, config_.frame_length_in_bytes));
-    RTC_CHECK(frame_reader_->Init());
-    RTC_CHECK(frame_writer_->Init());
+    analysis_frame_writer_.reset(new test::YuvFrameWriterImpl(
+        config_.output_filename, config_.codec_settings->width,
+        config_.codec_settings->height));
+    RTC_CHECK(analysis_frame_reader_->Init());
+    RTC_CHECK(analysis_frame_writer_->Init());
+
+    if (visualization_params) {
+      // clang-format off
+      const std::string output_filename_base =
+          test::OutputPath() + process.filename +
+          "_cd-" + CodecTypeToPayloadName(process.codec_type).value_or("") +
+          "_hw-" + std::to_string(process.hw_codec) +
+          "_fr-" + std::to_string(start_frame_rate_) +
+          "_br-" + std::to_string(static_cast<int>(start_bitrate_));
+      // clang-format on
+      if (visualization_params->save_source_y4m) {
+        source_frame_writer_.reset(new test::Y4mFrameWriterImpl(
+            output_filename_base + "_source.y4m", config_.codec_settings->width,
+            config_.codec_settings->height, start_frame_rate_));
+        RTC_CHECK(source_frame_writer_->Init());
+      }
+      if (visualization_params->save_encoded_ivf) {
+        rtc::File post_encode_file =
+            rtc::File::Create(output_filename_base + "_encoded.ivf");
+        encoded_frame_writer_ =
+            IvfFileWriter::Wrap(std::move(post_encode_file), 0);
+      }
+      if (visualization_params->save_decoded_y4m) {
+        decoded_frame_writer_.reset(new test::Y4mFrameWriterImpl(
+            output_filename_base + "_decoded.y4m",
+            config_.codec_settings->width, config_.codec_settings->height,
+            start_frame_rate_));
+        RTC_CHECK(decoded_frame_writer_->Init());
+      }
+    }
 
     packet_manipulator_.reset(new test::PacketManipulatorImpl(
         &packet_reader_, config_.networking_config, config_.verbose));
     processor_.reset(new test::VideoProcessorImpl(
-        encoder_.get(), decoder_.get(), frame_reader_.get(),
-        frame_writer_.get(), packet_manipulator_.get(), config_, &stats_));
+        encoder_.get(), decoder_.get(), analysis_frame_reader_.get(),
+        analysis_frame_writer_.get(), packet_manipulator_.get(), config_,
+        &stats_, source_frame_writer_.get(), encoded_frame_writer_.get(),
+        decoded_frame_writer_.get()));
     RTC_CHECK(processor_->Init());
   }
 
@@ -463,12 +508,14 @@
   void ProcessFramesAndVerify(QualityMetrics quality_metrics,
                               RateProfile rate_profile,
                               CodecConfigPars process,
-                              RateControlMetrics* rc_metrics) {
+                              RateControlMetrics* rc_metrics,
+                              const VisualizationParams* visualization_params) {
     // Codec/config settings.
     start_bitrate_ = rate_profile.target_bit_rate[0];
+    start_frame_rate_ = rate_profile.input_frame_rate[0];
     packet_loss_ = process.packet_loss;
     num_temporal_layers_ = process.num_temporal_layers;
-    SetUpCodecConfig(process);
+    SetUpCodecConfig(process, visualization_params);
     // Update the layers and the codec with the initial rates.
     bit_rate_ = rate_profile.target_bit_rate[0];
     frame_rate_ = rate_profile.input_frame_rate[0];
@@ -534,11 +581,22 @@
     EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
     EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, decoder_->Release());
 
-    // Close the files before we start using them for SSIM/PSNR calculations.
-    frame_reader_->Close();
-    frame_writer_->Close();
+    // Close the analysis files before we use them for SSIM/PSNR calculations.
+    analysis_frame_reader_->Close();
+    analysis_frame_writer_->Close();
 
-    // TODO(marpan): should compute these quality metrics per SetRates update.
+    // Close visualization files.
+    if (source_frame_writer_) {
+      source_frame_writer_->Close();
+    }
+    if (encoded_frame_writer_) {
+      encoded_frame_writer_->Close();
+    }
+    if (decoded_frame_writer_) {
+      decoded_frame_writer_->Close();
+    }
+
+    // TODO(marpan): Should compute these quality metrics per SetRates update.
     test::QualityMetricsResult psnr_result, ssim_result;
     EXPECT_EQ(0, test::I420MetricsFromFiles(config_.input_filename.c_str(),
                                             config_.output_filename.c_str(),
@@ -553,22 +611,13 @@
     EXPECT_GT(psnr_result.min, quality_metrics.minimum_min_psnr);
     EXPECT_GT(ssim_result.average, quality_metrics.minimum_avg_ssim);
     EXPECT_GT(ssim_result.min, quality_metrics.minimum_min_ssim);
+
+    // Remove analysis file.
     if (remove(config_.output_filename.c_str()) < 0) {
       fprintf(stderr, "Failed to remove temporary file!\n");
     }
   }
 
-  static void SetRateProfilePars(RateProfile* rate_profile,
-                                 int update_index,
-                                 int bit_rate,
-                                 int frame_rate,
-                                 int frame_index_rate_update) {
-    rate_profile->target_bit_rate[update_index] = bit_rate;
-    rate_profile->input_frame_rate[update_index] = frame_rate;
-    rate_profile->frame_index_rate_update[update_index] =
-        frame_index_rate_update;
-  }
-
   static void SetCodecParameters(CodecConfigPars* process_settings,
                                  VideoCodecType codec_type,
                                  bool hw_codec,
@@ -629,6 +678,17 @@
     quality_metrics->minimum_min_ssim = minimum_min_ssim;
   }
 
+  static void SetRateProfilePars(RateProfile* rate_profile,
+                                 int update_index,
+                                 int bit_rate,
+                                 int frame_rate,
+                                 int frame_index_rate_update) {
+    rate_profile->target_bit_rate[update_index] = bit_rate;
+    rate_profile->input_frame_rate[update_index] = frame_rate;
+    rate_profile->frame_index_rate_update[update_index] =
+        frame_index_rate_update;
+  }
+
   static void SetRateControlMetrics(RateControlMetrics* rc_metrics,
                                     int update_index,
                                     int max_num_dropped_frames,
@@ -650,20 +710,27 @@
     rc_metrics[update_index].num_key_frames = num_key_frames;
   }
 
+  // Codecs.
   std::unique_ptr<VideoEncoder> encoder_;
   std::unique_ptr<cricket::WebRtcVideoEncoderFactory> external_encoder_factory_;
   std::unique_ptr<VideoDecoder> decoder_;
   std::unique_ptr<cricket::WebRtcVideoDecoderFactory> external_decoder_factory_;
-  std::unique_ptr<test::FrameReader> frame_reader_;
-  std::unique_ptr<test::FrameWriter> frame_writer_;
+  VideoCodec codec_settings_;
+
+  // Helper objects.
+  std::unique_ptr<test::FrameReader> analysis_frame_reader_;
+  std::unique_ptr<test::FrameWriter> analysis_frame_writer_;
   test::PacketReader packet_reader_;
   std::unique_ptr<test::PacketManipulator> packet_manipulator_;
   test::Stats stats_;
   test::TestConfig config_;
-  VideoCodec codec_settings_;
   // Must be destroyed before |encoder_| and |decoder_|.
   std::unique_ptr<test::VideoProcessor> processor_;
-  TemporalLayersFactory tl_factory_;
+
+  // Visualization objects.
+  std::unique_ptr<test::FrameWriter> source_frame_writer_;
+  std::unique_ptr<IvfFileWriter> encoded_frame_writer_;
+  std::unique_ptr<test::FrameWriter> decoded_frame_writer_;
 
   // Quantities defined/updated for every encoder rate update.
   // Some quantities defined per temporal layer (at most 3 layers in this test).
@@ -688,6 +755,7 @@
   float sum_key_frame_size_mismatch_;
   int num_key_frames_;
   float start_bitrate_;
+  int start_frame_rate_;
 
   // Codec and network settings.
   float packet_loss_;
diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc
index 9846fbc..efc60c1 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_unittest.cc
@@ -69,7 +69,9 @@
   ExpectInit();
   VideoProcessorImpl video_processor(
       &encoder_mock_, &decoder_mock_, &frame_reader_mock_, &frame_writer_mock_,
-      &packet_manipulator_mock_, config_, &stats_);
+      &packet_manipulator_mock_, config_, &stats_,
+      nullptr /* source_frame_writer */, nullptr /* encoded_frame_writer */,
+      nullptr /* decoded_frame_writer */);
   ASSERT_TRUE(video_processor.Init());
 }
 
@@ -82,7 +84,9 @@
   // be more than initialized...
   VideoProcessorImpl video_processor(
       &encoder_mock_, &decoder_mock_, &frame_reader_mock_, &frame_writer_mock_,
-      &packet_manipulator_mock_, config_, &stats_);
+      &packet_manipulator_mock_, config_, &stats_,
+      nullptr /* source_frame_writer */, nullptr /* encoded_frame_writer */,
+      nullptr /* decoded_frame_writer */);
   ASSERT_TRUE(video_processor.Init());
   video_processor.ProcessFrame(0);
 }
diff --git a/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc b/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc
index f1b5807..5dd3ce7 100644
--- a/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc
+++ b/webrtc/modules/video_coding/codecs/tools/video_quality_measurement.cc
@@ -490,11 +490,12 @@
   webrtc::VP8Encoder* encoder = webrtc::VP8Encoder::Create();
   webrtc::VP8Decoder* decoder = webrtc::VP8Decoder::Create();
   webrtc::test::Stats stats;
-  webrtc::test::FrameReaderImpl frame_reader(config.input_filename,
-                                             config.codec_settings->width,
-                                             config.codec_settings->height);
-  webrtc::test::FrameWriterImpl frame_writer(config.output_filename,
-                                             config.frame_length_in_bytes);
+  webrtc::test::YuvFrameReaderImpl frame_reader(config.input_filename,
+                                                config.codec_settings->width,
+                                                config.codec_settings->height);
+  webrtc::test::YuvFrameWriterImpl frame_writer(config.output_filename,
+                                                config.codec_settings->width,
+                                                config.codec_settings->height);
   frame_reader.Init();
   frame_writer.Init();
   webrtc::test::PacketReader packet_reader;
@@ -507,9 +508,11 @@
     packet_manipulator.InitializeRandomSeed(time(NULL));
   }
   webrtc::test::VideoProcessor* processor =
-      new webrtc::test::VideoProcessorImpl(encoder, decoder, &frame_reader,
-                                           &frame_writer, &packet_manipulator,
-                                           config, &stats);
+      new webrtc::test::VideoProcessorImpl(
+          encoder, decoder, &frame_reader, &frame_writer, &packet_manipulator,
+          config, &stats, nullptr /* source_frame_writer */,
+          nullptr /* encoded_frame_writer */,
+          nullptr /* decoded_frame_writer */);
   processor->Init();
 
   int frame_number = 0;
diff --git a/webrtc/test/BUILD.gn b/webrtc/test/BUILD.gn
index a4c0185..07ffbee 100644
--- a/webrtc/test/BUILD.gn
+++ b/webrtc/test/BUILD.gn
@@ -129,14 +129,15 @@
   testonly = true
 
   sources = [
-    "testsupport/frame_reader.cc",
     "testsupport/frame_reader.h",
-    "testsupport/frame_writer.cc",
     "testsupport/frame_writer.h",
     "testsupport/metrics/video_metrics.cc",
     "testsupport/metrics/video_metrics.h",
     "testsupport/mock/mock_frame_reader.h",
     "testsupport/mock/mock_frame_writer.h",
+    "testsupport/y4m_frame_writer.cc",
+    "testsupport/yuv_frame_reader.cc",
+    "testsupport/yuv_frame_writer.cc",
   ]
 
   deps = [
@@ -259,12 +260,13 @@
     "rtp_file_reader_unittest.cc",
     "rtp_file_writer_unittest.cc",
     "testsupport/always_passing_unittest.cc",
-    "testsupport/frame_reader_unittest.cc",
-    "testsupport/frame_writer_unittest.cc",
     "testsupport/isolated_output_unittest.cc",
     "testsupport/metrics/video_metrics_unittest.cc",
     "testsupport/packet_reader_unittest.cc",
     "testsupport/perf_test_unittest.cc",
+    "testsupport/y4m_frame_writer_unittest.cc",
+    "testsupport/yuv_frame_reader_unittest.cc",
+    "testsupport/yuv_frame_writer_unittest.cc",
   ]
 
   # TODO(jschuh): Bug 1348: fix this warning.
diff --git a/webrtc/test/testsupport/frame_reader.h b/webrtc/test/testsupport/frame_reader.h
index 13800cd..94dd78b 100644
--- a/webrtc/test/testsupport/frame_reader.h
+++ b/webrtc/test/testsupport/frame_reader.h
@@ -46,14 +46,14 @@
   virtual int NumberOfFrames() = 0;
 };
 
-class FrameReaderImpl : public FrameReader {
+class YuvFrameReaderImpl : public FrameReader {
  public:
   // Creates a file handler. The input file is assumed to exist and be readable.
   // Parameters:
   //   input_filename          The file to read from.
   //   width, height           Size of each frame to read.
-  FrameReaderImpl(std::string input_filename, int width, int height);
-  ~FrameReaderImpl() override;
+  YuvFrameReaderImpl(std::string input_filename, int width, int height);
+  ~YuvFrameReaderImpl() override;
   bool Init() override;
   rtc::scoped_refptr<I420Buffer> ReadFrame() override;
   void Close() override;
@@ -61,10 +61,10 @@
   int NumberOfFrames() override;
 
  private:
-  std::string input_filename_;
+  const std::string input_filename_;
   size_t frame_length_in_bytes_;
-  int width_;
-  int height_;
+  const int width_;
+  const int height_;
   int number_of_frames_;
   FILE* input_file_;
 };
diff --git a/webrtc/test/testsupport/frame_reader_unittest.cc b/webrtc/test/testsupport/frame_reader_unittest.cc
deleted file mode 100644
index 58a3245..0000000
--- a/webrtc/test/testsupport/frame_reader_unittest.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  Copyright (c) 2011 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/test/testsupport/frame_reader.h"
-
-#include "webrtc/api/video/i420_buffer.h"
-#include "webrtc/test/gtest.h"
-#include "webrtc/test/testsupport/fileutils.h"
-
-namespace webrtc {
-namespace test {
-
-const std::string kInputFileContents = "baz";
-const size_t kFrameLength = 3;
-
-class FrameReaderTest: public testing::Test {
- protected:
-  FrameReaderTest() {}
-  virtual ~FrameReaderTest() {}
-  void SetUp() {
-    // Create a dummy input file.
-    temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
-                                                "frame_reader_unittest");
-    FILE* dummy = fopen(temp_filename_.c_str(), "wb");
-    fprintf(dummy, "%s", kInputFileContents.c_str());
-    fclose(dummy);
-
-    frame_reader_ = new FrameReaderImpl(temp_filename_, 1, 1);
-    ASSERT_TRUE(frame_reader_->Init());
-  }
-  void TearDown() {
-    delete frame_reader_;
-    // Cleanup the dummy input file.
-    remove(temp_filename_.c_str());
-  }
-  FrameReader* frame_reader_;
-  std::string temp_filename_;
-};
-
-TEST_F(FrameReaderTest, InitSuccess) {
-  FrameReaderImpl frame_reader(temp_filename_, 1, 1);
-  ASSERT_TRUE(frame_reader.Init());
-  ASSERT_EQ(kFrameLength, frame_reader.FrameLength());
-  ASSERT_EQ(1, frame_reader.NumberOfFrames());
-}
-
-TEST_F(FrameReaderTest, ReadFrame) {
-  rtc::scoped_refptr<VideoFrameBuffer> buffer;
-  buffer = frame_reader_->ReadFrame();
-  ASSERT_TRUE(buffer);
-  ASSERT_EQ(kInputFileContents[0], buffer->DataY()[0]);
-  ASSERT_EQ(kInputFileContents[1], buffer->DataU()[0]);
-  ASSERT_EQ(kInputFileContents[2], buffer->DataV()[0]);
-  ASSERT_FALSE(frame_reader_->ReadFrame());  // End of file
-}
-
-TEST_F(FrameReaderTest, ReadFrameUninitialized) {
-  FrameReaderImpl file_reader(temp_filename_, 1, 1);
-  ASSERT_FALSE(file_reader.ReadFrame());
-}
-
-}  // namespace test
-}  // namespace webrtc
diff --git a/webrtc/test/testsupport/frame_writer.cc b/webrtc/test/testsupport/frame_writer.cc
deleted file mode 100644
index 1b9e8a8..0000000
--- a/webrtc/test/testsupport/frame_writer.cc
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  Copyright (c) 2011 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/test/testsupport/frame_writer.h"
-
-#include <assert.h>
-
-namespace webrtc {
-namespace test {
-
-FrameWriterImpl::FrameWriterImpl(std::string output_filename,
-                                 size_t frame_length_in_bytes)
-    : output_filename_(output_filename),
-      frame_length_in_bytes_(frame_length_in_bytes),
-      output_file_(NULL) {
-}
-
-FrameWriterImpl::~FrameWriterImpl() {
-  Close();
-}
-
-bool FrameWriterImpl::Init() {
-  if (frame_length_in_bytes_ <= 0) {
-    fprintf(stderr, "Frame length must be >0, was %zu\n",
-            frame_length_in_bytes_);
-    return false;
-  }
-  output_file_ = fopen(output_filename_.c_str(), "wb");
-  if (output_file_ == NULL) {
-    fprintf(stderr, "Couldn't open output file for writing: %s\n",
-            output_filename_.c_str());
-    return false;
-  }
-  return true;
-}
-
-void FrameWriterImpl::Close() {
-  if (output_file_ != NULL) {
-    fclose(output_file_);
-    output_file_ = NULL;
-  }
-}
-
-size_t FrameWriterImpl::FrameLength() { return frame_length_in_bytes_; }
-
-bool FrameWriterImpl::WriteFrame(uint8_t* frame_buffer) {
-  assert(frame_buffer);
-  if (output_file_ == NULL) {
-    fprintf(stderr, "FrameWriter is not initialized (output file is NULL)\n");
-    return false;
-  }
-  size_t bytes_written = fwrite(frame_buffer, 1, frame_length_in_bytes_,
-                                output_file_);
-  if (bytes_written != frame_length_in_bytes_) {
-    fprintf(stderr, "Failed to write %zu bytes to file %s\n",
-            frame_length_in_bytes_, output_filename_.c_str());
-    return false;
-  }
-  return true;
-}
-
-}  // namespace test
-}  // namespace webrtc
diff --git a/webrtc/test/testsupport/frame_writer.h b/webrtc/test/testsupport/frame_writer.h
index 8a6b1c2..7629849 100644
--- a/webrtc/test/testsupport/frame_writer.h
+++ b/webrtc/test/testsupport/frame_writer.h
@@ -42,28 +42,46 @@
   virtual size_t FrameLength() = 0;
 };
 
-class FrameWriterImpl : public FrameWriter {
+// Writes raw I420 frames in sequence.
+class YuvFrameWriterImpl : public FrameWriter {
  public:
   // Creates a file handler. The input file is assumed to exist and be readable
   // and the output file must be writable.
   // Parameters:
   //   output_filename         The file to write. Will be overwritten if already
   //                           existing.
-  //   frame_length_in_bytes   The size of each frame.
-  //                           For YUV: 3*width*height/2
-  FrameWriterImpl(std::string output_filename, size_t frame_length_in_bytes);
-  ~FrameWriterImpl() override;
+  //   width, height           Size of each frame to read.
+  YuvFrameWriterImpl(std::string output_filename, int width, int height);
+  ~YuvFrameWriterImpl() override;
   bool Init() override;
   bool WriteFrame(uint8_t* frame_buffer) override;
   void Close() override;
   size_t FrameLength() override;
 
- private:
-  std::string output_filename_;
+ protected:
+  const std::string output_filename_;
   size_t frame_length_in_bytes_;
+  const int width_;
+  const int height_;
   FILE* output_file_;
 };
 
+// Writes raw I420 frames in sequence, but with Y4M file and frame headers for
+// more convenient playback in external media players.
+class Y4mFrameWriterImpl : public YuvFrameWriterImpl {
+ public:
+  Y4mFrameWriterImpl(std::string output_filename,
+                     int width,
+                     int height,
+                     int frame_rate);
+  ~Y4mFrameWriterImpl() override;
+  bool Init() override;
+  bool WriteFrame(uint8_t* frame_buffer) override;
+
+ private:
+  const int frame_rate_;
+};
+
 }  // namespace test
 }  // namespace webrtc
 
diff --git a/webrtc/test/testsupport/frame_writer_unittest.cc b/webrtc/test/testsupport/frame_writer_unittest.cc
deleted file mode 100644
index 59173bd..0000000
--- a/webrtc/test/testsupport/frame_writer_unittest.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- *  Copyright (c) 2011 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/test/testsupport/frame_writer.h"
-
-#include "webrtc/test/gtest.h"
-#include "webrtc/test/testsupport/fileutils.h"
-
-namespace webrtc {
-namespace test {
-
-const size_t kFrameLength = 1000;
-
-class FrameWriterTest: public testing::Test {
- protected:
-  FrameWriterTest() {}
-  virtual ~FrameWriterTest() {}
-  void SetUp() {
-    temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
-                                                "frame_writer_unittest");
-    frame_writer_ = new FrameWriterImpl(temp_filename_, kFrameLength);
-    ASSERT_TRUE(frame_writer_->Init());
-  }
-  void TearDown() {
-    delete frame_writer_;
-    // Cleanup the temporary file.
-    remove(temp_filename_.c_str());
-  }
-  FrameWriter* frame_writer_;
-  std::string temp_filename_;
-};
-
-TEST_F(FrameWriterTest, InitSuccess) {
-  FrameWriterImpl frame_writer(temp_filename_, kFrameLength);
-  ASSERT_TRUE(frame_writer.Init());
-  ASSERT_EQ(kFrameLength, frame_writer.FrameLength());
-}
-
-TEST_F(FrameWriterTest, WriteFrame) {
-  uint8_t buffer[kFrameLength];
-  memset(buffer, 9, kFrameLength);  // Write lots of 9s to the buffer
-  bool result = frame_writer_->WriteFrame(buffer);
-  ASSERT_TRUE(result);  // success
-  // Close the file and verify the size.
-  frame_writer_->Close();
-  ASSERT_EQ(kFrameLength, GetFileSize(temp_filename_));
-}
-
-TEST_F(FrameWriterTest, WriteFrameUninitialized) {
-  uint8_t buffer[3];
-  FrameWriterImpl frame_writer(temp_filename_, kFrameLength);
-  ASSERT_FALSE(frame_writer.WriteFrame(buffer));
-}
-
-}  // namespace test
-}  // namespace webrtc
diff --git a/webrtc/test/testsupport/y4m_frame_writer.cc b/webrtc/test/testsupport/y4m_frame_writer.cc
new file mode 100644
index 0000000..28fb4b0
--- /dev/null
+++ b/webrtc/test/testsupport/y4m_frame_writer.cc
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (c) 2017 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/base/checks.h"
+#include "webrtc/test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+Y4mFrameWriterImpl::Y4mFrameWriterImpl(std::string output_filename,
+                                       int width,
+                                       int height,
+                                       int frame_rate)
+    : YuvFrameWriterImpl(output_filename, width, height),
+      frame_rate_(frame_rate) {}
+
+Y4mFrameWriterImpl::~Y4mFrameWriterImpl() = default;
+
+bool Y4mFrameWriterImpl::Init() {
+  if (!YuvFrameWriterImpl::Init()) {
+    return false;
+  }
+  int bytes_written = fprintf(output_file_, "YUV4MPEG2 W%d H%d F%d:1 C420\n",
+                              width_, height_, frame_rate_);
+  if (bytes_written < 0) {
+    fprintf(stderr, "Failed to write Y4M file header to file %s\n",
+            output_filename_.c_str());
+    return false;
+  }
+  return true;
+}
+
+bool Y4mFrameWriterImpl::WriteFrame(uint8_t* frame_buffer) {
+  if (output_file_ == nullptr) {
+    fprintf(stderr,
+            "Y4mFrameWriterImpl is not initialized (output file is NULL)\n");
+    return false;
+  }
+  int bytes_written = fprintf(output_file_, "FRAME\n");
+  if (bytes_written < 0) {
+    fprintf(stderr, "Failed to write Y4M frame header to file %s\n",
+            output_filename_.c_str());
+    return false;
+  }
+  return YuvFrameWriterImpl::WriteFrame(frame_buffer);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/test/testsupport/y4m_frame_writer_unittest.cc b/webrtc/test/testsupport/y4m_frame_writer_unittest.cc
new file mode 100644
index 0000000..a4e4172
--- /dev/null
+++ b/webrtc/test/testsupport/y4m_frame_writer_unittest.cc
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (c) 2017 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 <memory>
+#include <string>
+
+#include "webrtc/test/gtest.h"
+#include "webrtc/test/testsupport/fileutils.h"
+#include "webrtc/test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+const size_t kFrameWidth = 50;
+const size_t kFrameHeight = 20;
+const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2;  // I420.
+const size_t kFrameRate = 30;
+
+const std::string kFileHeader = "YUV4MPEG2 W50 H20 F30:1 C420\n";
+const std::string kFrameHeader = "FRAME\n";
+}  // namespace
+
+class Y4mFrameWriterTest : public testing::Test {
+ protected:
+  Y4mFrameWriterTest() = default;
+  ~Y4mFrameWriterTest() override = default;
+
+  void SetUp() override {
+    temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+                                                "y4m_frame_writer_unittest");
+    frame_writer_.reset(new Y4mFrameWriterImpl(temp_filename_, kFrameWidth,
+                                               kFrameHeight, kFrameRate));
+    ASSERT_TRUE(frame_writer_->Init());
+  }
+
+  void TearDown() override { remove(temp_filename_.c_str()); }
+
+  std::unique_ptr<FrameWriter> frame_writer_;
+  std::string temp_filename_;
+};
+
+TEST_F(Y4mFrameWriterTest, InitSuccess) {}
+
+TEST_F(Y4mFrameWriterTest, FrameLength) {
+  EXPECT_EQ(kFrameLength, frame_writer_->FrameLength());
+}
+
+TEST_F(Y4mFrameWriterTest, WriteFrame) {
+  uint8_t buffer[kFrameLength];
+  memset(buffer, 9, kFrameLength);  // Write lots of 9s to the buffer.
+  bool result = frame_writer_->WriteFrame(buffer);
+  ASSERT_TRUE(result);
+  result = frame_writer_->WriteFrame(buffer);
+  ASSERT_TRUE(result);
+
+  frame_writer_->Close();
+  EXPECT_EQ(kFileHeader.size() + 2 * kFrameHeader.size() + 2 * kFrameLength,
+            GetFileSize(temp_filename_));
+}
+
+TEST_F(Y4mFrameWriterTest, WriteFrameUninitialized) {
+  uint8_t buffer[kFrameLength];
+  Y4mFrameWriterImpl frame_writer(temp_filename_, kFrameWidth, kFrameHeight,
+                                  kFrameRate);
+  EXPECT_FALSE(frame_writer.WriteFrame(buffer));
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/test/testsupport/frame_reader.cc b/webrtc/test/testsupport/yuv_frame_reader.cc
similarity index 61%
rename from webrtc/test/testsupport/frame_reader.cc
rename to webrtc/test/testsupport/yuv_frame_reader.cc
index 2593afd..3fe481e 100644
--- a/webrtc/test/testsupport/frame_reader.cc
+++ b/webrtc/test/testsupport/yuv_frame_reader.cc
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
+ *  Copyright (c) 2017 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
@@ -10,8 +10,6 @@
 
 #include "webrtc/test/testsupport/frame_reader.h"
 
-#include <assert.h>
-
 #include "webrtc/api/video/i420_buffer.h"
 #include "webrtc/test/frame_utils.h"
 #include "webrtc/test/testsupport/fileutils.h"
@@ -19,28 +17,31 @@
 namespace webrtc {
 namespace test {
 
-FrameReaderImpl::FrameReaderImpl(std::string input_filename,
-                                 int width, int height)
+YuvFrameReaderImpl::YuvFrameReaderImpl(std::string input_filename,
+                                       int width,
+                                       int height)
     : input_filename_(input_filename),
-      width_(width), height_(height),
-      input_file_(NULL) {
-}
+      frame_length_in_bytes_(0),
+      width_(width),
+      height_(height),
+      number_of_frames_(-1),
+      input_file_(nullptr) {}
 
-FrameReaderImpl::~FrameReaderImpl() {
+YuvFrameReaderImpl::~YuvFrameReaderImpl() {
   Close();
 }
 
-bool FrameReaderImpl::Init() {
+bool YuvFrameReaderImpl::Init() {
   if (width_ <= 0 || height_ <= 0) {
-    fprintf(stderr, "Frame width and height must be >0, was %d x %d\n",
-            width_, height_);
+    fprintf(stderr, "Frame width and height must be >0, was %d x %d\n", width_,
+            height_);
     return false;
   }
   frame_length_in_bytes_ =
       width_ * height_ + 2 * ((width_ + 1) / 2) * ((height_ + 1) / 2);
 
   input_file_ = fopen(input_filename_.c_str(), "rb");
-  if (input_file_ == NULL) {
+  if (input_file_ == nullptr) {
     fprintf(stderr, "Couldn't open input file for reading: %s\n",
             input_filename_.c_str());
     return false;
@@ -51,21 +52,15 @@
     fprintf(stderr, "Found empty file: %s\n", input_filename_.c_str());
     return false;
   }
-  number_of_frames_ = static_cast<int>(source_file_size /
-                                       frame_length_in_bytes_);
+  number_of_frames_ =
+      static_cast<int>(source_file_size / frame_length_in_bytes_);
   return true;
 }
 
-void FrameReaderImpl::Close() {
-  if (input_file_ != NULL) {
-    fclose(input_file_);
-    input_file_ = NULL;
-  }
-}
-
-rtc::scoped_refptr<I420Buffer> FrameReaderImpl::ReadFrame() {
-  if (input_file_ == NULL) {
-    fprintf(stderr, "FrameReader is not initialized (input file is NULL)\n");
+rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::ReadFrame() {
+  if (input_file_ == nullptr) {
+    fprintf(stderr,
+            "YuvFrameReaderImpl is not initialized (input file is NULL)\n");
     return nullptr;
   }
   rtc::scoped_refptr<I420Buffer> buffer(
@@ -77,8 +72,20 @@
   return buffer;
 }
 
-size_t FrameReaderImpl::FrameLength() { return frame_length_in_bytes_; }
-int FrameReaderImpl::NumberOfFrames() { return number_of_frames_; }
+void YuvFrameReaderImpl::Close() {
+  if (input_file_ != nullptr) {
+    fclose(input_file_);
+    input_file_ = nullptr;
+  }
+}
+
+size_t YuvFrameReaderImpl::FrameLength() {
+  return frame_length_in_bytes_;
+}
+
+int YuvFrameReaderImpl::NumberOfFrames() {
+  return number_of_frames_;
+}
 
 }  // namespace test
 }  // namespace webrtc
diff --git a/webrtc/test/testsupport/yuv_frame_reader_unittest.cc b/webrtc/test/testsupport/yuv_frame_reader_unittest.cc
new file mode 100644
index 0000000..9590814
--- /dev/null
+++ b/webrtc/test/testsupport/yuv_frame_reader_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ *  Copyright (c) 2017 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 <memory>
+#include <string>
+
+#include "webrtc/api/video/i420_buffer.h"
+#include "webrtc/test/gtest.h"
+#include "webrtc/test/testsupport/fileutils.h"
+#include "webrtc/test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+const std::string kInputFileContents = "bazouk";
+
+const size_t kFrameWidth = 2;
+const size_t kFrameHeight = 2;
+const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2;  // I420.
+}  // namespace
+
+class YuvFrameReaderTest : public testing::Test {
+ protected:
+  YuvFrameReaderTest() = default;
+  ~YuvFrameReaderTest() override = default;
+
+  void SetUp() override {
+    temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+                                                "yuv_frame_reader_unittest");
+    FILE* dummy = fopen(temp_filename_.c_str(), "wb");
+    fprintf(dummy, "%s", kInputFileContents.c_str());
+    fclose(dummy);
+
+    frame_reader_.reset(
+        new YuvFrameReaderImpl(temp_filename_, kFrameWidth, kFrameHeight));
+    ASSERT_TRUE(frame_reader_->Init());
+  }
+
+  void TearDown() override { remove(temp_filename_.c_str()); }
+
+  std::unique_ptr<FrameReader> frame_reader_;
+  std::string temp_filename_;
+};
+
+TEST_F(YuvFrameReaderTest, InitSuccess) {}
+
+TEST_F(YuvFrameReaderTest, FrameLength) {
+  EXPECT_EQ(kFrameLength, frame_reader_->FrameLength());
+}
+
+TEST_F(YuvFrameReaderTest, NumberOfFrames) {
+  EXPECT_EQ(1, frame_reader_->NumberOfFrames());
+}
+
+TEST_F(YuvFrameReaderTest, ReadFrame) {
+  rtc::scoped_refptr<VideoFrameBuffer> buffer;
+  buffer = frame_reader_->ReadFrame();
+  ASSERT_TRUE(buffer);
+  // Expect I420 packed as YUV.
+  EXPECT_EQ(kInputFileContents[0], buffer->DataY()[0]);
+  EXPECT_EQ(kInputFileContents[1], buffer->DataY()[1]);
+  EXPECT_EQ(kInputFileContents[2], buffer->DataY()[2]);
+  EXPECT_EQ(kInputFileContents[3], buffer->DataY()[3]);
+  EXPECT_EQ(kInputFileContents[4], buffer->DataU()[0]);
+  EXPECT_EQ(kInputFileContents[5], buffer->DataV()[0]);
+  EXPECT_FALSE(frame_reader_->ReadFrame());  // End of file.
+}
+
+TEST_F(YuvFrameReaderTest, ReadFrameUninitialized) {
+  YuvFrameReaderImpl file_reader(temp_filename_, kFrameWidth, kFrameHeight);
+  EXPECT_FALSE(file_reader.ReadFrame());
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/test/testsupport/yuv_frame_writer.cc b/webrtc/test/testsupport/yuv_frame_writer.cc
new file mode 100644
index 0000000..3c00761
--- /dev/null
+++ b/webrtc/test/testsupport/yuv_frame_writer.cc
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (c) 2017 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/base/checks.h"
+#include "webrtc/test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+YuvFrameWriterImpl::YuvFrameWriterImpl(std::string output_filename,
+                                       int width,
+                                       int height)
+    : output_filename_(output_filename),
+      frame_length_in_bytes_(0),
+      width_(width),
+      height_(height),
+      output_file_(nullptr) {}
+
+YuvFrameWriterImpl::~YuvFrameWriterImpl() {
+  Close();
+}
+
+bool YuvFrameWriterImpl::Init() {
+  if (width_ <= 0 || height_ <= 0) {
+    fprintf(stderr, "Frame width and height must be >0, was %d x %d\n", width_,
+            height_);
+    return false;
+  }
+  frame_length_in_bytes_ =
+      width_ * height_ + 2 * ((width_ + 1) / 2) * ((height_ + 1) / 2);
+
+  output_file_ = fopen(output_filename_.c_str(), "wb");
+  if (output_file_ == nullptr) {
+    fprintf(stderr, "Couldn't open output file for writing: %s\n",
+            output_filename_.c_str());
+    return false;
+  }
+  return true;
+}
+
+bool YuvFrameWriterImpl::WriteFrame(uint8_t* frame_buffer) {
+  RTC_DCHECK(frame_buffer);
+  if (output_file_ == nullptr) {
+    fprintf(stderr,
+            "YuvFrameWriterImpl is not initialized (output file is NULL)\n");
+    return false;
+  }
+  size_t bytes_written =
+      fwrite(frame_buffer, 1, frame_length_in_bytes_, output_file_);
+  if (bytes_written != frame_length_in_bytes_) {
+    fprintf(stderr, "Failed to write %zu bytes to file %s\n",
+            frame_length_in_bytes_, output_filename_.c_str());
+    return false;
+  }
+  return true;
+}
+
+void YuvFrameWriterImpl::Close() {
+  if (output_file_ != nullptr) {
+    fclose(output_file_);
+    output_file_ = nullptr;
+  }
+}
+
+size_t YuvFrameWriterImpl::FrameLength() {
+  return frame_length_in_bytes_;
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/test/testsupport/yuv_frame_writer_unittest.cc b/webrtc/test/testsupport/yuv_frame_writer_unittest.cc
new file mode 100644
index 0000000..5e3cc5c
--- /dev/null
+++ b/webrtc/test/testsupport/yuv_frame_writer_unittest.cc
@@ -0,0 +1,68 @@
+/*
+ *  Copyright (c) 2017 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 <memory>
+
+#include "webrtc/test/gtest.h"
+#include "webrtc/test/testsupport/fileutils.h"
+#include "webrtc/test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+const size_t kFrameWidth = 50;
+const size_t kFrameHeight = 20;
+const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2;  // I420.
+}  // namespace
+
+class YuvFrameWriterTest : public testing::Test {
+ protected:
+  YuvFrameWriterTest() = default;
+  ~YuvFrameWriterTest() override = default;
+
+  void SetUp() override {
+    temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+                                                "yuv_frame_writer_unittest");
+    frame_writer_.reset(
+        new YuvFrameWriterImpl(temp_filename_, kFrameWidth, kFrameHeight));
+    ASSERT_TRUE(frame_writer_->Init());
+  }
+
+  void TearDown() override { remove(temp_filename_.c_str()); }
+
+  std::unique_ptr<FrameWriter> frame_writer_;
+  std::string temp_filename_;
+};
+
+TEST_F(YuvFrameWriterTest, InitSuccess) {}
+
+TEST_F(YuvFrameWriterTest, FrameLength) {
+  EXPECT_EQ(kFrameLength, frame_writer_->FrameLength());
+}
+
+TEST_F(YuvFrameWriterTest, WriteFrame) {
+  uint8_t buffer[kFrameLength];
+  memset(buffer, 9, kFrameLength);  // Write lots of 9s to the buffer.
+  bool result = frame_writer_->WriteFrame(buffer);
+  ASSERT_TRUE(result);
+
+  frame_writer_->Close();
+  EXPECT_EQ(kFrameLength, GetFileSize(temp_filename_));
+}
+
+TEST_F(YuvFrameWriterTest, WriteFrameUninitialized) {
+  uint8_t buffer[kFrameLength];
+  YuvFrameWriterImpl frame_writer(temp_filename_, kFrameWidth, kFrameHeight);
+  EXPECT_FALSE(frame_writer.WriteFrame(buffer));
+}
+
+}  // namespace test
+}  // namespace webrtc