From d63589579a66df028efe7ec63cf465082f3857fa Mon Sep 17 00:00:00 2001 From: sprang Date: Wed, 29 Jul 2015 07:58:13 -0700 Subject: [PATCH] Add a frame generator that allows scrolling over a larger still image, for use with new screen sharing quality tests. BUG= Review URL: https://codereview.webrtc.org/1267463002 Cr-Commit-Position: refs/heads/master@{#9654} --- webrtc/test/frame_generator.cc | 127 +++++++++++++++++++++++++++ webrtc/test/frame_generator.h | 19 ++++ webrtc/video/screenshare_loopback.cc | 40 +++++++-- 3 files changed, 181 insertions(+), 5 deletions(-) diff --git a/webrtc/test/frame_generator.cc b/webrtc/test/frame_generator.cc index 55e5a41548..782e39218b 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,110 @@ class YuvFileGenerator : public FrameGenerator { VideoFrame last_read_frame_; VideoFrame temp_frame_copy_; }; + +class ScrollingImageFrameGenerator : public FrameGenerator { + public: + ScrollingImageFrameGenerator(Clock* clock, + const std::vector& 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(static_cast(target_width), + static_cast(target_height), + static_cast(target_width), + static_cast((target_width + 1) / 2), + static_cast((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(time_into_frame) / scroll_time_; + } else { + scroll_factor = 1.0; + } + CropSourceToScrolledImage(scroll_factor); + + return ¤t_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(scroll_margin_x * scroll_factor + 0.5); + int scroll_margin_y = current_source_frame_->height() - kTargetHeight; + int pixels_scrolled_y = + static_cast(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( + ¤t_source_frame_->buffer(PlaneType::kYPlane)[offset_y], + ¤t_source_frame_->buffer(PlaneType::kUPlane)[offset_u], + ¤t_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 +254,27 @@ FrameGenerator* FrameGenerator::CreateFromYuvFile( return new YuvFileGenerator(files, width, height, frame_repeat_count); } +FrameGenerator* FrameGenerator::CreateScrollingInputFromYuvFiles( + Clock* clock, + std::vector 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 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 03afaccc30..7f20c749e8 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 @@ class FrameGenerator { 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 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 f2133a211b..a308424981 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(FLAGS_width); } + +DEFINE_int32(height, 1110, "Video height (crops source)."); size_t Height() { - return 1110; + return static_cast(FLAGS_height); } DEFINE_int32(fps, 5, "Frames per second."); @@ -40,6 +43,21 @@ int Fps() { return static_cast(FLAGS_fps); } +DEFINE_int32(slide_change_interval, + 10, + "Interval (in seconds) between simulated slide changes."); +int SlideChangeInterval() { + return static_cast(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(FLAGS_scroll_duration); +} + DEFINE_int32(min_bitrate, 50, "Minimum video bitrate."); size_t MinBitrate() { return static_cast(FLAGS_min_bitrate); @@ -138,9 +156,21 @@ class ScreenshareLoopback : public test::Loopback { 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());