On-fly calculation of quality metrics.

Calculation of quality metrics required writing of decoded video
to file. There were two drawbacks with that approach. First, frame
drops significantly affected metrics because comparison was done
against the last decoded frame. Second, simulcast/SVC required
writing of multiple files. This might be too much data to dump.

On-fly metrics calculation is done in frame decoded callback.
Calculation time is excluded from encoding/decoding time. If CPU
usage measurement is enabled metrics calculation is disabled since
it affects CPU usage. The results are reported in Stats::PrintSummary.

Bug: webrtc:8524
Change-Id: Id54fb21f2f95deeb93757afaf46bde7d7ae18dac
Reviewed-on: https://webrtc-review.googlesource.com/22560
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20798}
diff --git a/modules/video_coding/codecs/test/stats.cc b/modules/video_coding/codecs/test/stats.cc
index b07a1f7..a3199ab 100644
--- a/modules/video_coding/codecs/test/stats.cc
+++ b/modules/video_coding/codecs/test/stats.cc
@@ -42,6 +42,16 @@
   return s1.bitrate_kbps < s2.bitrate_kbps;
 }
 
+bool LessForPsnr(const FrameStatistic& s1, const FrameStatistic& s2) {
+  RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
+  return s1.psnr < s2.psnr;
+}
+
+bool LessForSsim(const FrameStatistic& s1, const FrameStatistic& s2) {
+  RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
+  return s1.ssim < s2.ssim;
+}
+
 }  // namespace
 
 FrameStatistic* Stats::AddFrame() {
@@ -77,6 +87,8 @@
   size_t num_key_frames = 0;
   size_t num_delta_frames = 0;
   int num_encode_failures = 0;
+  double total_psnr = 0.0;
+  double total_ssim = 0.0;
 
   for (const FrameStatistic& stat : stats_) {
     total_encoding_time_us += stat.encode_time_us;
@@ -92,6 +104,10 @@
     if (stat.encode_return_code != 0) {
       ++num_encode_failures;
     }
+    if (stat.decoding_successful) {
+      total_psnr += stat.psnr;
+      total_ssim += stat.ssim;
+    }
   }
 
   // Encoding stats.
@@ -164,6 +180,24 @@
   printf("  Max bitrate: %7d kbps (frame %d)\n", frame_it->bitrate_kbps,
          frame_it->frame_number);
 
+  // Quality.
+  printf("Quality:\n");
+  if (decoded_frames.empty()) {
+    printf("No successfully decoded frames exist in this statistics.\n");
+  } else {
+    frame_it = std::min_element(decoded_frames.begin(), decoded_frames.end(),
+                                LessForPsnr);
+    printf("  PSNR min: %f (frame %d)\n", frame_it->psnr,
+           frame_it->frame_number);
+    printf("  PSNR avg: %f\n", total_psnr / decoded_frames.size());
+
+    frame_it = std::min_element(decoded_frames.begin(), decoded_frames.end(),
+                                LessForSsim);
+    printf("  SSIM min: %f (frame %d)\n", frame_it->ssim,
+           frame_it->frame_number);
+    printf("  SSIM avg: %f\n", total_ssim / decoded_frames.size());
+  }
+
   printf("\n");
   printf("Total encoding time  : %7d ms.\n", total_encoding_time_us / 1000);
   printf("Total decoding time  : %7d ms.\n", total_decoding_time_us / 1000);
diff --git a/modules/video_coding/codecs/test/stats.h b/modules/video_coding/codecs/test/stats.h
index fd6fa87..02ca641 100644
--- a/modules/video_coding/codecs/test/stats.h
+++ b/modules/video_coding/codecs/test/stats.h
@@ -50,6 +50,10 @@
   int packets_dropped = 0;
   size_t total_packets = 0;
   size_t manipulated_length = 0;
+
+  // Quality.
+  float psnr = 0.0;
+  float ssim = 0.0;
 };
 
 // Statistics for a sequence of processed frames. This class is not thread safe.
diff --git a/modules/video_coding/codecs/test/videoprocessor.cc b/modules/video_coding/codecs/test/videoprocessor.cc
index fb79308..5e20615 100644
--- a/modules/video_coding/codecs/test/videoprocessor.cc
+++ b/modules/video_coding/codecs/test/videoprocessor.cc
@@ -99,7 +99,6 @@
 VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
                                webrtc::VideoDecoder* decoder,
                                FrameReader* analysis_frame_reader,
