Add WriteVideoToFile to video_file_reader.

The function checks the file extension to determine YUV or Y4M format.

Also adds a flag aligned_output_file to compare_videos.py, which allows
saving the aligned reference video to a file.

Bug: webrtc:9642
Change-Id: Ia59f5c123a1e41104756eb6b235b6581c4ffbd77
Reviewed-on: https://webrtc-review.googlesource.com/99503
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Commit-Queue: Paulina Hensman <phensman@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24787}
diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn
index ae80585..437dca4 100644
--- a/rtc_tools/BUILD.gn
+++ b/rtc_tools/BUILD.gn
@@ -73,6 +73,20 @@
   ]
 }
 
+rtc_static_library("video_file_writer") {
+  sources = [
+    "video_file_writer.cc",
+    "video_file_writer.h",
+  ]
+  deps = [
+    ":video_file_reader",
+    "../api/video:video_frame",
+    "../api/video:video_frame_i420",
+    "../rtc_base:rtc_base_approved",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+}
+
 rtc_static_library("video_quality_analysis") {
   sources = [
     "frame_analyzer/video_quality_analysis.cc",
@@ -101,6 +115,7 @@
   deps = [
     ":command_line_parser",
     ":video_file_reader",
+    ":video_file_writer",
     ":video_quality_analysis",
     "../rtc_base:stringutils",
     "../test:perf_test",
@@ -343,6 +358,7 @@
       "sanitizers_unittest.cc",
       "simple_command_line_parser_unittest.cc",
       "video_file_reader_unittest.cc",
+      "video_file_writer_unittest.cc",
     ]
 
     if (!build_with_chromium && is_clang) {
@@ -355,6 +371,7 @@
       ":frame_editing_lib",
       ":reference_less_video_analysis_lib",
       ":video_file_reader",
+      ":video_file_writer",
       ":video_quality_analysis",
       "../common_video:common_video",
       "../rtc_base",
diff --git a/rtc_tools/compare_videos.py b/rtc_tools/compare_videos.py
index 40a2aab..d9cb670 100755
--- a/rtc_tools/compare_videos.py
+++ b/rtc_tools/compare_videos.py
@@ -36,6 +36,8 @@
                           'video (YUV).'))
   parser.add_option('--frame_analyzer', type='string',
                     help='Path to the frame analyzer executable.')
+  parser.add_option('--aligned_output_file', type='string',
+                    help='Path for output aligned YUV or Y4M file.')
   parser.add_option('--barcode_decoder', type='string',
                     help=('Path to the barcode decoder script. By default, we '
                           'will assume we can find it in barcode_tools/'
@@ -127,7 +129,7 @@
   """The main function.
 
   A simple invocation is:
-  ./webrtc/rtc_tools/barcode_tools/compare_videos.py
+  ./webrtc/rtc_tools/compare_videos.py
   --ref_video=<path_and_name_of_reference_video>
   --test_video=<path_and_name_of_test_video>
   --frame_analyzer=<path_and_name_of_the_frame_analyzer_executable>
@@ -165,6 +167,8 @@
   ]
   if options.chartjson_result_file:
     cmd.append('--chartjson_result_file=%s' % options.chartjson_result_file)
+  if options.aligned_output_file:
+    cmd.append('--aligned_output_file=%s' % options.aligned_output_file)
   frame_analyzer = subprocess.Popen(cmd, stdin=_DevNull(),
                                     stdout=sys.stdout, stderr=sys.stderr)
   frame_analyzer.wait()
diff --git a/rtc_tools/frame_analyzer/frame_analyzer.cc b/rtc_tools/frame_analyzer/frame_analyzer.cc
index aab2758..49d381f 100644
--- a/rtc_tools/frame_analyzer/frame_analyzer.cc
+++ b/rtc_tools/frame_analyzer/frame_analyzer.cc
@@ -20,6 +20,7 @@
 #include "rtc_tools/frame_analyzer/video_temporal_aligner.h"
 #include "rtc_tools/simple_command_line_parser.h"
 #include "rtc_tools/video_file_reader.h"
+#include "rtc_tools/video_file_writer.h"
 #include "test/testsupport/perf_test.h"
 
 /*
@@ -56,6 +57,9 @@
       " Default: test_file.yuv\n"
       "  - chartjson_result_file: Where to store perf result in chartjson"
       " format. If not present, no perf result will be stored."
+      " Default: None\n"
+      "  - aligned_output_file: Where to write aligned YUV/Y4M output file."
+      " If not present, no file will be written."
       " Default: None\n";
 
   webrtc::test::CommandLineParser parser;
@@ -69,6 +73,7 @@
   parser.SetFlag("label", "MY_TEST");
   parser.SetFlag("reference_file", "ref.yuv");
   parser.SetFlag("test_file", "test.yuv");
+  parser.SetFlag("aligned_output_file", "");
   parser.SetFlag("chartjson_result_file", "");
   parser.SetFlag("help", "false");
 
@@ -128,6 +133,14 @@
   if (!chartjson_result_file.empty()) {
     webrtc::test::WritePerfResults(chartjson_result_file);
   }
+  std::string aligned_output_file = parser.GetFlag("aligned_output_file");
+  if (!aligned_output_file.empty()) {
+    rtc::scoped_refptr<webrtc::test::Video> reordered_video =
+        webrtc::test::GenerateAlignedReferenceVideo(reference_video,
+                                                    matching_indices);
+    webrtc::test::WriteVideoToFile(reordered_video, aligned_output_file,
+                                   /*fps=*/30);
+  }
 
   return 0;
 }
diff --git a/rtc_tools/frame_analyzer/video_temporal_aligner.cc b/rtc_tools/frame_analyzer/video_temporal_aligner.cc
index f106852..2ebffbc 100644
--- a/rtc_tools/frame_analyzer/video_temporal_aligner.cc
+++ b/rtc_tools/frame_analyzer/video_temporal_aligner.cc
@@ -221,8 +221,14 @@
 rtc::scoped_refptr<Video> GenerateAlignedReferenceVideo(
     const rtc::scoped_refptr<Video>& reference_video,
     const rtc::scoped_refptr<Video>& test_video) {
-  return ReorderVideo(new LoopingVideo(reference_video),
-                      FindMatchingFrameIndices(reference_video, test_video));
+  return GenerateAlignedReferenceVideo(
+      reference_video, FindMatchingFrameIndices(reference_video, test_video));
+}
+
+rtc::scoped_refptr<Video> GenerateAlignedReferenceVideo(
+    const rtc::scoped_refptr<Video>& reference_video,
+    const std::vector<size_t>& indices) {
+  return ReorderVideo(new LoopingVideo(reference_video), indices);
 }
 
 }  // namespace test
diff --git a/rtc_tools/frame_analyzer/video_temporal_aligner.h b/rtc_tools/frame_analyzer/video_temporal_aligner.h
index 6f2015a..6024618 100644
--- a/rtc_tools/frame_analyzer/video_temporal_aligner.h
+++ b/rtc_tools/frame_analyzer/video_temporal_aligner.h
@@ -47,6 +47,11 @@
     const rtc::scoped_refptr<Video>& reference_video,
     const rtc::scoped_refptr<Video>& test_video);
 
+// As above, but using precalculated indices.
+rtc::scoped_refptr<Video> GenerateAlignedReferenceVideo(
+    const rtc::scoped_refptr<Video>& reference_video,
+    const std::vector<size_t>& indices);
+
 }  // namespace test
 }  // namespace webrtc
 
diff --git a/rtc_tools/video_file_writer.cc b/rtc_tools/video_file_writer.cc
new file mode 100644
index 0000000..72f3a69
--- /dev/null
+++ b/rtc_tools/video_file_writer.cc
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (c) 2018 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 "rtc_tools/video_file_writer.h"
+
+#include <cmath>
+#include <string>
+
+#include "api/video/i420_buffer.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/refcountedobject.h"
+
+namespace webrtc {
+namespace test {
+
+void WriteVideoToFile(const rtc::scoped_refptr<Video>& video,
+                      const std::string& file_name,
+                      int fps) {
+  FILE* output_file = fopen(file_name.c_str(), "wb");
+  if (output_file == nullptr) {
+    RTC_LOG(LS_ERROR) << "Could not open file for writing: " << file_name;
+    return;
+  }
+
+  bool isY4m = rtc::ends_with(file_name.c_str(), ".y4m");
+  if (isY4m) {
+    fprintf(output_file, "YUV4MPEG2 W%d H%d F%d:1 C420\n", video->width(),
+            video->height(), fps);
+  }
+  for (size_t i = 0; i < video->number_of_frames(); ++i) {
+    if (isY4m) {
+      std::string frame = "FRAME\n";
+      fwrite(frame.c_str(), 1, 6, output_file);
+    }
+    rtc::scoped_refptr<I420BufferInterface> buffer = video->GetFrame(i);
+    const uint8_t* data_y = buffer->DataY();
+    int stride = buffer->StrideY();
+    for (int i = 0; i < video->height(); ++i) {
+      fwrite(data_y + i * stride, /*size=*/1, stride, output_file);
+    }
+    const uint8_t* data_u = buffer->DataU();
+    stride = buffer->StrideU();
+    for (int i = 0; i < buffer->ChromaHeight(); ++i) {
+      fwrite(data_u + i * stride, /*size=*/1, stride, output_file);
+    }
+    const uint8_t* data_v = buffer->DataV();
+    stride = buffer->StrideV();
+    for (int i = 0; i < buffer->ChromaHeight(); ++i) {
+      fwrite(data_v + i * stride, /*size=*/1, stride, output_file);
+    }
+  }
+  if (ferror(output_file) != 0) {
+    RTC_LOG(LS_ERROR) << "Error writing to file " << file_name;
+  }
+  fclose(output_file);
+}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/rtc_tools/video_file_writer.h b/rtc_tools/video_file_writer.h
new file mode 100644
index 0000000..bd4524b
--- /dev/null
+++ b/rtc_tools/video_file_writer.h
@@ -0,0 +1,30 @@
+/*
+ *  Copyright (c) 2018 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 RTC_TOOLS_VIDEO_FILE_WRITER_H_
+#define RTC_TOOLS_VIDEO_FILE_WRITER_H_
+
+#include <cstdio>
+#include <string>
+
+#include "rtc_base/refcount.h"
+#include "rtc_tools/video_file_reader.h"
+
+namespace webrtc {
+namespace test {
+
+// Writes video to file, determining YUV or Y4M format from the file extension.
+void WriteVideoToFile(const rtc::scoped_refptr<Video>& video,
+                      const std::string& file_name,
+                      int fps);
+
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // RTC_TOOLS_VIDEO_FILE_WRITER_H_
diff --git a/rtc_tools/video_file_writer_unittest.cc b/rtc_tools/video_file_writer_unittest.cc
new file mode 100644
index 0000000..11b1c37
--- /dev/null
+++ b/rtc_tools/video_file_writer_unittest.cc
@@ -0,0 +1,123 @@
+/*
+ *  Copyright (c) 2018 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 <string>
+
+#include "rtc_base/refcountedobject.h"
+#include "rtc_tools/video_file_reader.h"
+#include "rtc_tools/video_file_writer.h"
+#include "test/gtest.h"
+#include "test/testsupport/fileutils.h"
+
+namespace webrtc {
+namespace test {
+
+class VideoFileWriterTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    const std::string filename =
+        webrtc::test::OutputPath() + "test_video_file.y4m";
+
+    // Create simple test video of size 6x4.
+    FILE* file = fopen(filename.c_str(), "wb");
+    ASSERT_TRUE(file != nullptr);
+    fprintf(file, "YUV4MPEG2 W6 H4 F60:1 C420 dummyParam\n");
+    fprintf(file, "FRAME\n");
+
+    const int i420_size = width * height * 3 / 2;
+    // First frame.
+    for (int i = 0; i < i420_size; ++i)
+      fputc(static_cast<char>(i), file);
+    fprintf(file, "FRAME\n");
+    // Second frame.
+    for (int i = 0; i < i420_size; ++i)
+      fputc(static_cast<char>(i + i420_size), file);
+    fclose(file);
+
+    // Open the newly created file.
+    video = webrtc::test::OpenY4mFile(filename);
+    ASSERT_TRUE(video);
+  }
+
+  // Write and read Y4M file.
+  void WriteVideoY4m() {
+    const std::string filename =
+        webrtc::test::OutputPath() + "test_video_file2.y4m";
+    webrtc::test::WriteVideoToFile(video, filename, fps);
+    written_video = webrtc::test::OpenY4mFile(filename);
+    ASSERT_TRUE(written_video);
+  }
+
+  // Write and read YUV file.
+  void WriteVideoYuv() {
+    const std::string filename =
+        webrtc::test::OutputPath() + "test_video_file2.yuv";
+    webrtc::test::WriteVideoToFile(video, filename, fps);
+    written_video = webrtc::test::OpenYuvFile(filename, width, height);
+    ASSERT_TRUE(written_video);
+  }
+
+  const int width = 6;
+  const int height = 4;
+  const int fps = 60;
+  rtc::scoped_refptr<webrtc::test::Video> video;
+  rtc::scoped_refptr<webrtc::test::Video> written_video;
+};
+
+TEST_F(VideoFileWriterTest, TestParsingFileHeaderY4m) {
+  WriteVideoY4m();
+  EXPECT_EQ(video->width(), written_video->width());
+  EXPECT_EQ(video->height(), written_video->height());
+}
+
+TEST_F(VideoFileWriterTest, TestParsingFileHeaderYuv) {
+  WriteVideoYuv();
+  EXPECT_EQ(video->width(), written_video->width());
+  EXPECT_EQ(video->height(), written_video->height());
+}
+
+TEST_F(VideoFileWriterTest, TestParsingNumberOfFramesY4m) {
+  WriteVideoY4m();
+  EXPECT_EQ(video->number_of_frames(), written_video->number_of_frames());
+}
+
+TEST_F(VideoFileWriterTest, TestParsingNumberOfFramesYuv) {
+  WriteVideoYuv();
+  EXPECT_EQ(video->number_of_frames(), written_video->number_of_frames());
+}
+
+TEST_F(VideoFileWriterTest, TestPixelContentY4m) {
+  WriteVideoY4m();
+  int cnt = 0;
+  for (const rtc::scoped_refptr<I420BufferInterface> frame : *written_video) {
+    for (int i = 0; i < width * height; ++i, ++cnt)
+      EXPECT_EQ(cnt, frame->DataY()[i]);
+    for (int i = 0; i < width / 2 * height / 2; ++i, ++cnt)
+      EXPECT_EQ(cnt, frame->DataU()[i]);
+    for (int i = 0; i < width / 2 * height / 2; ++i, ++cnt)
+      EXPECT_EQ(cnt, frame->DataV()[i]);
+  }
+}
+
+TEST_F(VideoFileWriterTest, TestPixelContentYuv) {
+  WriteVideoYuv();
+  int cnt = 0;
+  for (const rtc::scoped_refptr<I420BufferInterface> frame : *written_video) {
+    for (int i = 0; i < width * height; ++i, ++cnt)
+      EXPECT_EQ(cnt, frame->DataY()[i]);
+    for (int i = 0; i < width / 2 * height / 2; ++i, ++cnt)
+      EXPECT_EQ(cnt, frame->DataU()[i]);
+    for (int i = 0; i < width / 2 * height / 2; ++i, ++cnt)
+      EXPECT_EQ(cnt, frame->DataV()[i]);
+  }
+}
+
+}  // namespace test
+}  // namespace webrtc