Add a new frame generator that cycles through randomly generated slides.

Like YuvFileGenerator, this also updates the display with a new slide on every Nth frame, but it generates the slides itself instead of reading them from files.

BUG=webrtc:8138

Review-Url: https://codereview.webrtc.org/3003193002
Cr-Original-Commit-Position: refs/heads/master@{#19585}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: 579de6faef60ebe6e60a795041fecb92569d4f72
diff --git a/test/frame_generator.cc b/test/frame_generator.cc
index 52ae14f..1956cdb 100644
--- a/test/frame_generator.cc
+++ b/test/frame_generator.cc
@@ -181,6 +181,85 @@
   std::unique_ptr<VideoFrame> temp_frame_;
 };
 
+// SlideGenerator works similarly to YuvFileGenerator but it fills the frames
+// with randomly sized and colored squares instead of reading their content
+// from files.
+class SlideGenerator : public FrameGenerator {
+ public:
+  SlideGenerator(int width, int height, int frame_repeat_count)
+      : width_(width),
+        height_(height),
+        frame_display_count_(frame_repeat_count),
+        current_display_count_(0),
+        random_generator_(1234) {
+    RTC_DCHECK_GT(width, 0);
+    RTC_DCHECK_GT(height, 0);
+    RTC_DCHECK_GT(frame_repeat_count, 0);
+  }
+
+  VideoFrame* NextFrame() override {
+    if (current_display_count_ == 0)
+      GenerateNewFrame();
+    if (++current_display_count_ >= frame_display_count_)
+      current_display_count_ = 0;
+
+    frame_.reset(
+        new VideoFrame(buffer_, 0, 0, webrtc::kVideoRotation_0));
+    return frame_.get();
+  }
+
+  // Generates some randomly sized and colored squares scattered
+  // over the frame.
+  void GenerateNewFrame() {
+    // The squares should have a varying order of magnitude in order
+    // to simulate variation in the slides' complexity.
+    const int kSquareNum =  1 << (4 + (random_generator_.Rand(0, 3) * 4));
+
+    buffer_ = I420Buffer::Create(width_, height_);
+    memset(buffer_->MutableDataY(), 127, height_ * buffer_->StrideY());
+    memset(buffer_->MutableDataU(), 127,
+           buffer_->ChromaHeight() * buffer_->StrideU());
+    memset(buffer_->MutableDataV(), 127,
+           buffer_->ChromaHeight() * buffer_->StrideV());
+
+    for (int i = 0; i < kSquareNum; ++i) {
+      int length = random_generator_.Rand(1, width_ > 4 ? width_ / 4 : 1);
+      // Limit the length of later squares so that they don't overwrite the
+      // previous ones too much.
+      length = (length * (kSquareNum - i)) / kSquareNum;
+
+      int x = random_generator_.Rand(0, width_ - length);
+      int y = random_generator_.Rand(0, height_ - length);
+      uint8_t yuv_y = random_generator_.Rand(0, 255);
+      uint8_t yuv_u = random_generator_.Rand(0, 255);
+      uint8_t yuv_v = random_generator_.Rand(0, 255);
+
+      for (int yy = y; yy < y + length; ++yy) {
+        uint8_t* pos_y =
+            (buffer_->MutableDataY() + x + yy * buffer_->StrideY());
+        memset(pos_y, yuv_y, length);
+      }
+      for (int yy = y; yy < y + length; yy += 2) {
+        uint8_t* pos_u =
+            (buffer_->MutableDataU() + x / 2 + yy / 2 * buffer_->StrideU());
+        memset(pos_u, yuv_u, length / 2);
+        uint8_t* pos_v =
+            (buffer_->MutableDataV() + x / 2 + yy / 2 * buffer_->StrideV());
+        memset(pos_v, yuv_v, length / 2);
+      }
+    }
+  }
+
+ private:
+  const int width_;
+  const int height_;
+  const int frame_display_count_;
+  int current_display_count_;
+  Random random_generator_;
+  rtc::scoped_refptr<I420Buffer> buffer_;
+  std::unique_ptr<VideoFrame> frame_;
+};
+
 class ScrollingImageFrameGenerator : public FrameGenerator {
  public:
   ScrollingImageFrameGenerator(Clock* clock,
@@ -321,6 +400,12 @@
   return std::unique_ptr<FrameGenerator>(new SquareGenerator(width, height));
 }
 
+std::unique_ptr<FrameGenerator> FrameGenerator::CreateSlideGenerator(
+    int width, int height, int frame_repeat_count) {
+  return std::unique_ptr<FrameGenerator>(new SlideGenerator(
+      width, height, frame_repeat_count));
+}
+
 std::unique_ptr<FrameGenerator> FrameGenerator::CreateFromYuvFile(
     std::vector<std::string> filenames,
     size_t width,
diff --git a/test/frame_generator.h b/test/frame_generator.h
index 6b0137d..dfe35b8 100644
--- a/test/frame_generator.h
+++ b/test/frame_generator.h
@@ -89,6 +89,11 @@
       size_t target_height,
       int64_t scroll_time_ms,
       int64_t pause_time_ms);
+
+  // Creates a frame generator that produces randomly generated slides.
+  // frame_repeat_count determines how many times each slide is shown.
+  static std::unique_ptr<FrameGenerator> CreateSlideGenerator(
+      int width, int height, int frame_repeat_count);
 };
 }  // namespace test
 }  // namespace webrtc
