[PCLF] Add support for dumping video with multiple receivers

Bug: b/197896468
Change-Id: I7896246eedb2e9efe847df4dddfc8ef05f7d152b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230424
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34866}
diff --git a/api/test/peerconnection_quality_test_fixture.h b/api/test/peerconnection_quality_test_fixture.h
index 031b795..4f691d7 100644
--- a/api/test/peerconnection_quality_test_fixture.h
+++ b/api/test/peerconnection_quality_test_fixture.h
@@ -233,9 +233,31 @@
     // written to the dump file. The value must be greater than 0.
     int input_dump_sampling_modulo = 1;
     // If specified this file will be used as output on the receiver side for
-    // this stream. If multiple streams will be produced by input stream,
-    // output files will be appended with indexes. The produced files contains
-    // what was rendered for this video stream on receiver side.
+    // this stream.
+    //
+    // If multiple output streams will be produced by this stream (e.g. when the
+    // stream represented by this `VideoConfig` is received by more than one
+    // peer), output files will be appended with receiver names. If the second
+    // and other receivers will be added in the middle of the call after the
+    // first frame for this stream has been already written to the output file,
+    // then only dumps for newly added peers will be appended with receiver
+    // name, the dump for the first receiver will have name equal to the
+    // specified one. For example:
+    //   * If we have peers A and B and A has `VideoConfig` V_a with
+    //     V_a.output_dump_file_name = "/foo/a_output.yuv", then the stream
+    //     related to V_a will be written into "/foo/a_output.yuv".
+    //   * If we have peers A, B and C and A has `VideoConfig` V_a with
+    //     V_a.output_dump_file_name = "/foo/a_output.yuv", then the stream
+    //     related to V_a will be written for peer B into "/foo/a_output.yuv.B"
+    //     and for peer C into "/foo/a_output.yuv.C"
+    //   * If we have peers A and B and A has `VideoConfig` V_a with
+    //     V_a.output_dump_file_name = "/foo/a_output.yuv", then if after B
+    //     received the first frame related to V_a peer C joined the call, then
+    //     the stream related to V_a will be written for peer B into
+    //     "/foo/a_output.yuv" and for peer C into "/foo/a_output.yuv.C"
+    //
+    // The produced files contains what was rendered for this video stream on
+    // receiver side.
     absl::optional<std::string> output_dump_file_name;
     // Used only if `output_dump_file_name` is set. Specifies the module for the
     // video frames to be dumped. Modulo equals X means every Xth frame will be
diff --git a/test/pc/e2e/BUILD.gn b/test/pc/e2e/BUILD.gn
index 20e59a5..11c95d0 100644
--- a/test/pc/e2e/BUILD.gn
+++ b/test/pc/e2e/BUILD.gn
@@ -187,6 +187,7 @@
         "../../../api/video:video_rtp_headers",
         "../../../api/video_codecs:video_codecs_api",
         "../../../rtc_base:criticalsection",
+        "../../../rtc_base:stringutils",
         "../../../rtc_base/synchronization:mutex",
         "../../../test:video_test_common",
         "../../../test:video_test_support",
diff --git a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc
index b1a2220..d89640a 100644
--- a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc
+++ b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc
@@ -16,6 +16,7 @@
 #include "absl/memory/memory.h"
 #include "absl/strings/string_view.h"
 #include "api/array_view.h"
+#include "rtc_base/strings/string_builder.h"
 #include "test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h"
 #include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h"
 #include "test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h"
@@ -134,7 +135,7 @@
                                     config.width, config.height)));
   }
   {
-    MutexLock lock(&lock_);
+    MutexLock lock(&mutex_);
     known_video_configs_.insert({*config.stream_label, config});
   }
   return std::make_unique<AnalyzingFramePreprocessor>(
@@ -154,6 +155,16 @@
     int max_threads_count) {
   analyzer_->Start(std::move(test_case_name), peer_names, max_threads_count);
   extractor_->Start(peer_names.size());
+  MutexLock lock(&mutex_);
+  peers_count_ = peer_names.size();
+}
+
+void VideoQualityAnalyzerInjectionHelper::RegisterParticipantInCall(
+    absl::string_view peer_name) {
+  analyzer_->RegisterParticipantInCall(peer_name);
+  extractor_->AddParticipantInCall();
+  MutexLock lock(&mutex_);
+  peers_count_++;
 }
 
 void VideoQualityAnalyzerInjectionHelper::OnStatsReports(
@@ -203,7 +214,7 @@
 
   std::string stream_label = analyzer_->GetStreamLabel(frame.id());
   std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>* sinks =
-      PopulateSinks(stream_label);
+      PopulateSinks(ReceiverStream(peer_name, stream_label));
   if (sinks == nullptr) {
     return;
   }
@@ -214,20 +225,27 @@
 
 std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>*
 VideoQualityAnalyzerInjectionHelper::PopulateSinks(
-    const std::string& stream_label) {
-  MutexLock lock(&lock_);
-  auto sinks_it = sinks_.find(stream_label);
+    const ReceiverStream& receiver_stream) {
+  MutexLock lock(&mutex_);
+  auto sinks_it = sinks_.find(receiver_stream);
   if (sinks_it != sinks_.end()) {
     return &sinks_it->second;
   }
-  auto it = known_video_configs_.find(stream_label);
+  auto it = known_video_configs_.find(receiver_stream.stream_label);
   RTC_DCHECK(it != known_video_configs_.end())
-      << "No video config for stream " << stream_label;
+      << "No video config for stream " << receiver_stream.stream_label;
   const VideoConfig& config = it->second;
 
   std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks;
+  absl::optional<std::string> output_dump_file_name =
+      config.output_dump_file_name;
+  if (output_dump_file_name.has_value() && peers_count_ > 2) {
+    rtc::StringBuilder builder(*output_dump_file_name);
+    builder << "." << receiver_stream.peer_name;
+    output_dump_file_name = builder.str();
+  }
   test::VideoFrameWriter* writer =
-      MaybeCreateVideoWriter(config.output_dump_file_name, config);
+      MaybeCreateVideoWriter(output_dump_file_name, config);
   if (writer) {
     sinks.push_back(std::make_unique<VideoWriter>(
         writer, config.output_dump_sampling_modulo));
@@ -237,8 +255,8 @@
         test::VideoRenderer::Create((*config.stream_label + "-render").c_str(),
                                     config.width, config.height)));
   }
