Add YUV to IVF video converter util

Bug: webrtc:10138
Change-Id: I79ca08c45a664c66b15a1ed0c1322719c9f5574d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161449
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30559}
diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn
index dbc163b..1bbebb0 100644
--- a/rtc_tools/BUILD.gn
+++ b/rtc_tools/BUILD.gn
@@ -30,7 +30,10 @@
   }
 
   if (rtc_include_tests) {
-    deps += [ ":tools_unittests" ]
+    deps += [
+      ":tools_unittests",
+      ":yuv_to_ivf_converter",
+    ]
     if (rtc_enable_protobuf) {
       if (!build_with_chromium) {
         deps += [ ":event_log_visualizer" ]
@@ -363,6 +366,42 @@
 }
 
 if (rtc_include_tests) {
+  rtc_executable("yuv_to_ivf_converter") {
+    visibility = [ "*" ]
+    testonly = true
+    sources = [
+      "converter/yuv_to_ivf_converter.cc",
+    ]
+    deps = [
+      "../api:create_frame_generator",
+      "../api:frame_generator_api",
+      "../api/task_queue:default_task_queue_factory",
+      "../api/video:encoded_image",
+      "../api/video:video_frame",
+      "../api/video_codecs:video_codecs_api",
+      "../media:rtc_media_base",
+      "../modules/rtp_rtcp:rtp_rtcp_format",
+      "../modules/video_coding:video_codec_interface",
+      "../modules/video_coding:video_coding_utility",
+      "../modules/video_coding:webrtc_h264",
+      "../modules/video_coding:webrtc_vp8",
+      "../modules/video_coding:webrtc_vp9",
+      "../rtc_base:checks",
+      "../rtc_base:criticalsection",
+      "../rtc_base:logging",
+      "../rtc_base:rtc_event",
+      "../rtc_base:rtc_task_queue",
+      "../rtc_base/system:file_wrapper",
+      "../test:video_test_common",
+      "../test:video_test_support",
+      "//third_party/abseil-cpp/absl/debugging:failure_signal_handler",
+      "//third_party/abseil-cpp/absl/debugging:symbolize",
+      "//third_party/abseil-cpp/absl/flags:flag",
+      "//third_party/abseil-cpp/absl/flags:parse",
+      "//third_party/abseil-cpp/absl/strings",
+    ]
+  }
+
   if (rtc_enable_protobuf && !build_with_chromium) {
     rtc_executable("event_log_visualizer") {
       testonly = true
diff --git a/rtc_tools/DEPS b/rtc_tools/DEPS
index 0cddb4a..5ccd86b 100644
--- a/rtc_tools/DEPS
+++ b/rtc_tools/DEPS
@@ -19,3 +19,14 @@
   "+third_party/libyuv",
 ]
 
+specific_include_rules = {
+  ".*ivf_converter\.cc": [
+    "+absl/debugging/failure_signal_handler.h",
+    "+absl/debugging/symbolize.h",
+    "+modules/video_coding/codecs/vp8/include/vp8.h",
+    "+modules/video_coding/codecs/vp9/include/vp9.h",
+    "+modules/video_coding/include/video_error_codes.h",
+    "+modules/video_coding/utility/ivf_file_writer.h",
+    "+modules/video_coding/codecs/h264/include/h264.h",
+  ],
+}
diff --git a/rtc_tools/converter/yuv_to_ivf_converter.cc b/rtc_tools/converter/yuv_to_ivf_converter.cc
new file mode 100644
index 0000000..6f15bd3
--- /dev/null
+++ b/rtc_tools/converter/yuv_to_ivf_converter.cc
@@ -0,0 +1,288 @@
+/*
+ *  Copyright 2019 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 "absl/debugging/failure_signal_handler.h"
+#include "absl/debugging/symbolize.h"
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/strings/match.h"
+#include "api/task_queue/default_task_queue_factory.h"
+#include "api/test/create_frame_generator.h"
+#include "api/test/frame_generator_interface.h"
+#include "api/video/encoded_image.h"
+#include "api/video/video_codec_type.h"
+#include "api/video_codecs/video_codec.h"
+#include "api/video_codecs/video_encoder.h"
+#include "media/base/media_constants.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "modules/video_coding/codecs/vp9/include/vp9.h"
+#include "modules/video_coding/include/video_error_codes.h"
+#include "modules/video_coding/utility/ivf_file_writer.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/critical_section.h"
+#include "rtc_base/event.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/system/file_wrapper.h"
+#include "rtc_base/task_queue.h"
+#include "test/testsupport/frame_reader.h"
+#include "test/video_codec_settings.h"
+
+#if defined(WEBRTC_USE_H264)
+#include "modules/video_coding/codecs/h264/include/h264.h"
+#endif
+
+ABSL_FLAG(std::string, input, "", "Input YUV file to convert to IVF");
+ABSL_FLAG(int, width, 0, "Input frame width");
+ABSL_FLAG(int, height, 0, "Input frame height");
+ABSL_FLAG(std::string, codec, cricket::kVp8CodecName, "Codec to use");
+ABSL_FLAG(std::string, output, "", "Output IVF file");
+
+namespace webrtc {
+namespace test {
+namespace {
+
+constexpr int kMaxFramerate = 30;
+// We use very big value here to ensure that codec won't hit any limits.
+constexpr uint32_t kBitrateBps = 100000000;
+constexpr int kKeyFrameIntervalMs = 30000;
+constexpr int kMaxFrameEncodeWaitTimeoutMs = 2000;
+constexpr int kFrameLogInterval = 100;
+static const VideoEncoder::Capabilities kCapabilities(false);
+
+class IvfFileWriterEncodedCallback : public EncodedImageCallback {
+ public:
+  IvfFileWriterEncodedCallback(const std::string& file_name,
+                               VideoCodecType video_codec_type,
+                               int expected_frames_count)
+      : file_writer_(
+            IvfFileWriter::Wrap(FileWrapper::OpenWriteOnly(file_name), 0)),
+        video_codec_type_(video_codec_type),
+        expected_frames_count_(expected_frames_count) {
+    RTC_CHECK(file_writer_.get());
+  }
+  ~IvfFileWriterEncodedCallback() { RTC_CHECK(file_writer_->Close()); }
+
+  Result OnEncodedImage(const EncodedImage& encoded_image,
+                        const CodecSpecificInfo* codec_specific_info,
+                        const RTPFragmentationHeader* fragmentation) override {
+    RTC_CHECK(file_writer_->WriteFrame(encoded_image, video_codec_type_));
+
+    rtc::CritScope crit(&lock_);
+    received_frames_count_++;
+    RTC_CHECK_LE(received_frames_count_, expected_frames_count_);
+    if (received_frames_count_ % kFrameLogInterval == 0) {
+      RTC_LOG(INFO) << received_frames_count_ << " out of "
+                    << expected_frames_count_ << " frames written";
+    }
+    next_frame_written_.Set();
+    return Result(Result::Error::OK);
+  }
+
+  void WaitNextFrameWritten(int timeout_ms) {
+    RTC_CHECK(next_frame_written_.Wait(timeout_ms));
+    next_frame_written_.Reset();
+  }
+
+ private:
+  std::unique_ptr<IvfFileWriter> file_writer_;
+  const VideoCodecType video_codec_type_;
+  const int expected_frames_count_;
+
+  rtc::CriticalSection lock_;
+  int received_frames_count_ RTC_GUARDED_BY(lock_) = 0;
+  rtc::Event next_frame_written_;
+};
+
+class Encoder {
+ public:
+  Encoder(int width,
+          int height,
+          int frames_count,
+          const std::string& output_file_name,
+          VideoCodecType video_codec_type,
+          std::unique_ptr<VideoEncoder> video_encoder)
+      : video_encoder_(std::move(video_encoder)),
+        task_queue_(CreateDefaultTaskQueueFactory()->CreateTaskQueue(
+            "Encoder",
+            TaskQueueFactory::Priority::HIGH)) {
+    ivf_writer_callback_ = std::make_unique<IvfFileWriterEncodedCallback>(
+        output_file_name, video_codec_type, frames_count);
+
+    task_queue_.PostTask([width, height, video_codec_type, this]() {
+      VideoCodec codec_settings;
+      CodecSettings(video_codec_type, &codec_settings);
+      codec_settings.width = width;
+      codec_settings.height = height;
+      codec_settings.maxFramerate = kMaxFramerate;
+      codec_settings.startBitrate = kBitrateBps;
+      codec_settings.minBitrate = kBitrateBps;
+      codec_settings.maxBitrate = kBitrateBps;
+      switch (video_codec_type) {
+        case VideoCodecType::kVideoCodecVP8: {
+          VideoCodecVP8* vp8_settings = codec_settings.VP8();
+          vp8_settings->frameDroppingOn = false;
+          vp8_settings->keyFrameInterval = kKeyFrameIntervalMs;
+          vp8_settings->denoisingOn = false;
+        } break;
+        case VideoCodecType::kVideoCodecVP9: {
+          VideoCodecVP9* vp9_settings = codec_settings.VP9();
+          vp9_settings->denoisingOn = false;
+          vp9_settings->frameDroppingOn = false;
+          vp9_settings->keyFrameInterval = kKeyFrameIntervalMs;
+          vp9_settings->automaticResizeOn = false;
+        } break;
+        case VideoCodecType::kVideoCodecH264: {
+          VideoCodecH264* h264_settings = codec_settings.H264();
+          h264_settings->frameDroppingOn = false;
+          h264_settings->keyFrameInterval = kKeyFrameIntervalMs;
+        } break;
+        default:
+          RTC_CHECK(false) << "Unsupported codec type";
+      }
+      VideoBitrateAllocation bitrate_allocation;
+      bitrate_allocation.SetBitrate(0, 0, kBitrateBps);
+
+      video_encoder_->RegisterEncodeCompleteCallback(
+          ivf_writer_callback_.get());
+      RTC_CHECK_EQ(
+          WEBRTC_VIDEO_CODEC_OK,
+          video_encoder_->InitEncode(
+              &codec_settings,
+              VideoEncoder::Settings(kCapabilities, /*number_of_cores=*/4,
+                                     /*max_payload_size=*/0)));
+      video_encoder_->SetRates(VideoEncoder::RateControlParameters(
+          bitrate_allocation,
+          static_cast<double>(codec_settings.maxFramerate)));
+    });
+  }
+
+  void Encode(const VideoFrame& frame) {
+    task_queue_.PostTask([frame, this]() {
+      RTC_CHECK_EQ(WEBRTC_VIDEO_CODEC_OK,
+                   video_encoder_->Encode(frame, nullptr));
+    });
+  }
+
+  void WaitNextFrameWritten(int timeout_ms) {
+    ivf_writer_callback_->WaitNextFrameWritten(timeout_ms);
+  }
+
+ private:
+  std::unique_ptr<VideoEncoder> video_encoder_;
+  std::unique_ptr<IvfFileWriterEncodedCallback> ivf_writer_callback_;
+
+  rtc::TaskQueue task_queue_;
+};
+
+int GetFrameCount(std::string yuv_file_name, int width, int height) {
+  std::unique_ptr<FrameReader> yuv_reader =
+      std::make_unique<YuvFrameReaderImpl>(std::move(yuv_file_name), width,
+                                           height);
+  RTC_CHECK(yuv_reader->Init());
+  int frames_count = yuv_reader->NumberOfFrames();
+  yuv_reader->Close();
+  return frames_count;
+}
+
+VideoFrame BuildFrame(FrameGeneratorInterface::VideoFrameData frame_data,
+                      uint32_t rtp_timestamp) {
+  return VideoFrame::Builder()
+      .set_video_frame_buffer(frame_data.buffer)
+      .set_update_rect(frame_data.update_rect)
+      .set_timestamp_rtp(rtp_timestamp)
+      .build();
+}
+
+void WriteVideoFile(std::string input_file_name,
+                    int width,
+                    int height,
+                    std::string output_file_name,
+                    VideoCodecType video_codec_type,
+                    std::unique_ptr<VideoEncoder> video_encoder) {
+  int frames_count = GetFrameCount(input_file_name, width, height);
+
+  std::unique_ptr<FrameGeneratorInterface> frame_generator =
+      CreateFromYuvFileFrameGenerator({input_file_name}, width, height,
+                                      /*frame_repeat_count=*/1);
+
+  Encoder encoder(width, height, frames_count, output_file_name,
+                  video_codec_type, std::move(video_encoder));
+
+  uint32_t last_frame_timestamp = 0;
+
+  for (int i = 0; i < frames_count; ++i) {
+    const uint32_t timestamp =
+        last_frame_timestamp + kVideoPayloadTypeFrequency / kMaxFramerate;
+    VideoFrame frame = BuildFrame(frame_generator->NextFrame(), timestamp);
+
+    last_frame_timestamp = timestamp;
+
+    encoder.Encode(frame);
+    encoder.WaitNextFrameWritten(kMaxFrameEncodeWaitTimeoutMs);
+
+    if ((i + 1) % kFrameLogInterval == 0) {
+      RTC_LOG(INFO) << i + 1 << " out of " << frames_count
+                    << " frames are sent for encoding";
+    }
+  }
+  RTC_LOG(INFO) << "All " << frames_count << " frame are sent for encoding";
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace webrtc
+
+int main(int argc, char* argv[]) {
+  // Initialize the symbolizer to get a human-readable stack trace.
+  absl::InitializeSymbolizer(argv[0]);
+
+  absl::FailureSignalHandlerOptions options;
+  absl::InstallFailureSignalHandler(options);
+
+  absl::ParseCommandLine(argc, argv);
+
+  std::string codec_name = absl::GetFlag(FLAGS_codec);
+  std::string input_file_name = absl::GetFlag(FLAGS_input);
+  std::string output_file_name = absl::GetFlag(FLAGS_output);
+  int width = absl::GetFlag(FLAGS_width);
+  int height = absl::GetFlag(FLAGS_height);
+  RTC_CHECK_NE(input_file_name, "") << "--input is required";
+  RTC_CHECK_NE(output_file_name, "") << "--output is required";
+  RTC_CHECK_GT(width, 0) << "width must be greater then 0";
+  RTC_CHECK_GT(height, 0) << "height must be greater then 0";
+  if (absl::EqualsIgnoreCase(codec_name, cricket::kVp8CodecName)) {
+    webrtc::test::WriteVideoFile(
+        input_file_name, width, height, output_file_name,
+        webrtc::VideoCodecType::kVideoCodecVP8, webrtc::VP8Encoder::Create());
+    return 0;
+  }
+  if (absl::EqualsIgnoreCase(codec_name, cricket::kVp9CodecName)) {
+    webrtc::test::WriteVideoFile(
+        input_file_name, width, height, output_file_name,
+        webrtc::VideoCodecType::kVideoCodecVP9, webrtc::VP9Encoder::Create());
+    return 0;
+  }
+#if defined(WEBRTC_USE_H264)
+  if (absl::EqualsIgnoreCase(codec_name, cricket::kH264CodecName)) {
+    webrtc::test::WriteVideoFile(
+        input_file_name, width, height, output_file_name,
+        webrtc::VideoCodecType::kVideoCodecH264,
+        webrtc::H264Encoder::Create(
+            cricket::VideoCodec(cricket::kH264CodecName)));
+    return 0;
+  }
+#endif
+  RTC_CHECK(false) << "Unsupported codec: " << codec_name;
+  return 1;
+}
diff --git a/test/DEPS b/test/DEPS
index 0f4fd2f..c4634f8 100644
--- a/test/DEPS
+++ b/test/DEPS
@@ -65,5 +65,5 @@
   ".*sdp_changer\.(h|cc)": [
     "+pc",
     "+p2p",
-  ]
+  ],
 }