diff --git a/test/frame_generator_capturer.cc b/test/frame_generator_capturer.cc
index 4f1937b..095a204 100644
--- a/test/frame_generator_capturer.cc
+++ b/test/frame_generator_capturer.cc
@@ -114,6 +114,22 @@
   return capturer.release();
 }
 
+FrameGeneratorCapturer* FrameGeneratorCapturer::CreateSlideGenerator(
+    int width,
+    int height,
+    int frame_repeat_count,
+    int target_fps,
+    Clock* clock) {
+  std::unique_ptr<FrameGeneratorCapturer> capturer(new FrameGeneratorCapturer(
+      clock, FrameGenerator::CreateSlideGenerator(width, height,
+                                                  frame_repeat_count),
+      target_fps));
+  if (!capturer->Init())
+    return nullptr;
+
+  return capturer.release();
+}
+
 FrameGeneratorCapturer::FrameGeneratorCapturer(
     Clock* clock,
     std::unique_ptr<FrameGenerator> frame_generator,
diff --git a/test/frame_generator_capturer.h b/test/frame_generator_capturer.h
index f62afba..4425121 100644
--- a/test/frame_generator_capturer.h
+++ b/test/frame_generator_capturer.h
@@ -50,6 +50,12 @@
                                                    size_t height,
                                                    int target_fps,
                                                    Clock* clock);
+
+  static FrameGeneratorCapturer* CreateSlideGenerator(int width,
+                                                      int height,
+                                                      int frame_repeat_count,
+                                                      int target_fps,
+                                                      Clock* clock);
   virtual ~FrameGeneratorCapturer();
 
   void Start() override;
diff --git a/test/frame_generator_unittest.cc b/test/frame_generator_unittest.cc
index 1ead698..79e7b63 100644
--- a/test/frame_generator_unittest.cc
+++ b/test/frame_generator_unittest.cc
@@ -81,6 +81,26 @@
     frame->set_timestamp(13);
   }
 
+  uint64_t Hash(VideoFrame* frame) {
+    // Generate a 64-bit hash from the frame's buffer.
+    uint64_t hash = 19;
+    rtc::scoped_refptr<I420BufferInterface> i420_buffer =
+        frame->video_frame_buffer()->ToI420();
+    const uint8_t* buffer = i420_buffer->DataY();
+    for (int i = 0; i < y_size; ++i) {
+      hash = (37 * hash) + buffer[i];
+    }
+    buffer = i420_buffer->DataU();
+    for (int i = 0; i < uv_size; ++i) {
+      hash = (37 * hash) + buffer[i];
+    }
+    buffer = i420_buffer->DataV();
+    for (int i = 0; i < uv_size; ++i) {
+      hash = (37 * hash) + buffer[i];
+    }
+    return hash;
+  }
+
   std::string two_frame_filename_;
   std::string one_frame_filename_;
   const int y_size = kFrameWidth * kFrameHeight;
@@ -145,5 +165,25 @@
   CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0);
 }
 