-  sinks_.insert({stream_label, std::move(sinks)});
-  return &(sinks_.find(stream_label)->second);
+  sinks_.insert({receiver_stream, std::move(sinks)});
+  return &(sinks_.find(receiver_stream)->second);
 }
 
 }  // namespace webrtc_pc_e2e
diff --git a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h
index 997b8ad..f130efc 100644
--- a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h
+++ b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h
@@ -45,13 +45,6 @@
       EncodedImageDataExtractor* extractor);
   ~VideoQualityAnalyzerInjectionHelper() override;
 
-  // Registers new call participant to the underlying video quality analyzer.
-  // The method should be called before the participant is actually added.
-  void RegisterParticipantInCall(absl::string_view peer_name) {
-    analyzer_->RegisterParticipantInCall(peer_name);
-    extractor_->AddParticipantInCall();
-  }
-
   // Wraps video encoder factory to give video quality analyzer access to frames
   // before encoding and encoded images after.
   std::unique_ptr<VideoEncoderFactory> WrapVideoEncoderFactory(
@@ -83,6 +76,9 @@
   void Start(std::string test_case_name,
              rtc::ArrayView<const std::string> peer_names,
              int max_threads_count = 1);
+  // Registers new call participant to the underlying video quality analyzer.
+  // The method should be called before the participant is actually added.
+  void RegisterParticipantInCall(absl::string_view peer_name);
 
   // Forwards `stats_reports` for Peer Connection `pc_label` to
   // `analyzer_`.
@@ -111,6 +107,23 @@
     VideoQualityAnalyzerInjectionHelper* const helper_;
   };
 
+  struct ReceiverStream {
+    ReceiverStream(absl::string_view peer_name, absl::string_view stream_label)
+        : peer_name(peer_name), stream_label(stream_label) {}
+
+    std::string peer_name;
+    std::string stream_label;
+
+    // Define operators required to use ReceiverStream as std::map key.
+    bool operator==(const ReceiverStream& o) const {
+      return peer_name == o.peer_name && stream_label == o.stream_label;
+    }
+    bool operator<(const ReceiverStream& o) const {
+      return (peer_name == o.peer_name) ? stream_label < o.stream_label
+                                        : peer_name < o.peer_name;
+    }
+  };
+
   test::VideoFrameWriter* MaybeCreateVideoWriter(
       absl::optional<std::string> file_name,
       const PeerConnectionE2EQualityTestFixture::VideoConfig& config);
@@ -118,7 +131,7 @@
   // passing real frame to the sinks
   void OnFrame(absl::string_view peer_name, const VideoFrame& frame);
   std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>*
-  PopulateSinks(const std::string& stream_label);
+  PopulateSinks(const ReceiverStream& receiver_stream);
 
   std::unique_ptr<VideoQualityAnalyzerInterface> analyzer_;
   EncodedImageDataInjector* injector_;
@@ -126,11 +139,14 @@
 
   std::vector<std::unique_ptr<test::VideoFrameWriter>> video_writers_;
 
-  Mutex lock_;
-  std::map<std::string, VideoConfig> known_video_configs_ RTC_GUARDED_BY(lock_);
-  std::map<std::string,
+  Mutex mutex_;
+  int peers_count_ RTC_GUARDED_BY(mutex_);
+  // Map from stream label to the video config.
+  std::map<std::string, VideoConfig> known_video_configs_
+      RTC_GUARDED_BY(mutex_);
+  std::map<ReceiverStream,
            std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>>
-      sinks_ RTC_GUARDED_BY(lock_);
+      sinks_ RTC_GUARDED_BY(mutex_);
 };
 
 }  // namespace webrtc_pc_e2e