Add a frame generator that allows scrolling over a larger still image, for use with new screen sharing quality tests.
Also add support for this in the loopback tests.

BUG=
R=pbos@webrtc.org

Review URL: https://codereview.webrtc.org/1256713004 .

Cr-Commit-Position: refs/heads/master@{#9652}
diff --git a/webrtc/test/frame_generator.cc b/webrtc/test/frame_generator.cc
index 55e5a41..9a14ff1 100644
--- a/webrtc/test/frame_generator.cc
+++ b/webrtc/test/frame_generator.cc
@@ -15,6 +15,7 @@
 
 #include "webrtc/base/checks.h"
 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
+#include "webrtc/system_wrappers/interface/clock.h"
 
 namespace webrtc {
 namespace test {
@@ -126,6 +127,108 @@
   VideoFrame last_read_frame_;
   VideoFrame temp_frame_copy_;
 };
+
+class ScrollingImageFrameGenerator : public FrameGenerator {
+ public:
+  ScrollingImageFrameGenerator(Clock* clock,
+                               const std::vector<FILE*>& files,
+                               size_t source_width,
+                               size_t source_height,
+                               size_t target_width,
+                               size_t target_height,
+                               int64_t scroll_time_ms,
+                               int64_t pause_time_ms)
+      : clock_(clock),
+        start_time_(clock->TimeInMilliseconds()),
+        scroll_time_(scroll_time_ms),
+        pause_time_(pause_time_ms),
+        num_frames_(files.size()),
+        current_frame_num_(num_frames_ - 1),
+        current_source_frame_(nullptr),
+        file_generator_(files, source_width, source_height, 1) {
+    DCHECK(clock_ != nullptr);
+    DCHECK_GT(num_frames_, 0u);
+    DCHECK_GE(source_height, target_height);
+    DCHECK_GE(source_width, target_width);
+    DCHECK_GE(scroll_time_ms, 0);
+    DCHECK_GE(pause_time_ms, 0);
+    DCHECK_GT(scroll_time_ms + pause_time_ms, 0);
+    current_frame_.CreateEmptyFrame(target_width, target_height, target_width,
+                                    (target_width + 1) / 2,
+                                    (target_width + 1) / 2);
+  }
+
+  virtual ~ScrollingImageFrameGenerator() {}
+
+  VideoFrame* NextFrame() override {
+    const int64_t kFrameDisplayTime = scroll_time_ + pause_time_;
+    const int64_t now = clock_->TimeInMilliseconds();
+    int64_t ms_since_start = now - start_time_;
+
+    size_t frame_num = (ms_since_start / kFrameDisplayTime) % num_frames_;
+    UpdateSourceFrame(frame_num);
+
+    double scroll_factor;
+    int64_t time_into_frame = ms_since_start % kFrameDisplayTime;
+    if (time_into_frame < scroll_time_) {
+      scroll_factor = static_cast<double>(time_into_frame) / scroll_time_;
+    } else {
+      scroll_factor = 1.0;
+    }
+    CropSourceToScrolledImage(scroll_factor);
+
+    return &current_frame_;
+  }
+
+  void UpdateSourceFrame(size_t frame_num) {
+    while (current_frame_num_ != frame_num) {
+      current_source_frame_ = file_generator_.NextFrame();
+      current_frame_num_ = (current_frame_num_ + 1) % num_frames_;
+    }
+    DCHECK(current_source_frame_ != nullptr);
+  }
+
+  void CropSourceToScrolledImage(double scroll_factor) {
+    const int kTargetWidth = current_frame_.width();
+    const int kTargetHeight = current_frame_.height();
+    int scroll_margin_x = current_source_frame_->width() - kTargetWidth;
+    int pixels_scrolled_x =
+        static_cast<int>(scroll_margin_x * scroll_factor + 0.5);
+    int scroll_margin_y = current_source_frame_->height() - kTargetHeight;
+    int pixels_scrolled_y =
+        static_cast<int>(scroll_margin_y * scroll_factor + 0.5);
+
+    int offset_y = (current_source_frame_->stride(PlaneType::kYPlane) *
+                    pixels_scrolled_y) +
+                   pixels_scrolled_x;
+    int offset_u = (current_source_frame_->stride(PlaneType::kUPlane) *
+                    (pixels_scrolled_y / 2)) +
+                   (pixels_scrolled_x / 2);
+    int offset_v = (current_source_frame_->stride(PlaneType::kVPlane) *
+                    (pixels_scrolled_y / 2)) +
+                   (pixels_scrolled_x / 2);
+
+    current_frame_.CreateFrame(
+        &current_source_frame_->buffer(PlaneType::kYPlane)[offset_y],
+        &current_source_frame_->buffer(PlaneType::kUPlane)[offset_u],
+        &current_source_frame_->buffer(PlaneType::kVPlane)[offset_v],
+        kTargetWidth, kTargetHeight,
+        current_source_frame_->stride(PlaneType::kYPlane),
+        current_source_frame_->stride(PlaneType::kUPlane),
+        current_source_frame_->stride(PlaneType::kVPlane));
+  }
+
+  Clock* const clock_;
+  const int64_t start_time_;
+  const int64_t scroll_time_;
+  const int64_t pause_time_;
+  const size_t num_frames_;
+  size_t current_frame_num_;
+  VideoFrame* current_source_frame_;
+  VideoFrame current_frame_;
+  YuvFileGenerator file_generator_;
+};
+
 }  // namespace
 
 FrameGenerator* FrameGenerator::CreateChromaGenerator(size_t width,
@@ -149,5 +252,27 @@
   return new YuvFileGenerator(files, width, height, frame_repeat_count);
 }
 
+FrameGenerator* FrameGenerator::CreateScrollingInputFromYuvFiles(
+    Clock* clock,
+    std::vector<std::string> filenames,
+    size_t source_width,
+    size_t source_height,
+    size_t target_width,
+    size_t target_height,
+    int64_t scroll_time_ms,
+    int64_t pause_time_ms) {
+  assert(!filenames.empty());
+  std::vector<FILE*> files;
+  for (const std::string& filename : filenames) {
+    FILE* file = fopen(filename.c_str(), "rb");
+    DCHECK(file != nullptr);
+    files.push_back(file);
+  }
+
+  return new ScrollingImageFrameGenerator(
+      clock, files, source_width, source_height, target_width, target_height,
+      scroll_time_ms, pause_time_ms);
+}
+
 }  // namespace test
 }  // namespace webrtc
