Add screen share support to PC level test framework

Bug: webrtc:10138
Change-Id: I1a8ac683e91f8061387f407610d7db2a6d0d4fe9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/136805
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Bjorn Mellem <mellem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27950}
diff --git a/api/test/peerconnection_quality_test_fixture.h b/api/test/peerconnection_quality_test_fixture.h
index 76de3d0..3955da5 100644
--- a/api/test/peerconnection_quality_test_fixture.h
+++ b/api/test/peerconnection_quality_test_fixture.h
@@ -13,6 +13,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "absl/memory/memory.h"
@@ -40,19 +41,72 @@
 namespace webrtc {
 namespace webrtc_pc_e2e {
 
+constexpr size_t kDefaultSlidesWidth = 1850;
+constexpr size_t kDefaultSlidesHeight = 1110;
+
 // API is in development. Can be changed/removed without notice.
 class PeerConnectionE2EQualityTestFixture {
  public:
+  // Contains parameters for screen share scrolling.
+  //
+  // If scrolling is enabled, then it will be done by putting sliding window
+  // on source video and moving this window from top left corner to the
+  // bottom right corner of the picture.
+  //
+  // In such case source dimensions must be greater or equal to the sliding
+  // window dimensions. So |source_width| and |source_height| are the dimensions
+  // of the source frame, while |VideoConfig::width| and |VideoConfig::height|
+  // are the dimensions of the sliding window.
+  //
+  // Because |source_width| and |source_height| are dimensions of the source
+  // frame, they have to be width and height of videos from
+  // |ScreenShareConfig::slides_yuv_file_names|.
+  //
+  // Because scrolling have to be done on single slide it also requires, that
+  // |duration| must be less or equal to
+  // |ScreenShareConfig::slide_change_interval|.
+  struct ScrollingParams {
+    ScrollingParams(TimeDelta duration,
+                    size_t source_width,
+                    size_t source_height)
+        : duration(duration),
+          source_width(source_width),
+          source_height(source_height) {
+      RTC_CHECK_GT(duration.ms(), 0);
+    }
+
+    // Duration of scrolling.
+    TimeDelta duration;
+    // Width of source slides video.
+    size_t source_width;
+    // Height of source slides video.
+    size_t source_height;
+  };
+
   // Contains screen share video stream properties.
   struct ScreenShareConfig {
-    // If true, slides will be generated programmatically.
-    bool generate_slides;
+    explicit ScreenShareConfig(TimeDelta slide_change_interval)
+        : slide_change_interval(slide_change_interval) {
+      RTC_CHECK_GT(slide_change_interval.ms(), 0);
+    }
+
     // Shows how long one slide should be presented on the screen during
     // slide generation.
     TimeDelta slide_change_interval;
-    // If equal to 0, no scrolling will be applied.
-    TimeDelta scroll_duration;
-    // If empty, default set of slides will be used.
+    // If true, slides will be generated programmatically. No scrolling params
+    // will be applied in such case.
+    bool generate_slides = false;
+    // If present scrolling will be applied. Please read extra requirement on
+    // |slides_yuv_file_names| for scrolling.
+    absl::optional<ScrollingParams> scrolling_params;
+    // Contains list of yuv files with slides.
+    //
+    // If empty, default set of slides will be used. In such case
+    // |VideoConfig::width| must be equal to |kDefaultSlidesWidth| and
+    // |VideoConfig::height| must be equal to |kDefaultSlidesHeight| or if
+    // |scrolling_params| are specified, then |ScrollingParams::source_width|
+    // must be equal to |kDefaultSlidesWidth| and
+    // |ScrollingParams::source_height| must be equal to |kDefaultSlidesHeight|.
     std::vector<std::string> slides_yuv_file_names;
   };
 
@@ -63,7 +117,9 @@
     VideoConfig(size_t width, size_t height, int32_t fps)
         : width(width), height(height), fps(fps) {}
 
+    // Video stream width.
     const size_t width;
+    // Video stream height.
     const size_t height;
     const int32_t fps;
     // Have to be unique among all specified configs for all peers in the call.
diff --git a/test/pc/e2e/peer_connection_e2e_smoke_test.cc b/test/pc/e2e/peer_connection_e2e_smoke_test.cc
index f29746f..fab4a90 100644
--- a/test/pc/e2e/peer_connection_e2e_smoke_test.cc
+++ b/test/pc/e2e/peer_connection_e2e_smoke_test.cc
@@ -37,6 +37,9 @@
   using RunParams = PeerConnectionE2EQualityTestFixture::RunParams;
   using VideoConfig = PeerConnectionE2EQualityTestFixture::VideoConfig;
   using AudioConfig = PeerConnectionE2EQualityTestFixture::AudioConfig;
+  using ScreenShareConfig =
+      PeerConnectionE2EQualityTestFixture::ScreenShareConfig;
+  using ScrollingParams = PeerConnectionE2EQualityTestFixture::ScrollingParams;
 
   // Setup emulated network
   std::unique_ptr<NetworkEmulationManager> network_emulation_manager =
@@ -87,32 +90,43 @@
           {alice_endpoint});
   fixture->AddPeer(alice_network->network_thread(),
                    alice_network->network_manager(), [](PeerConfigurer* alice) {
-                     VideoConfig video_config(640, 360, 30);
-                     video_config.stream_label = "alice-video";
-                     alice->AddVideoConfig(std::move(video_config));
-                     AudioConfig audio_config;
-                     audio_config.stream_label = "alice-audio";
-                     audio_config.mode = AudioConfig::Mode::kFile;
-                     audio_config.input_file_name = test::ResourcePath(
+                     VideoConfig video(640, 360, 30);
+                     video.stream_label = "alice-video";
+                     alice->AddVideoConfig(std::move(video));
+
+                     AudioConfig audio;
+                     audio.stream_label = "alice-audio";
+                     audio.mode = AudioConfig::Mode::kFile;
+                     audio.input_file_name = test::ResourcePath(
                          "pc_quality_smoke_test_alice_source", "wav");
-                     alice->SetAudioConfig(std::move(audio_config));
+                     alice->SetAudioConfig(std::move(audio));
                    });
 
   EmulatedNetworkManagerInterface* bob_network =
       network_emulation_manager->CreateEmulatedNetworkManagerInterface(
           {bob_endpoint});
-  fixture->AddPeer(bob_network->network_thread(),
-                   bob_network->network_manager(), [](PeerConfigurer* bob) {
-                     VideoConfig video_config(640, 360, 30);
-                     video_config.stream_label = "bob-video";
-                     bob->AddVideoConfig(std::move(video_config));
-                     AudioConfig audio_config;
-                     audio_config.stream_label = "bob-audio";
-                     audio_config.mode = AudioConfig::Mode::kFile;
-                     audio_config.input_file_name = test::ResourcePath(
-                         "pc_quality_smoke_test_bob_source", "wav");
-                     bob->SetAudioConfig(std::move(audio_config));
-                   });
+  fixture->AddPeer(
+      bob_network->network_thread(), bob_network->network_manager(),
+      [](PeerConfigurer* bob) {
+        VideoConfig video(640, 360, 30);
+        video.stream_label = "bob-video";
+        bob->AddVideoConfig(std::move(video));
+
+        VideoConfig screenshare(640, 360, 30);
+        screenshare.stream_label = "bob-screenshare";
+        screenshare.screen_share_config =
+            ScreenShareConfig(TimeDelta::seconds(2));
+        screenshare.screen_share_config->scrolling_params = ScrollingParams(
+            TimeDelta::ms(1800), kDefaultSlidesWidth, kDefaultSlidesHeight);
+        bob->AddVideoConfig(screenshare);
+
+        AudioConfig audio;
+        audio.stream_label = "bob-audio";
+        audio.mode = AudioConfig::Mode::kFile;
+        audio.input_file_name =
+            test::ResourcePath("pc_quality_smoke_test_bob_source", "wav");
+        bob->SetAudioConfig(std::move(audio));
+      });
 
   fixture->AddQualityMetricsReporter(
       absl::make_unique<NetworkQualityMetricsReporter>(alice_network,
diff --git a/test/pc/e2e/peer_connection_quality_test.cc b/test/pc/e2e/peer_connection_quality_test.cc
index 325ac39..ecc909b 100644
--- a/test/pc/e2e/peer_connection_quality_test.cc
+++ b/test/pc/e2e/peer_connection_quality_test.cc
@@ -461,6 +461,37 @@
           << VideoConfigSourcePresenceToString(video_config);
       RTC_CHECK(!(video_config.screen_share_config && video_config.generator))
           << VideoConfigSourcePresenceToString(video_config);
+
+      if (video_config.screen_share_config) {
+        if (video_config.screen_share_config->slides_yuv_file_names.empty()) {
+          if (video_config.screen_share_config->scrolling_params) {
+            // If we have scrolling params, then its |source_width| and
+            // |source_heigh| will be used as width and height of video input,
+            // so we have to validate it against width and height of default
+            // input.
+            RTC_CHECK_EQ(video_config.screen_share_config->scrolling_params
+                             ->source_width,
+                         kDefaultSlidesWidth);
+            RTC_CHECK_EQ(video_config.screen_share_config->scrolling_params
+                             ->source_height,
+                         kDefaultSlidesHeight);
+          } else {
+            RTC_CHECK_EQ(video_config.width, kDefaultSlidesWidth);
+            RTC_CHECK_EQ(video_config.height, kDefaultSlidesHeight);
+          }
+        }
+        if (video_config.screen_share_config->scrolling_params) {
+          RTC_CHECK_LE(
+              video_config.screen_share_config->scrolling_params->duration,
+              video_config.screen_share_config->slide_change_interval);
+          RTC_CHECK_GE(
+              video_config.screen_share_config->scrolling_params->source_width,
+              video_config.width);
+          RTC_CHECK_GE(
+              video_config.screen_share_config->scrolling_params->source_height,
+              video_config.height);
+        }
+      }
     }
     if (p->audio_config) {
       bool inserted =
@@ -616,15 +647,55 @@
         video_config.width, video_config.height, /*frame_repeat_count=*/1);
   }
   if (video_config.screen_share_config) {
-    // TODO(titovartem) implement screen share support
-    // (http://bugs.webrtc.org/10138)
-    RTC_NOTREACHED() << "Screen share is not implemented";
-    return nullptr;
+    return CreateScreenShareFrameGenerator(video_config);
   }
   RTC_NOTREACHED() << "Unsupported video_config input source";
   return nullptr;
 }
 
+std::unique_ptr<test::FrameGenerator>
+PeerConnectionE2EQualityTest::CreateScreenShareFrameGenerator(
+    const VideoConfig& video_config) {
+  RTC_CHECK(video_config.screen_share_config);
+  if (video_config.screen_share_config->generate_slides) {
+    return test::FrameGenerator::CreateSlideGenerator(
+        video_config.width, video_config.height,
+        video_config.screen_share_config->slide_change_interval.seconds() *
+            video_config.fps);
+  }
+  std::vector<std::string> slides =
+      video_config.screen_share_config->slides_yuv_file_names;
+  if (slides.empty()) {
+    // If slides is empty we need to add default slides as source. In such case
+    // video width and height is validated to be equal to kDefaultSlidesWidth
+    // and kDefaultSlidesHeight.
+    slides.push_back(test::ResourcePath("web_screenshot_1850_1110", "yuv"));
+    slides.push_back(test::ResourcePath("presentation_1850_1110", "yuv"));
+    slides.push_back(test::ResourcePath("photo_1850_1110", "yuv"));
+    slides.push_back(test::ResourcePath("difficult_photo_1850_1110", "yuv"));
+  }
+  if (!video_config.screen_share_config->scrolling_params) {
+    // Cycle image every slide_change_interval seconds.
+    return test::FrameGenerator::CreateFromYuvFile(
+        slides, video_config.width, video_config.height,
+        video_config.screen_share_config->slide_change_interval.seconds() *
+            video_config.fps);
+  }
+
+  // |pause_duration| is nonnegative. It is validated in ValidateParams(...).
+  TimeDelta pause_duration =
+      video_config.screen_share_config->slide_change_interval -
+      video_config.screen_share_config->scrolling_params->duration;
+
+  return test::FrameGenerator::CreateScrollingInputFromYuvFiles(
+      clock_, slides,
+      video_config.screen_share_config->scrolling_params->source_width,
+      video_config.screen_share_config->scrolling_params->source_height,
+      video_config.width, video_config.height,
+      video_config.screen_share_config->scrolling_params->duration.ms(),
+      pause_duration.ms());
+}
+
 void PeerConnectionE2EQualityTest::MaybeAddAudio(TestPeer* peer) {
   if (!peer->params()->audio_config) {
     return;
diff --git a/test/pc/e2e/peer_connection_quality_test.h b/test/pc/e2e/peer_connection_quality_test.h
index 3b39916..5db8f03 100644
--- a/test/pc/e2e/peer_connection_quality_test.h
+++ b/test/pc/e2e/peer_connection_quality_test.h
@@ -219,6 +219,8 @@
   MaybeAddVideo(TestPeer* peer);
   std::unique_ptr<test::FrameGenerator> CreateFrameGenerator(
       const VideoConfig& video_config);
+  std::unique_ptr<test::FrameGenerator> CreateScreenShareFrameGenerator(
+      const VideoConfig& video_config);
   void MaybeAddAudio(TestPeer* peer);
   void SetPeerCodecPreferences(TestPeer* peer, const RunParams& run_params);
   void SetupCall();