-                               FrameWriter* analysis_frame_writer,
                                PacketManipulator* packet_manipulator,
                                const TestConfig& config,
                                Stats* stats,
@@ -113,7 +112,6 @@
       decode_callback_(this),
       packet_manipulator_(packet_manipulator),
       analysis_frame_reader_(analysis_frame_reader),
-      analysis_frame_writer_(analysis_frame_writer),
       encoded_frame_writer_(encoded_frame_writer),
       decoded_frame_writer_(decoded_frame_writer),
       last_inputed_frame_num_(-1),
@@ -127,7 +125,6 @@
   RTC_DCHECK(decoder);
   RTC_DCHECK(packet_manipulator);
   RTC_DCHECK(analysis_frame_reader);
-  RTC_DCHECK(analysis_frame_writer);
   RTC_DCHECK(stats);
 
   // Setup required callbacks for the encoder and decoder.
@@ -158,7 +155,7 @@
 
 void VideoProcessor::ProcessFrame() {
   RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
-  ++last_inputed_frame_num_;
+  const int frame_number = ++last_inputed_frame_num_;
 
   // Get frame from file.
   rtc::scoped_refptr<I420BufferInterface> buffer(
@@ -167,15 +164,13 @@
   // Use the frame number as the 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.
-  const uint32_t rtp_timestamp = (last_inputed_frame_num_ + 1) *
-                                 kRtpClockRateHz /
+  const uint32_t rtp_timestamp = (frame_number + 1) * kRtpClockRateHz /
                                  config_.codec_settings.maxFramerate;
-  rtp_timestamp_to_frame_num_[rtp_timestamp] = last_inputed_frame_num_;
-  VideoFrame source_frame(buffer, rtp_timestamp, kNoRenderTime,
-                          webrtc::kVideoRotation_0);
+  rtp_timestamp_to_frame_num_[rtp_timestamp] = frame_number;
+  input_frames_[frame_number] = rtc::MakeUnique<VideoFrame>(
+      buffer, rtp_timestamp, kNoRenderTime, webrtc::kVideoRotation_0);
 
-  std::vector<FrameType> frame_types =
-      config_.FrameTypeForFrame(last_inputed_frame_num_);
+  std::vector<FrameType> frame_types = config_.FrameTypeForFrame(frame_number);
 
   // Create frame statistics object used for aggregation at end of test run.
   FrameStatistic* frame_stat = stats_->AddFrame();
@@ -184,7 +179,7 @@
   // time recordings should wrap the Encode call as tightly as possible.
   frame_stat->encode_start_ns = rtc::TimeNanos();
   frame_stat->encode_return_code =
-      encoder_->Encode(source_frame, nullptr, &frame_types);
+      encoder_->Encode(*input_frames_[frame_number], nullptr, &frame_types);
 }
 
 void VideoProcessor::SetRates(int bitrate_kbps, int framerate_fps) {
@@ -222,30 +217,24 @@
     config_.encoded_frame_checker->CheckEncodedFrame(codec, encoded_image);
   }
 
-  // Check for dropped frames.
   const int frame_number =
       rtp_timestamp_to_frame_num_[encoded_image._timeStamp];
+
+  // Ensure strict monotonicity.
+  RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
+
+  // Check for dropped frames.
   bool last_frame_missing = false;
   if (frame_number > 0) {
-    RTC_DCHECK_GE(last_encoded_frame_num_, 0);
     int num_dropped_from_last_encode =
         frame_number - last_encoded_frame_num_ - 1;
     RTC_DCHECK_GE(num_dropped_from_last_encode, 0);
     RTC_CHECK_GE(rate_update_index_, 0);
     num_dropped_frames_[rate_update_index_] += num_dropped_from_last_encode;
-    if (num_dropped_from_last_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_last_encode; i++) {
-        WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
-      }
-    }
     const FrameStatistic* last_encoded_frame_stat =
         stats_->GetFrame(last_encoded_frame_num_);
     last_frame_missing = (last_encoded_frame_stat->manipulated_length == 0);
   }
-  // Ensure strict monotonicity.
-  RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
   last_encoded_frame_num_ = frame_number;
 
   // Update frame statistics.