diff --git a/webrtc/test/frame_generator.h b/webrtc/test/frame_generator.h
index 03afacc..7f20c74 100644
--- a/webrtc/test/frame_generator.h
+++ b/webrtc/test/frame_generator.h
@@ -17,6 +17,7 @@
 #include "webrtc/video_frame.h"
 
 namespace webrtc {
+class Clock;
 namespace test {
 
 class FrameGenerator {
@@ -38,6 +39,24 @@
                                            size_t width,
                                            size_t height,
                                            int frame_repeat_count);
+
+  // Creates a frame generator which takes a set of yuv files (wrapping a
+  // frame generator created by CreateFromYuvFile() above), but outputs frames
+  // that have been cropped to specified resolution: source_width/source_height
+  // is the size of the source images, target_width/target_height is the size of
+  // the cropped output. For each source image read, the cropped viewport will
+  // be scrolled top to bottom/left to right for scroll_tim_ms milliseconds.
+  // After that the image will stay in place for pause_time_ms milliseconds,
+  // and then this will be repeated with the next file from the input set.
+  static FrameGenerator* CreateScrollingInputFromYuvFiles(
+      Clock* clock,
+      std::vector<std::string> filenames,
+      size_t source_width,
+      size_t source_height,
+      size_t target_width,
+      size_t target_height,
+      int64_t scroll_time_ms,
+      int64_t pause_time_ms);
 };
 }  // namespace test
 }  // namespace webrtc
diff --git a/webrtc/video/screenshare_loopback.cc b/webrtc/video/screenshare_loopback.cc
index f2133a2..a308424 100644
--- a/webrtc/video/screenshare_loopback.cc
+++ b/webrtc/video/screenshare_loopback.cc
@@ -15,6 +15,7 @@
 #include "gflags/gflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#include "webrtc/base/checks.h"
 #include "webrtc/test/field_trial.h"
 #include "webrtc/test/frame_generator.h"
 #include "webrtc/test/frame_generator_capturer.h"
@@ -27,12 +28,14 @@
 namespace webrtc {
 namespace flags {
 
-// Fixed for prerecorded screenshare content.
+DEFINE_int32(width, 1850, "Video width (crops source).");
 size_t Width() {
-  return 1850;
+  return static_cast<size_t>(FLAGS_width);
 }
+
+DEFINE_int32(height, 1110, "Video height (crops source).");
 size_t Height() {
-  return 1110;
+  return static_cast<size_t>(FLAGS_height);
 }
 
 DEFINE_int32(fps, 5, "Frames per second.");
@@ -40,6 +43,21 @@
   return static_cast<int>(FLAGS_fps);
 }
 
+DEFINE_int32(slide_change_interval,
+             10,
+             "Interval (in seconds) between simulated slide changes.");
+int SlideChangeInterval() {
+  return static_cast<int>(FLAGS_slide_change_interval);
+}
+
+DEFINE_int32(
+    scroll_duration,
+    0,
+    "Duration (in seconds) during which a slide will be scrolled into place.");
+int ScrollDuration() {
+  return static_cast<int>(FLAGS_scroll_duration);
+}
+
 DEFINE_int32(min_bitrate, 50, "Minimum video bitrate.");
 size_t MinBitrate() {
   return static_cast<size_t>(FLAGS_min_bitrate);
@@ -138,9 +156,21 @@
     slides.push_back(test::ResourcePath("photo_1850_1110", "yuv"));
     slides.push_back(test::ResourcePath("difficult_photo_1850_1110", "yuv"));
 
+    // Fixed for input resolution for prerecorded screenshare content.
+    const size_t kWidth = 1850;
+    const size_t kHeight = 1110;
+    CHECK_LE(flags::Width(), kWidth);
+    CHECK_LE(flags::Height(), kHeight);
+    CHECK_GT(flags::SlideChangeInterval(), 0);
+    const int kPauseDurationMs =
+        (flags::SlideChangeInterval() - flags::ScrollDuration()) * 1000;
+    CHECK_LE(flags::ScrollDuration(), flags::SlideChangeInterval());
+
     test::FrameGenerator* frame_generator =
-        test::FrameGenerator::CreateFromYuvFile(
-            slides, flags::Width(), flags::Height(), 10 * flags::Fps());
+        test::FrameGenerator::CreateScrollingInputFromYuvFiles(
+            Clock::GetRealTimeClock(), slides, kWidth, kHeight, flags::Width(),
+            flags::Height(), flags::ScrollDuration() * 1000, kPauseDurationMs);
+
     test::FrameGeneratorCapturer* capturer(new test::FrameGeneratorCapturer(
         clock_, send_stream->Input(), frame_generator, flags::Fps()));
     EXPECT_TRUE(capturer->Init());