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-Commit-Position: refs/heads/master@{#19585}
This commit is contained in:
erikvarga 2017-08-29 09:12:57 -07:00 committed by Commit Bot
parent c63f9345e7
commit 579de6faef
9 changed files with 207 additions and 37 deletions

View File

@ -181,6 +181,85 @@ class YuvFileGenerator : public FrameGenerator {
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 @@ std::unique_ptr<FrameGenerator> FrameGenerator::CreateSquareGenerator(
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,

View File

@ -89,6 +89,11 @@ class FrameGenerator {
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

View File

@ -114,6 +114,22 @@ FrameGeneratorCapturer* FrameGeneratorCapturer::CreateFromYuvFile(
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,

View File

@ -50,6 +50,12 @@ class FrameGeneratorCapturer : public VideoCapturer {
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;

View File

@ -81,6 +81,26 @@ class FrameGeneratorTest : public ::testing::Test {
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 @@ TEST_F(FrameGeneratorTest, MultipleFrameFilesWithRepeat) {
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

View File

@ -342,7 +342,7 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL) {
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_F(FullStackTest, ScreenshareSlidesVP8_3TL_Simulcast) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_Scroll) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_LossyNet) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_VeryLossyNet) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_LossyNetRestrictedQueue) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_ModeratelyRestricted) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_LossyNetRestrictedQueue_ALR) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_ALR) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_2TL_ModeratelyRestricted_ALR) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP8_3TL_Simulcast_ALR) {
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 @@ TEST_F(FullStackTest, ScreenshareSlidesVP9_2SL) {
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,

View File

@ -218,6 +218,13 @@ int MinTransmitBitrateKbps() {
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 @@ void Loopback() {
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()};

View File

@ -1174,7 +1174,7 @@ VideoQualityTest::Params::Params()
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 @@ void VideoQualityTest::SetupScreenshareOrSVC() {
// 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");

View File

@ -61,6 +61,7 @@ class VideoQualityTest : public test::CallTest {
} audio;
struct Screenshare {
bool enabled;
bool generate_slides;
int32_t slide_change_interval;
int32_t scroll_duration;
std::vector<std::string> slides;