@@ -285,18 +274,12 @@
   frame_stat->decode_return_code =
       decoder_->Decode(copied_image, last_frame_missing, nullptr);
 
-  if (frame_stat->decode_return_code != 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.
-    WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
-  }
-
   if (encoded_frame_writer_) {
     RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec));
   }
 }
 
-void VideoProcessor::FrameDecoded(const VideoFrame& image) {
+void VideoProcessor::FrameDecoded(const VideoFrame& decoded_frame) {
   RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
 
   // For the highest measurement accuracy of the decode time, the start/stop
@@ -304,46 +287,67 @@
   int64_t decode_stop_ns = rtc::TimeNanos();
 
   // Update frame statistics.
-  const int frame_number = rtp_timestamp_to_frame_num_[image.timestamp()];
+  const int frame_number =
+      rtp_timestamp_to_frame_num_[decoded_frame.timestamp()];
   FrameStatistic* frame_stat = stats_->GetFrame(frame_number);
-  frame_stat->decoded_width = image.width();
-  frame_stat->decoded_height = image.height();
+  frame_stat->decoded_width = decoded_frame.width();
+  frame_stat->decoded_height = decoded_frame.height();
   frame_stat->decode_time_us =
       GetElapsedTimeMicroseconds(frame_stat->decode_start_ns, decode_stop_ns);
   frame_stat->decoding_successful = true;
 
+  // Ensure strict monotonicity.
+  RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
+
   // Check if the codecs have resized the frame since previously decoded frame.
   if (frame_number > 0) {
-    RTC_CHECK_GE(last_decoded_frame_num_, 0);
+    if (decoded_frame_writer_ && last_decoded_frame_num_ >= 0) {
+      // For dropped/lost frames, write out the last decoded frame to make it
+      // look like a freeze at playback.
+      const int num_dropped_frames = frame_number - last_decoded_frame_num_;
+      for (int i = 0; i < num_dropped_frames; i++) {
+        WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
+      }
+    }
+    // TODO(ssilkin): move to FrameEncoded when webm:1474 is implemented.
     const FrameStatistic* last_decoded_frame_stat =
         stats_->GetFrame(last_decoded_frame_num_);
-    if (image.width() != last_decoded_frame_stat->decoded_width ||
-        image.height() != last_decoded_frame_stat->decoded_height) {
+    if (decoded_frame.width() != last_decoded_frame_stat->decoded_width ||
+        decoded_frame.height() != last_decoded_frame_stat->decoded_height) {
       RTC_CHECK_GE(rate_update_index_, 0);
       ++num_spatial_resizes_[rate_update_index_];
     }
   }
-  // Ensure strict monotonicity.
-  RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
   last_decoded_frame_num_ = frame_number;
 
-  // If the frame size is different from the original size, scale back to the
-  // original size. This is needed for the PSNR and SSIM calculations.
-  rtc::Buffer buffer;
-  ExtractBufferWithSize(image, config_.codec_settings.width,
-                        config_.codec_settings.height, &buffer);
-  WriteDecodedFrameToFile(&buffer);
+  // Skip quality metrics calculation to not affect CPU usage.
+  if (!config_.measure_cpu) {
+    frame_stat->psnr =
+        I420PSNR(input_frames_[frame_number].get(), &decoded_frame);
+    frame_stat->ssim =
+        I420SSIM(input_frames_[frame_number].get(), &decoded_frame);
+  }
 
-  last_decoded_frame_buffer_ = std::move(buffer);
+  // Delay erasing of input frames by one frame. The current frame might
+  // still be needed for other simulcast stream or spatial layer.
+  const int frame_number_to_erase = frame_number - 1;
+  if (frame_number_to_erase >= 0) {
+    auto input_frame_erase_to =
+        input_frames_.lower_bound(frame_number_to_erase);
+    input_frames_.erase(input_frames_.begin(), input_frame_erase_to);
+  }
+
+  if (decoded_frame_writer_) {
+    ExtractBufferWithSize(decoded_frame, config_.codec_settings.width,
+                          config_.codec_settings.height,
+                          &last_decoded_frame_buffer_);
+    WriteDecodedFrameToFile(&last_decoded_frame_buffer_);
+  }
 }
 
 void VideoProcessor::WriteDecodedFrameToFile(rtc::Buffer* buffer) {
-  RTC_DCHECK_EQ(buffer->size(), analysis_frame_writer_->FrameLength());
-  RTC_CHECK(analysis_frame_writer_->WriteFrame(buffer->data()));
-  if (decoded_frame_writer_) {
-    RTC_DCHECK_EQ(buffer->size(), decoded_frame_writer_->FrameLength());
-    RTC_CHECK(decoded_frame_writer_->WriteFrame(buffer->data()));
-  }
+  RTC_DCHECK_EQ(buffer->size(), decoded_frame_writer_->FrameLength());
+  RTC_CHECK(decoded_frame_writer_->WriteFrame(buffer->data()));
 }
 
 bool VideoProcessor::ExcludeFrame(const EncodedImage& encoded_image) {
diff --git a/modules/video_coding/codecs/test/videoprocessor.h b/modules/video_coding/codecs/test/videoprocessor.h
index 0960744..62a12ef 100644
--- a/modules/video_coding/codecs/test/videoprocessor.h
+++ b/modules/video_coding/codecs/test/videoprocessor.h
@@ -62,7 +62,6 @@
   VideoProcessor(webrtc::VideoEncoder* encoder,
                  webrtc::VideoDecoder* decoder,
                  FrameReader* analysis_frame_reader,
-                 FrameWriter* analysis_frame_writer,
                  PacketManipulator* packet_manipulator,
                  const TestConfig& config,
                  Stats* stats,
@@ -199,10 +198,16 @@
   // Fake network.
   PacketManipulator* const packet_manipulator_;
 
+  // Input frames. Used as reference at frame quality evaluation.
+  // Async codecs might queue frames. To handle that we keep input frame
+  // and release it after corresponding coded frame is decoded and quality
+  // measurement is done.
+  std::map<int, std::unique_ptr<VideoFrame>> input_frames_
+      RTC_GUARDED_BY(sequence_checker_);
+
   // 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_;
 
   // These (optional) file writers are used to persistently store the encoded
   // and decoded bitstreams. The purpose is to give the experimenter an option
diff --git a/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc b/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
index 4694f2d..bcfb132 100644
--- a/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
+++ b/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
@@ -51,23 +51,6 @@
 const float kOptimalBufferSize = 0.6f;
 const float kScaleKeyFrameSize = 0.5f;
 
-void VerifyQuality(const QualityMetricsResult& psnr_result,
-                   const QualityMetricsResult& ssim_result,
-                   const QualityThresholds& quality_thresholds) {
-  EXPECT_GT(psnr_result.average, quality_thresholds.min_avg_psnr);
-  EXPECT_GT(psnr_result.min, quality_thresholds.min_min_psnr);
-  EXPECT_GT(ssim_result.average, quality_thresholds.min_avg_ssim);
-  EXPECT_GT(ssim_result.min, quality_thresholds.min_min_ssim);
-}
-
-void PrintQualityMetrics(const QualityMetricsResult& psnr_result,
-                         const QualityMetricsResult& ssim_result) {
-  printf("Quality statistics\n==\n");
-  printf("PSNR avg: %f, min: %f\n", psnr_result.average, psnr_result.min);
-  printf("SSIM avg: %f, min: %f\n", ssim_result.average, ssim_result.min);
-  printf("\n");
-}
-
 bool RunEncodeInRealTime(const TestConfig& config) {
   if (config.measure_cpu) {
     return true;
@@ -284,10 +267,15 @@
   // Calculate and print rate control statistics.
   rate_update_index = 0;
   frame_number = 0;
+  quality_ = QualityMetrics();
   ResetRateControlMetrics(rate_update_index, rate_profiles);
   while (frame_number < num_frames) {
     UpdateRateControlMetrics(frame_number);
 
+    if (quality_thresholds) {
+      UpdateQualityMetrics(frame_number);
+    }
+
     if (bs_thresholds) {
       VerifyBitstream(frame_number, *bs_thresholds);
     }
@@ -310,28 +298,14 @@
   VerifyRateControlMetrics(rate_update_index, rc_thresholds, num_dropped_frames,
                            num_spatial_resizes);
 
+  if (quality_thresholds) {
+    VerifyQualityMetrics(*quality_thresholds);
+  }
+
   // Calculate and print other statistics.
   EXPECT_EQ(num_frames, static_cast<int>(stats_.size()));
   stats_.PrintSummary();
   cpu_process_time_->Print();
-
-  // Calculate and print image quality statistics.
-  // TODO(marpan): Should compute these quality metrics per SetRates update.
-  QualityMetricsResult psnr_result, ssim_result;
-  EXPECT_EQ(0, I420MetricsFromFiles(config_.input_filename.c_str(),
-                                    config_.output_filename.c_str(),
-                                    config_.codec_settings.width,
-                                    config_.codec_settings.height, &psnr_result,
-                                    &ssim_result));
-  if (quality_thresholds) {
-    VerifyQuality(psnr_result, ssim_result, *quality_thresholds);
-  }
-  PrintQualityMetrics(psnr_result, ssim_result);
-
-  // Remove analysis file.
-  if (remove(config_.output_filename.c_str()) < 0) {
-    fprintf(stderr, "Failed to remove temporary file!\n");
-  }
 }
 
 void VideoProcessorIntegrationTest::CreateEncoderAndDecoder() {
@@ -482,8 +456,8 @@
   task_queue->PostTask([this, &sync_event]() {
     processor_ = rtc::MakeUnique<VideoProcessor>(
         encoder_.get(), decoder_.get(), analysis_frame_reader_.get(),
-        analysis_frame_writer_.get(), packet_manipulator_.get(), config_,
-        &stats_, encoded_frame_writer_.get(), decoded_frame_writer_.get());
+        packet_manipulator_.get(), config_, &stats_,
+        encoded_frame_writer_.get(), decoded_frame_writer_.get());
     sync_event.Set();
   });
   sync_event.Wait(rtc::Event::kForever);
@@ -501,9 +475,7 @@
   // The VideoProcessor must be destroyed before the codecs.
   DestroyEncoderAndDecoder();
 
-  // Close the analysis files before we use them for SSIM/PSNR calculations.
   analysis_frame_reader_->Close();
-  analysis_frame_writer_->Close();
 
   // Close visualization files.
   if (encoded_frame_writer_) {
@@ -591,6 +563,19 @@
   }
 }
 
+void VideoProcessorIntegrationTest::UpdateQualityMetrics(int frame_number) {
+  FrameStatistic* frame_stat = stats_.GetFrame(frame_number);
+  if (frame_stat->decoding_successful) {
+    ++quality_.num_decoded_frames;
+    quality_.total_psnr += frame_stat->psnr;
+    quality_.total_ssim += frame_stat->ssim;
+    if (frame_stat->psnr < quality_.min_psnr)
+      quality_.min_psnr = frame_stat->psnr;
+    if (frame_stat->ssim < quality_.min_ssim)
+      quality_.min_ssim = frame_stat->ssim;
+  }
+}
+
 void VideoProcessorIntegrationTest::PrintRateControlMetrics(
     int rate_update_index,
     const std::vector<int>& num_dropped_frames,
@@ -656,6 +641,17 @@
   EXPECT_LE(*(frame_stat->max_nalu_length), bs_thresholds.max_nalu_length);
 }
 
+void VideoProcessorIntegrationTest::VerifyQualityMetrics(
+    const QualityThresholds& quality_thresholds) {
+  EXPECT_GT(quality_.num_decoded_frames, 0);
+  EXPECT_GT(quality_.total_psnr / quality_.num_decoded_frames,
+            quality_thresholds.min_avg_psnr);
+  EXPECT_GT(quality_.min_psnr, quality_thresholds.min_min_psnr);
+  EXPECT_GT(quality_.total_ssim / quality_.num_decoded_frames,
+            quality_thresholds.min_avg_ssim);
+  EXPECT_GT(quality_.min_ssim, quality_thresholds.min_min_ssim);
+}
+
 // Reset quantities before each encoder rate update.
 void VideoProcessorIntegrationTest::ResetRateControlMetrics(
     int rate_update_index,
diff --git a/modules/video_coding/codecs/test/videoprocessor_integrationtest.h b/modules/video_coding/codecs/test/videoprocessor_integrationtest.h
index b32029d..10677da 100644
--- a/modules/video_coding/codecs/test/videoprocessor_integrationtest.h
+++ b/modules/video_coding/codecs/test/videoprocessor_integrationtest.h
@@ -12,6 +12,7 @@
 #define MODULES_VIDEO_CODING_CODECS_TEST_VIDEOPROCESSOR_INTEGRATIONTEST_H_
 
 #include <cmath>
+#include <limits>
 #include <memory>
 #include <string>
 #include <vector>
@@ -159,6 +160,14 @@
     float key_framesize_kbits;
   };
 
+  struct QualityMetrics {
+    int num_decoded_frames = 0;
+    double total_psnr = 0.0;
+    double total_ssim = 0.0;
+    double min_psnr = std::numeric_limits<double>::max();
+    double min_ssim = std::numeric_limits<double>::max();
+  };
+
   void CreateEncoderAndDecoder();
   void DestroyEncoderAndDecoder();
   void SetUpAndInitObjects(rtc::TaskQueue* task_queue,
@@ -185,6 +194,9 @@
   void VerifyBitstream(int frame_number,
                        const BitstreamThresholds& bs_thresholds);
 
+  void UpdateQualityMetrics(int frame_number);
+  void VerifyQualityMetrics(const QualityThresholds& quality_thresholds);
+
   void PrintSettings() const;
 
   // Codecs.
@@ -207,6 +219,8 @@
 
   // Rates set for every encoder rate update.
   TargetRates target_;
+
+  QualityMetrics quality_;
 };
 
 }  // namespace test
diff --git a/modules/video_coding/codecs/test/videoprocessor_unittest.cc b/modules/video_coding/codecs/test/videoprocessor_unittest.cc
index a3bf9e3..1a51df2 100644
--- a/modules/video_coding/codecs/test/videoprocessor_unittest.cc
+++ b/modules/video_coding/codecs/test/videoprocessor_unittest.cc
@@ -20,7 +20,6 @@
 #include "test/gmock.h"
 #include "test/gtest.h"
 #include "test/testsupport/mock/mock_frame_reader.h"
-#include "test/testsupport/mock/mock_frame_writer.h"
 #include "test/testsupport/packet_reader.h"
 #include "test/testsupport/unittest_utils.h"
 #include "test/video_codec_settings.h"
@@ -55,7 +54,7 @@
         .WillRepeatedly(Return(kFrameSize));
     video_processor_ = rtc::MakeUnique<VideoProcessor>(
         &encoder_mock_, &decoder_mock_, &frame_reader_mock_,
-        &frame_writer_mock_, &packet_manipulator_mock_, config_, &stats_,
+        &packet_manipulator_mock_, config_, &stats_,
         nullptr /* encoded_frame_writer */, nullptr /* decoded_frame_writer */);
   }
 
@@ -78,7 +77,6 @@
   MockVideoEncoder encoder_mock_;
   MockVideoDecoder decoder_mock_;
   MockFrameReader frame_reader_mock_;
-  MockFrameWriter frame_writer_mock_;
   MockPacketManipulator packet_manipulator_mock_;
   Stats stats_;
   std::unique_ptr<VideoProcessor> video_processor_;
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 8a1c823..9a50e1c 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -202,7 +202,6 @@
       "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",
diff --git a/test/testsupport/mock/mock_frame_writer.h b/test/testsupport/mock/mock_frame_writer.h
deleted file mode 100644
index 2f6602d..0000000
--- a/test/testsupport/mock/mock_frame_writer.h
+++ /dev/null
@@ -1,32 +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.
- */
-
-#ifndef TEST_TESTSUPPORT_MOCK_MOCK_FRAME_WRITER_H_
-#define TEST_TESTSUPPORT_MOCK_MOCK_FRAME_WRITER_H_
-
-#include "test/testsupport/frame_writer.h"
-
-#include "test/gmock.h"
-
-namespace webrtc {
-namespace test {
-
-class MockFrameWriter : public FrameWriter {
- public:
-  MOCK_METHOD0(Init, bool());
-  MOCK_METHOD1(WriteFrame, bool(uint8_t* frame_buffer));
-  MOCK_METHOD0(Close, void());
-  MOCK_METHOD0(FrameLength, size_t());
-};
-
-}  // namespace test
-}  // namespace webrtc
-
-#endif  // TEST_TESTSUPPORT_MOCK_MOCK_FRAME_WRITER_H_