+TEST_F(FrameGeneratorTest, SlideGenerator) {
+  const int kGenCount = 9;
+  const int kRepeatCount = 3;
+  std::unique_ptr<FrameGenerator> generator(
+      FrameGenerator::CreateSlideGenerator(
+          kFrameWidth, kFrameHeight, kRepeatCount));
+  uint64_t hashes[kGenCount];
+  for (int i = 0; i < kGenCount; ++i) {
+    hashes[i] = Hash(generator->NextFrame());
+  }
+  // Check that the buffer changes only every |kRepeatCount| frames.
+  for (int i = 1; i < kGenCount; ++i) {
+    if (i % kRepeatCount == 0) {
+      EXPECT_NE(hashes[i-1], hashes[i]);
+    } else {
+      EXPECT_EQ(hashes[i-1], hashes[i]);
+    }
+  }
+}
+
 }  // namespace test
 }  // namespace webrtc
diff --git a/video/full_stack_tests.cc b/video/full_stack_tests.cc
index 8190bf6..bd694bc 100644
--- a/video/full_stack_tests.cc
+++ b/video/full_stack_tests.cc
@@ -342,7 +342,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   RunTest(screenshare);
@@ -352,7 +352,7 @@
   test::ScopedFieldTrials field_trial(kScreenshareSimulcastExperiment);
   VideoQualityTest::Params screenshare;
   screenshare.call.send_side_bwe = true;
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.video = {true,    1850,    1110,  5,     800000,
                        2500000, 2500000, false, "VP8", 3,
                        2,       400000,  false, false, ""};
@@ -379,7 +379,7 @@
   config.call.send_side_bwe = true;
   config.video = {true,  1850, 1110 / 2, 5,      50000, 200000, 2000000, false,
                   "VP8", 2,    1,        400000, false, false,  ""};
-  config.screenshare = {true, 10, 2};
+  config.screenshare = {true, false, 10, 2};
   config.analyzer = {"screenshare_slides_scrolling", 0.0, 0.0,
                      kFullStackTestDurationSecs};
   RunTest(config);
@@ -390,7 +390,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_lossy_net", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   screenshare.pipe.loss_percent = 5;
@@ -404,7 +404,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_very_lossy", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   screenshare.pipe.loss_percent = 10;
@@ -418,7 +418,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_lossy_limited", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   screenshare.pipe.loss_percent = 5;
@@ -433,7 +433,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_moderately_restricted", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   screenshare.pipe.loss_percent = 1;
@@ -450,7 +450,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_lossy_limited_ALR", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   screenshare.pipe.loss_percent = 5;
@@ -466,7 +466,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_ALR", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   RunTest(screenshare);
@@ -478,7 +478,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP8", 2,    1,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_moderately_restricted_ALR", 0.0,
                           0.0, kFullStackTestDurationSecs};
   screenshare.pipe.loss_percent = 1;
@@ -493,7 +493,7 @@
                                       kAlrProbingExperiment);
   VideoQualityTest::Params screenshare;
   screenshare.call.send_side_bwe = true;
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.video = {true,    1850,    1110,  5,     800000,
                        2500000, 2500000, false, "VP8", 3,
                        2,       400000,  false, false, ""};
@@ -541,7 +541,7 @@
   screenshare.call.send_side_bwe = true;
   screenshare.video = {true,  1850, 1110, 5,      50000, 200000, 2000000, false,
                        "VP9", 1,    0,    400000, false, false,  ""};
-  screenshare.screenshare = {true, 10};
+  screenshare.screenshare = {true, false, 10};
   screenshare.analyzer = {"screenshare_slides_vp9_2sl", 0.0, 0.0,
                           kFullStackTestDurationSecs};
   screenshare.ss = {std::vector<VideoStream>(),  0,    2, 1,
diff --git a/video/screenshare_loopback.cc b/video/screenshare_loopback.cc
index d9de841..17fd8de 100644
--- a/video/screenshare_loopback.cc
+++ b/video/screenshare_loopback.cc
@@ -218,6 +218,13 @@
   return FLAG_min_transmit_bitrate;
 }
 
+DEFINE_bool(generate_slides,
+           false,
+           "Whether to use randomly generated slides or read them from files.");
+bool GenerateSlides() {
+  return static_cast<int>(FLAG_generate_slides);
+}
+
 DEFINE_int(slide_change_interval,
            10,
            "Interval (in seconds) between simulated slide changes.");
@@ -278,7 +285,8 @@
                   false,  // ULPFEC disabled.
                   false,  // FlexFEC disabled.
                   ""};
