Add support for multiple peers in SingleProcessEncodedImageDataInjector

Bug: webrtc:11779
Change-Id: Ie59e39e7fa903432ec13400b1c3e0e1456e812fc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/180127
Reviewed-by: Andrey Logvin <landrey@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31790}
diff --git a/test/pc/e2e/analyzer/video/default_encoded_image_data_injector.h b/test/pc/e2e/analyzer/video/default_encoded_image_data_injector.h
index 6ed87f5..b60c214 100644
--- a/test/pc/e2e/analyzer/video/default_encoded_image_data_injector.h
+++ b/test/pc/e2e/analyzer/video/default_encoded_image_data_injector.h
@@ -63,6 +63,8 @@
                           bool discard,
                           const EncodedImage& source,
                           int /*coding_entity_id*/) override;
+
+  void Start(int expected_receivers_count) override {}
   EncodedImageExtractionResult ExtractData(const EncodedImage& source,
                                            int coding_entity_id) override;
 };
diff --git a/test/pc/e2e/analyzer/video/default_encoded_image_data_injector_unittest.cc b/test/pc/e2e/analyzer/video/default_encoded_image_data_injector_unittest.cc
index 3ad978f..045a655 100644
--- a/test/pc/e2e/analyzer/video/default_encoded_image_data_injector_unittest.cc
+++ b/test/pc/e2e/analyzer/video/default_encoded_image_data_injector_unittest.cc
@@ -32,6 +32,7 @@
 
 TEST(DefaultEncodedImageDataInjector, InjectExtractDiscardFalse) {
   DefaultEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
 
@@ -50,6 +51,7 @@
 
 TEST(DefaultEncodedImageDataInjector, InjectExtractDiscardTrue) {
   DefaultEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
 
@@ -65,6 +67,7 @@
 
 TEST(DefaultEncodedImageDataInjector, Inject3Extract3) {
   DefaultEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
   rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11);
@@ -108,6 +111,7 @@
 
 TEST(DefaultEncodedImageDataInjector, InjectExtractFromConcatenated) {
   DefaultEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
   rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11);
@@ -151,6 +155,7 @@
 TEST(DefaultEncodedImageDataInjector,
      InjectExtractFromConcatenatedAllDiscarded) {
   DefaultEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
   rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11);
diff --git a/test/pc/e2e/analyzer/video/encoded_image_data_injector.h b/test/pc/e2e/analyzer/video/encoded_image_data_injector.h
index 8e21862..ddd6959 100644
--- a/test/pc/e2e/analyzer/video/encoded_image_data_injector.h
+++ b/test/pc/e2e/analyzer/video/encoded_image_data_injector.h
@@ -47,6 +47,11 @@
  public:
   virtual ~EncodedImageDataExtractor() = default;
 
+  // Invoked by framework before any image will come to the extractor.
+  // |expected_receivers_count| is the expected amount of receivers for each
+  // encoded image.
+  virtual void Start(int expected_receivers_count) = 0;
+
   // Returns encoded image id, extracted from payload and also encoded image
   // with its original payload. For concatenated spatial layers it should be the
   // same id. |coding_entity_id| is unique id of decoder or encoder.
diff --git a/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc b/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc
index 8e7a511..419a7be 100644
--- a/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc
+++ b/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc
@@ -95,16 +95,17 @@
     {
       MutexLock lock(&lock_);
       auto ext_vector_it = extraction_cache_.find(next_id);
-      // TODO(titovartem) add support for receiving single frame multiple times
-      // when in simulcast key frame for another spatial stream can be received.
       RTC_CHECK(ext_vector_it != extraction_cache_.end())
           << "Unknown frame_id=" << next_id;
 
       auto info_it = ext_vector_it->second.infos.find(sub_id);
       RTC_CHECK(info_it != ext_vector_it->second.infos.end())
           << "Unknown sub_id=" << sub_id << " for frame_id=" << next_id;
+      info_it->second.received_count++;
       info = info_it->second;
-      ext_vector_it->second.infos.erase(info_it);
+      if (info.received_count == expected_receivers_count_) {
+        ext_vector_it->second.infos.erase(info_it);
+      }
     }
     // We need to discard encoded image only if all concatenated encoded images
     // have to be discarded.
diff --git a/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h b/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h
index c70c25b..8cf1bc4 100644
--- a/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h
+++ b/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h
@@ -50,6 +50,11 @@
                           bool discard,
                           const EncodedImage& source,
                           int coding_entity_id) override;
+
+  void Start(int expected_receivers_count) override {
+    MutexLock crit(&lock_);
+    expected_receivers_count_ = expected_receivers_count;
+  }
   EncodedImageExtractionResult ExtractData(const EncodedImage& source,
                                            int coding_entity_id) override;
 
@@ -67,6 +72,8 @@
     bool discard;
     // Data from first 3 bytes of origin encoded image's payload.
     uint8_t origin_data[ExtractionInfo::kUsedBufferSize];
+    // Count of how many times this frame was received.
+    int received_count = 0;
   };
 
   struct ExtractionInfoVector {
@@ -79,6 +86,7 @@
   };
 
   Mutex lock_;
