Introduce a frame dumping encoder wrapper.

Expose new function MaybeCreateFrameDumpingEncoderWrapper that
wraps another passed encoder and dumps its encoded frames out
into a unique IVF file into the directory specified by the
"WebRTC-EncoderDataDumpDirectory" field trial. If the passed
encoder is nullptr, or the field trial is not setup, the function
just returns the passed encoder. The directory specified by the
field trial parameter should be delimited by ';'.

The new function is wired up in VideoStreamEncoder.

Bug: b/296242528
Change-Id: I6143adf899f78fcc03d4239a86c68dcbab483f1c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/317200
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Markus Handell <handellm@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40600}
diff --git a/modules/video_coding/utility/ivf_file_writer.cc b/modules/video_coding/utility/ivf_file_writer.cc
index cabfd49..0102598 100644
--- a/modules/video_coding/utility/ivf_file_writer.cc
+++ b/modules/video_coding/utility/ivf_file_writer.cc
@@ -163,8 +163,8 @@
   int64_t timestamp = using_capture_timestamps_
                           ? encoded_image.capture_time_ms_
                           : wrap_handler_.Unwrap(encoded_image.Timestamp());
-  if (last_timestamp_ != -1 && timestamp <= last_timestamp_) {
-    RTC_LOG(LS_WARNING) << "Timestamp no increasing: " << last_timestamp_
+  if (last_timestamp_ != -1 && timestamp < last_timestamp_) {
+    RTC_LOG(LS_WARNING) << "Timestamp not increasing: " << last_timestamp_
                         << " -> " << timestamp;
   }
   last_timestamp_ = timestamp;
diff --git a/video/BUILD.gn b/video/BUILD.gn
index e0ef6fa..3de58b6 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -194,6 +194,32 @@
   ]
 }
 
