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}
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 f265230..655aa42 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};
@@ -26,6 +27,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
 
@@ -74,7 +81,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 2acc848..b98f149 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.
@@ -141,7 +151,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);
@@ -207,15 +218,26 @@
   RTC_DCHECK_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_DCHECK_GT(extracted_length, 0);
+      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);
@@ -258,30 +280,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_DCHECK(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_DCHECK(analysis_frame_writer_->WriteFrame(
+          last_successful_frame_buffer_.get()));
+      if (decoded_frame_writer_) {
+        RTC_DCHECK(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 =
@@ -338,7 +371,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_DCHECK(analysis_frame_writer_->WriteFrame(
+        last_successful_frame_buffer_.get()));
+    if (decoded_frame_writer_) {
+      RTC_DCHECK(decoded_frame_writer_->WriteFrame(
+          last_successful_frame_buffer_.get()));
+    }
   }
 
   // Save status for losses so we can inform the decoder for the next frame.
@@ -351,7 +389,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);
@@ -389,10 +427,10 @@
     // 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_DCHECK(analysis_frame_writer_->WriteFrame(image_buffer.get()));
+    if (decoded_frame_writer_) {
+      RTC_DCHECK(decoded_frame_writer_->WriteFrame(image_buffer.get()));
     }
   } else {  // No resize.
     // Update our copy of the last successful frame.
@@ -412,10 +450,9 @@
     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_DCHECK(analysis_frame_writer_->WriteFrame(image_buffer.get()));
+    if (decoded_frame_writer_) {
+      RTC_DCHECK(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 cb2315d..7445c92 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
@@ -43,10 +43,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)
@@ -75,7 +73,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
@@ -97,7 +95,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):
@@ -125,7 +123,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.
@@ -158,7 +156,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).
@@ -179,7 +177,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.
@@ -204,7 +202,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
@@ -232,7 +230,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
@@ -254,7 +252,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.
@@ -276,7 +274,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)
@@ -322,7 +320,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.
@@ -363,7 +361,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
@@ -398,7 +396,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 e1c085e..ea07490 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"
@@ -109,6 +111,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
@@ -145,7 +154,8 @@
   void SetUpCodecConfig(const std::string& filename,
                         int width,
                         int height,
-                        bool verbose_logging) {
+                        bool verbose_logging,
+                        const VisualizationParams* visualization_params) {
     if (hw_codec_) {
 #if defined(WEBRTC_VIDEOPROCESSOR_INTEGRATIONTEST_HW_CODECS_ENABLED)
 #if defined(WEBRTC_ANDROID)
@@ -248,19 +258,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() + filename +
+          "_cd-" + CodecTypeToPayloadName(codec_type_).value_or("") +
+          "_hw-" + std::to_string(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());
   }
 
@@ -446,11 +491,13 @@
   void ProcessFramesAndVerify(QualityMetrics quality_metrics,
                               RateProfile rate_profile,
                               CodecConfigPars process,
-                              RateControlMetrics* rc_metrics) {
+                              RateControlMetrics* rc_metrics,
+                              const VisualizationParams* visualization_params) {
     // Codec/config settings.
     codec_type_ = process.codec_type;
     hw_codec_ = process.hw_codec;
     start_bitrate_ = rate_profile.target_bit_rate[0];
+    start_frame_rate_ = rate_profile.input_frame_rate[0];
     packet_loss_ = process.packet_loss;
     key_frame_interval_ = process.key_frame_interval;
     num_temporal_layers_ = process.num_temporal_layers;
@@ -459,7 +506,8 @@
     frame_dropper_on_ = process.frame_dropper_on;
     spatial_resize_on_ = process.spatial_resize_on;
     SetUpCodecConfig(process.filename, process.width, process.height,
-                     process.verbose_logging);
+                     process.verbose_logging, 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];
@@ -522,20 +570,31 @@
     EXPECT_EQ(num_frames + 1, static_cast<int>(stats_.stats_.size()));
 
     // Release encoder and decoder to make sure they have finished processing:
-    EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
-    EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, decoder_->Release());
+    RTC_DCHECK_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
+    RTC_DCHECK_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(),
-                                            config_.codec_settings->width,
-                                            config_.codec_settings->height,
-                                            &psnr_result, &ssim_result));
+    RTC_DCHECK_EQ(0, test::I420MetricsFromFiles(config_.input_filename.c_str(),
+                                                config_.output_filename.c_str(),
+                                                config_.codec_settings->width,
+                                                config_.codec_settings->height,
+                                                &psnr_result, &ssim_result));
     printf("PSNR avg: %f, min: %f\nSSIM avg: %f, min: %f\n",
            psnr_result.average, psnr_result.min, ssim_result.average,
            ssim_result.min);
@@ -544,22 +603,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,
@@ -617,6 +667,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,
@@ -638,20 +699,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).
@@ -676,6 +744,7 @@
   float sum_key_frame_size_mismatch_;
   int num_key_frames_;
   float start_bitrate_;
+  int start_frame_rate_;
 
   // Codec and network settings.
   VideoCodecType codec_type_;
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