+  int expected_receivers_count_ RTC_GUARDED_BY(lock_);
   // Stores a mapping from frame id to extraction info for spatial layers
   // for this frame id. There can be a lot of them, because if frame was
   // dropped we can't clean it up, because we won't receive a signal on
diff --git a/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc b/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc
index e25361e..00197f3 100644
--- a/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc
+++ b/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc
@@ -28,10 +28,9 @@
   return buffer;
 }
 
-}  // namespace
-
 TEST(SingleProcessEncodedImageDataInjector, InjectExtractDiscardFalse) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
 
@@ -52,6 +51,7 @@
 
 TEST(SingleProcessEncodedImageDataInjector, InjectExtractDiscardTrue) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
 
@@ -69,6 +69,7 @@
 
 TEST(SingleProcessEncodedImageDataInjector, InjectWithUnsetSpatialLayerSizes) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
 
@@ -94,6 +95,7 @@
 
 TEST(SingleProcessEncodedImageDataInjector, InjectWithZeroSpatialLayerSizes) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
 
@@ -122,6 +124,7 @@
 
 TEST(SingleProcessEncodedImageDataInjector, Inject3Extract3) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
   rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11);
@@ -171,6 +174,7 @@
 
 TEST(SingleProcessEncodedImageDataInjector, InjectExtractFromConcatenated) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
   rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11);
@@ -223,6 +227,7 @@
 TEST(SingleProcessEncodedImageDataInjector,
      InjectExtractFromConcatenatedAllDiscarded) {
   SingleProcessEncodedImageDataInjector injector;
+  injector.Start(1);
 
   rtc::Buffer buffer1 = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
   rtc::Buffer buffer2 = CreateBufferOfSizeNFilledWithValuesFromX(10, 11);
@@ -268,5 +273,72 @@
   }
 }
 
+TEST(SingleProcessEncodedImageDataInjector, InjectOnceExtractTwice) {
+  SingleProcessEncodedImageDataInjector injector;
+  injector.Start(2);
+
+  rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
+
+  EncodedImage source(buffer.data(), 10, 10);
+  source.SetTimestamp(123456789);
+
+  EncodedImageExtractionResult out =
+      injector.ExtractData(injector.InjectData(/*id=*/512, /*discard=*/false,
+                                               source, /*coding_entity_id=*/1),
+                           /*coding_entity_id=*/2);
+  EXPECT_EQ(out.id, 512);
+  EXPECT_FALSE(out.discard);
+  EXPECT_EQ(out.image.size(), 10ul);
+  EXPECT_EQ(out.image.capacity(), 10ul);
+  EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul);
+  for (int i = 0; i < 10; ++i) {
+    EXPECT_EQ(out.image.data()[i], i + 1);
+  }
+  out =
+      injector.ExtractData(injector.InjectData(/*id=*/512, /*discard=*/false,
+                                               source, /*coding_entity_id=*/1),
+                           2);
+  EXPECT_EQ(out.id, 512);
+  EXPECT_FALSE(out.discard);
+  EXPECT_EQ(out.image.size(), 10ul);
+  EXPECT_EQ(out.image.capacity(), 10ul);
+  EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul);
+  for (int i = 0; i < 10; ++i) {
+    EXPECT_EQ(out.image.data()[i], i + 1);
+  }
+}
+
+// Death tests.
+// Disabled on Android because death tests misbehave on Android, see
+// base/test/gtest_util.h.
+#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+EncodedImage DeepCopyEncodedImage(const EncodedImage& source) {
+  EncodedImage copy = source;
+  copy.SetEncodedData(EncodedImageBuffer::Create(source.size()));
+  memcpy(copy.data(), source.data(), source.size());
+  return copy;
+}
+
+TEST(SingleProcessEncodedImageDataInjector, InjectOnceExtractMoreThenExpected) {
+  SingleProcessEncodedImageDataInjector injector;
+  injector.Start(2);
+
+  rtc::Buffer buffer = CreateBufferOfSizeNFilledWithValuesFromX(10, 1);
+
+  EncodedImage source(buffer.data(), 10, 10);
+  source.SetTimestamp(123456789);
+
+  EncodedImage modified = injector.InjectData(/*id=*/512, /*discard=*/false,
+                                              source, /*coding_entity_id=*/1);
+
+  injector.ExtractData(DeepCopyEncodedImage(modified), /*coding_entity_id=*/2);
+  injector.ExtractData(DeepCopyEncodedImage(modified), /*coding_entity_id=*/2);
+  EXPECT_DEATH(injector.ExtractData(DeepCopyEncodedImage(modified),
+                                    /*coding_entity_id=*/2),
+               "Unknown sub_id=0 for frame_id=512");
+}
+#endif  // RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+
+}  // namespace
 }  // namespace webrtc_pc_e2e
 }  // namespace webrtc
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 48e65ef..ebfb416 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
@@ -149,6 +149,7 @@
     rtc::ArrayView<const std::string> peer_names,
     int max_threads_count) {
   analyzer_->Start(std::move(test_case_name), peer_names, max_threads_count);
+  extractor_->Start(peer_names.size());
 }
 
 void VideoQualityAnalyzerInjectionHelper::OnStatsReports(