+rtc_library("frame_dumping_encoder") {
+  visibility = [ "*" ]
+
+  sources = [
+    "frame_dumping_encoder.cc",
+    "frame_dumping_encoder.h",
+  ]
+
+  deps = [
+    "../api:field_trials_view",
+    "../api:sequence_checker",
+    "../api/video:encoded_frame",
+    "../api/video:encoded_image",
+    "../api/video:video_frame",
+    "../api/video_codecs:video_codecs_api",
+    "../modules/video_coding",
+    "../modules/video_coding:video_codec_interface",
+    "../modules/video_coding:video_coding_utility",
+    "../rtc_base:stringutils",
+    "../rtc_base:timeutils",
+    "../rtc_base/system:file_wrapper",
+  ]
+
+  absl_deps = [ "//third_party/abseil-cpp/absl/algorithm:container" ]
+}
+
 rtc_library("frame_cadence_adapter") {
   visibility = [ "*" ]
   sources = [
@@ -371,6 +397,7 @@
 
   deps = [
     ":frame_cadence_adapter",
+    ":frame_dumping_encoder",
     ":video_stream_encoder_interface",
     "../api:field_trials_view",
     "../api:rtp_parameters",
diff --git a/video/frame_dumping_encoder.cc b/video/frame_dumping_encoder.cc
new file mode 100644
index 0000000..5c9e601
--- /dev/null
+++ b/video/frame_dumping_encoder.cc
@@ -0,0 +1,138 @@
+/*
+ *  Copyright (c) 2023 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 "video/frame_dumping_encoder.h"
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "api/sequence_checker.h"
+#include "api/video/video_codec_type.h"
+#include "modules/video_coding/utility/ivf_file_writer.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/system/file_wrapper.h"
+#include "rtc_base/time_utils.h"
+
+namespace webrtc {
+namespace {
+
+constexpr auto kEncoderDataDumpDirectoryFieldTrial =
+    "WebRTC-EncoderDataDumpDirectory";
+
+class FrameDumpingEncoder : public VideoEncoder, public EncodedImageCallback {
+ public:
+  FrameDumpingEncoder(std::unique_ptr<VideoEncoder> wrapped,
+                      int64_t origin_time_micros,
+                      std::string output_directory)
+      : wrapped_(std::move(wrapped)),
+        output_directory_(output_directory),
+        origin_time_micros_(origin_time_micros) {
+    sequence_checker_.Detach();
+  }
+
+  // VideoEncoder overloads.
+  void SetFecControllerOverride(
+      FecControllerOverride* fec_controller_override) override {
+    wrapped_->SetFecControllerOverride(fec_controller_override);
+  }
+  int InitEncode(const VideoCodec* codec_settings,
+                 const VideoEncoder::Settings& settings) override {
+    codec_settings_ = *codec_settings;
+    return wrapped_->InitEncode(codec_settings, settings);
+  }
+  int32_t RegisterEncodeCompleteCallback(
+      EncodedImageCallback* callback) override {
+    callback_ = callback;
+    return wrapped_->RegisterEncodeCompleteCallback(this);
+  }
+  int32_t Release() override { return wrapped_->Release(); }
+  int32_t Encode(const VideoFrame& frame,
+                 const std::vector<VideoFrameType>* frame_types) override {
+    return wrapped_->Encode(frame, frame_types);
+  }
+  void SetRates(const RateControlParameters& parameters) override {
+    wrapped_->SetRates(parameters);
+  }
+  void OnPacketLossRateUpdate(float packet_loss_rate) override {
+    wrapped_->OnPacketLossRateUpdate(packet_loss_rate);
+  }
+  void OnRttUpdate(int64_t rtt_ms) override { wrapped_->OnRttUpdate(rtt_ms); }
+  void OnLossNotification(const LossNotification& loss_notification) override {
+    wrapped_->OnLossNotification(loss_notification);
+  }
+  EncoderInfo GetEncoderInfo() const override {
+    return wrapped_->GetEncoderInfo();
+  }
+
+  // EncodedImageCallback overrides.
+  Result OnEncodedImage(const EncodedImage& encoded_image,
+                        const CodecSpecificInfo* codec_specific_info) override {
+    RTC_DCHECK_RUN_ON(&sequence_checker_);
+    GetFileWriterForSimulcastIndex(encoded_image.SimulcastIndex().value_or(0))
+        .WriteFrame(encoded_image, codec_settings_.codecType);
+    return callback_->OnEncodedImage(encoded_image, codec_specific_info);
+  }
+  void OnDroppedFrame(DropReason reason) override {
+    RTC_DCHECK_RUN_ON(&sequence_checker_);
+    callback_->OnDroppedFrame(reason);
+  }
+
+ private:
+  std::string FilenameFromSimulcastIndex(int index) {
+    char filename_buffer[1024];
+    rtc::SimpleStringBuilder builder(filename_buffer);
+    builder << output_directory_ << "/webrtc_encoded_frames"
+            << "." << origin_time_micros_ << "." << index << ".ivf";
+    return builder.str();
+  }
+
+  IvfFileWriter& GetFileWriterForSimulcastIndex(int index) {
+    const auto& it = writers_by_simulcast_index_.find(index);
+    if (it != writers_by_simulcast_index_.end()) {
+      return *it->second;
+    }
+    auto writer = IvfFileWriter::Wrap(
+        FileWrapper::OpenWriteOnly(FilenameFromSimulcastIndex(index)),
+        /*byte_limit=*/100'000'000);
+    auto* writer_ptr = writer.get();
+    writers_by_simulcast_index_.insert(
+        std::make_pair(index, std::move(writer)));
+    return *writer_ptr;
+  }
+
+  SequenceChecker sequence_checker_;
+  std::unique_ptr<VideoEncoder> wrapped_;
+  std::map<int, std::unique_ptr<IvfFileWriter>> writers_by_simulcast_index_;
+  std::unique_ptr<IvfFileWriter> writer_;
+  VideoCodec codec_settings_;
+  EncodedImageCallback* callback_ = nullptr;
+  std::string output_directory_;
+  int64_t origin_time_micros_ = 0;
+};
+
+}  // namespace
+
+std::unique_ptr<VideoEncoder> MaybeCreateFrameDumpingEncoderWrapper(
+    std::unique_ptr<VideoEncoder> encoder,
+    const FieldTrialsView& field_trials) {
+  auto output_directory =
+      field_trials.Lookup(kEncoderDataDumpDirectoryFieldTrial);
+  if (output_directory.empty() || !encoder) {
+    return encoder;
+  }
+  absl::c_replace(output_directory, ';', '/');
+  return std::make_unique<FrameDumpingEncoder>(
+      std::move(encoder), rtc::TimeMicros(), output_directory);
+}
+
+}  // namespace webrtc
diff --git a/video/frame_dumping_encoder.h b/video/frame_dumping_encoder.h
new file mode 100644
index 0000000..2fd543a
--- /dev/null
+++ b/video/frame_dumping_encoder.h
@@ -0,0 +1,34 @@
+/*
+ *  Copyright (c) 2023 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 VIDEO_FRAME_DUMPING_ENCODER_H_
+#define VIDEO_FRAME_DUMPING_ENCODER_H_
+
+#include <memory>
+
+#include "api/field_trials_view.h"
+#include "api/video_codecs/video_encoder.h"
+
+namespace webrtc {
+
+// Creates an encoder that wraps another passed encoder and dumps its encoded
+// frames out into a unique IVF file into the directory specified by the
+// "WebRTC-EncoderDataDumpDirectory" field trial. Each file generated is
+// suffixed by the simulcast index of the encoded frames. If the passed encoder
+// is nullptr, or the field trial is not setup, the function just returns the
+// passed encoder. The directory specified by the field trial parameter should
+// be delimited by ';'.
+std::unique_ptr<VideoEncoder> MaybeCreateFrameDumpingEncoderWrapper(
+    std::unique_ptr<VideoEncoder> encoder,
+    const FieldTrialsView& field_trials);
+
+}  // namespace webrtc
+
+#endif  // VIDEO_FRAME_DUMPING_ENCODER_H_
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index 98d1630..c367510 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -55,6 +55,7 @@
 #include "video/alignment_adjuster.h"
 #include "video/config/encoder_stream_factory.h"
 #include "video/frame_cadence_adapter.h"
+#include "video/frame_dumping_encoder.h"
 
 namespace webrtc {
 
@@ -950,8 +951,10 @@
     // supports only single instance of encoder of given type.
     encoder_.reset();
 
-    encoder_ = settings_.encoder_factory->CreateVideoEncoder(
-        encoder_config_.video_format);
+    encoder_ = MaybeCreateFrameDumpingEncoderWrapper(
+        settings_.encoder_factory->CreateVideoEncoder(
+            encoder_config_.video_format),
+        field_trials_);
     if (!encoder_) {
       RTC_LOG(LS_ERROR) << "CreateVideoEncoder failed, failing encoder format: "
                         << encoder_config_.video_format.ToString();