-  params.screenshare = {true, flags::SlideChangeInterval(),
+  params.screenshare = {true, flags::GenerateSlides(),
+                        flags::SlideChangeInterval(),
                         flags::ScrollDuration(), flags::Slides()};
   params.analyzer = {"screenshare", 0.0, 0.0, flags::DurationSecs(),
       flags::OutputFilename(), flags::GraphTitle()};
diff --git a/video/video_quality_test.cc b/video/video_quality_test.cc
index 1e7ce0f..2aa6295 100644
--- a/video/video_quality_test.cc
+++ b/video/video_quality_test.cc
@@ -1174,7 +1174,7 @@
       video({false, 640, 480, 30, 50, 800, 800, false, "VP8", 1, -1, 0, false,
              false, ""}),
       audio({false, false, false}),
-      screenshare({false, 10, 0}),
+      screenshare({false, false, 10, 0}),
       analyzer({"", 0.0, 0.0, 0, "", ""}),
       pipe(),
       ss({std::vector<VideoStream>(), 0, 0, -1, std::vector<SpatialLayer>()}),
@@ -1689,32 +1689,41 @@
     // Setup frame generator.
     const size_t kWidth = 1850;
     const size_t kHeight = 1110;
-    std::vector<std::string> slides = params_.screenshare.slides;
-    if (slides.size() == 0) {
-      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 (params_.screenshare.scroll_duration == 0) {
-      // Cycle image every slide_change_interval seconds.
-      frame_generator_ = test::FrameGenerator::CreateFromYuvFile(
-          slides, kWidth, kHeight,
+    if (params_.screenshare.generate_slides) {
+      frame_generator_ = test::FrameGenerator::CreateSlideGenerator(
+          kWidth, kHeight,
           params_.screenshare.slide_change_interval * params_.video.fps);
     } else {
-      RTC_CHECK_LE(params_.video.width, kWidth);
-      RTC_CHECK_LE(params_.video.height, kHeight);
-      RTC_CHECK_GT(params_.screenshare.slide_change_interval, 0);
-      const int kPauseDurationMs = (params_.screenshare.slide_change_interval -
-                                    params_.screenshare.scroll_duration) *
-                                   1000;
-      RTC_CHECK_LE(params_.screenshare.scroll_duration,
-                   params_.screenshare.slide_change_interval);
+      std::vector<std::string> slides = params_.screenshare.slides;
+      if (slides.size() == 0) {
+        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 (params_.screenshare.scroll_duration == 0) {
+        // Cycle image every slide_change_interval seconds.
+        frame_generator_ = test::FrameGenerator::CreateFromYuvFile(
+            slides, kWidth, kHeight,
+            params_.screenshare.slide_change_interval * params_.video.fps);
+      } else {
+        RTC_CHECK_LE(params_.video.width, kWidth);
+        RTC_CHECK_LE(params_.video.height, kHeight);
+        RTC_CHECK_GT(params_.screenshare.slide_change_interval, 0);
+        const int kPauseDurationMs =
+            (params_.screenshare.slide_change_interval -
+             params_.screenshare.scroll_duration) *
+            1000;
+        RTC_CHECK_LE(params_.screenshare.scroll_duration,
+                     params_.screenshare.slide_change_interval);
 
-      frame_generator_ = test::FrameGenerator::CreateScrollingInputFromYuvFiles(
-          clock_, slides, kWidth, kHeight, params_.video.width,
-          params_.video.height, params_.screenshare.scroll_duration * 1000,
-          kPauseDurationMs);
+        frame_generator_ =
+            test::FrameGenerator::CreateScrollingInputFromYuvFiles(
+                clock_, slides, kWidth, kHeight, params_.video.width,
+                params_.video.height,
+                params_.screenshare.scroll_duration * 1000, kPauseDurationMs);
+      }
     }
   } else if (params_.ss.num_spatial_layers > 1) {  // For non-screenshare case.
     RTC_CHECK(params_.video.codec == "VP9");
diff --git a/video/video_quality_test.h b/video/video_quality_test.h
index 3bb5cd2..4238447 100644
--- a/video/video_quality_test.h
+++ b/video/video_quality_test.h
@@ -61,6 +61,7 @@
     } audio;
     struct Screenshare {
       bool enabled;
+      bool generate_slides;
       int32_t slide_change_interval;
       int32_t scroll_duration;
       std::vector<std::string> slides;