blob: 4594e1de2037511a351d29dbf8c1939ab5fcb467 [file] [log] [blame]
andresp@webrtc.orgab654952013-09-19 12:14:03 +00001/*
2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020010#include "test/frame_generator.h"
andresp@webrtc.orgab654952013-09-19 12:14:03 +000011
pbos@webrtc.org266c7b32013-10-15 09:15:47 +000012#include <string.h>
Jonas Olssona4d87372019-07-05 19:08:33 +020013
Yves Gerey3e707812018-11-28 16:47:49 +010014#include <cstdint>
15#include <cstdio>
kwibergbfefb032016-05-01 14:53:46 -070016#include <memory>
17
Emircan Uysaler0823eec2018-07-13 17:10:00 -070018#include "api/video/i010_buffer.h"
Evan Shrubsole55c17862020-09-28 10:16:00 +020019#include "api/video/nv12_buffer.h"
Yves Gerey3e707812018-11-28 16:47:49 +010020#include "api/video/video_rotation.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020021#include "common_video/include/video_frame_buffer.h"
22#include "common_video/libyuv/include/webrtc_libyuv.h"
23#include "rtc_base/checks.h"
24#include "rtc_base/keep_ref_until_done.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020025#include "test/frame_utils.h"
andresp@webrtc.orgab654952013-09-19 12:14:03 +000026
27namespace webrtc {
28namespace test {
Artem Titov33f9d2b2019-12-05 15:59:00 +010029
30SquareGenerator::SquareGenerator(int width,
31 int height,
32 OutputType type,
33 int num_squares)
34 : type_(type) {
35 ChangeResolution(width, height);
36 for (int i = 0; i < num_squares; ++i) {
37 squares_.emplace_back(new Square(width, height, i + 1));
38 }
39}
40
41void SquareGenerator::ChangeResolution(size_t width, size_t height) {
Markus Handella5a4be12020-07-08 16:09:21 +020042 MutexLock lock(&mutex_);
Artem Titov33f9d2b2019-12-05 15:59:00 +010043 width_ = static_cast<int>(width);
44 height_ = static_cast<int>(height);
45 RTC_CHECK(width_ > 0);
46 RTC_CHECK(height_ > 0);
47}
48
49rtc::scoped_refptr<I420Buffer> SquareGenerator::CreateI420Buffer(int width,
50 int height) {
51 rtc::scoped_refptr<I420Buffer> buffer(I420Buffer::Create(width, height));
52 memset(buffer->MutableDataY(), 127, height * buffer->StrideY());
53 memset(buffer->MutableDataU(), 127,
54 buffer->ChromaHeight() * buffer->StrideU());
55 memset(buffer->MutableDataV(), 127,
56 buffer->ChromaHeight() * buffer->StrideV());
57 return buffer;
58}
59
60FrameGeneratorInterface::VideoFrameData SquareGenerator::NextFrame() {
Markus Handella5a4be12020-07-08 16:09:21 +020061 MutexLock lock(&mutex_);
Artem Titov33f9d2b2019-12-05 15:59:00 +010062
63 rtc::scoped_refptr<VideoFrameBuffer> buffer = nullptr;
64 switch (type_) {
65 case OutputType::kI420:
Evan Shrubsole55c17862020-09-28 10:16:00 +020066 case OutputType::kI010:
67 case OutputType::kNV12: {
Artem Titov33f9d2b2019-12-05 15:59:00 +010068 buffer = CreateI420Buffer(width_, height_);
69 break;
70 }
71 case OutputType::kI420A: {
72 rtc::scoped_refptr<I420Buffer> yuv_buffer =
73 CreateI420Buffer(width_, height_);
74 rtc::scoped_refptr<I420Buffer> axx_buffer =
75 CreateI420Buffer(width_, height_);
Niels Möllera68bfc52021-01-11 13:26:35 +010076 buffer = WrapI420ABuffer(yuv_buffer->width(), yuv_buffer->height(),
77 yuv_buffer->DataY(), yuv_buffer->StrideY(),
78 yuv_buffer->DataU(), yuv_buffer->StrideU(),
79 yuv_buffer->DataV(), yuv_buffer->StrideV(),
80 axx_buffer->DataY(), axx_buffer->StrideY(),
81 // To keep references alive.
82 [yuv_buffer, axx_buffer] {});
Artem Titov33f9d2b2019-12-05 15:59:00 +010083 break;
84 }
85 default:
86 RTC_NOTREACHED() << "The given output format is not supported.";
87 }
88
89 for (const auto& square : squares_)
90 square->Draw(buffer);
91
92 if (type_ == OutputType::kI010) {
93 buffer = I010Buffer::Copy(*buffer->ToI420());
Evan Shrubsole55c17862020-09-28 10:16:00 +020094 } else if (type_ == OutputType::kNV12) {
95 buffer = NV12Buffer::Copy(*buffer->ToI420());
Artem Titov33f9d2b2019-12-05 15:59:00 +010096 }
97
98 return VideoFrameData(buffer, absl::nullopt);
99}
100
101SquareGenerator::Square::Square(int width, int height, int seed)
102 : random_generator_(seed),
103 x_(random_generator_.Rand(0, width)),
104 y_(random_generator_.Rand(0, height)),
105 length_(random_generator_.Rand(1, width > 4 ? width / 4 : 1)),
106 yuv_y_(random_generator_.Rand(0, 255)),
107 yuv_u_(random_generator_.Rand(0, 255)),
108 yuv_v_(random_generator_.Rand(0, 255)),
109 yuv_a_(random_generator_.Rand(0, 255)) {}
110
111void SquareGenerator::Square::Draw(
112 const rtc::scoped_refptr<VideoFrameBuffer>& frame_buffer) {
113 RTC_DCHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420 ||
114 frame_buffer->type() == VideoFrameBuffer::Type::kI420A);
115 rtc::scoped_refptr<I420BufferInterface> buffer = frame_buffer->ToI420();
Sebastian Janssond35a6862020-03-09 19:18:14 +0100116 int length_cap = std::min(buffer->height(), buffer->width()) / 4;
117 int length = std::min(length_, length_cap);
118 x_ = (x_ + random_generator_.Rand(0, 4)) % (buffer->width() - length);
119 y_ = (y_ + random_generator_.Rand(0, 4)) % (buffer->height() - length);
120 for (int y = y_; y < y_ + length; ++y) {
Artem Titov33f9d2b2019-12-05 15:59:00 +0100121 uint8_t* pos_y =
122 (const_cast<uint8_t*>(buffer->DataY()) + x_ + y * buffer->StrideY());
Sebastian Janssond35a6862020-03-09 19:18:14 +0100123 memset(pos_y, yuv_y_, length);
Artem Titov33f9d2b2019-12-05 15:59:00 +0100124 }
125
Sebastian Janssond35a6862020-03-09 19:18:14 +0100126 for (int y = y_; y < y_ + length; y = y + 2) {
Artem Titov33f9d2b2019-12-05 15:59:00 +0100127 uint8_t* pos_u = (const_cast<uint8_t*>(buffer->DataU()) + x_ / 2 +
128 y / 2 * buffer->StrideU());
Sebastian Janssond35a6862020-03-09 19:18:14 +0100129 memset(pos_u, yuv_u_, length / 2);
Artem Titov33f9d2b2019-12-05 15:59:00 +0100130 uint8_t* pos_v = (const_cast<uint8_t*>(buffer->DataV()) + x_ / 2 +
131 y / 2 * buffer->StrideV());
Sebastian Janssond35a6862020-03-09 19:18:14 +0100132 memset(pos_v, yuv_v_, length / 2);
Artem Titov33f9d2b2019-12-05 15:59:00 +0100133 }
134
135 if (frame_buffer->type() == VideoFrameBuffer::Type::kI420)
136 return;
137
138 // Optionally draw on alpha plane if given.
139 const webrtc::I420ABufferInterface* yuva_buffer = frame_buffer->GetI420A();
Sebastian Janssond35a6862020-03-09 19:18:14 +0100140 for (int y = y_; y < y_ + length; ++y) {
Artem Titov33f9d2b2019-12-05 15:59:00 +0100141 uint8_t* pos_y = (const_cast<uint8_t*>(yuva_buffer->DataA()) + x_ +
142 y * yuva_buffer->StrideA());
Sebastian Janssond35a6862020-03-09 19:18:14 +0100143 memset(pos_y, yuv_a_, length);
Artem Titov33f9d2b2019-12-05 15:59:00 +0100144 }
145}
146
147YuvFileGenerator::YuvFileGenerator(std::vector<FILE*> files,
148 size_t width,
149 size_t height,
150 int frame_repeat_count)
151 : file_index_(0),
152 frame_index_(std::numeric_limits<size_t>::max()),
153 files_(files),
154 width_(width),
155 height_(height),
156 frame_size_(CalcBufferSize(VideoType::kI420,
157 static_cast<int>(width_),
158 static_cast<int>(height_))),
159 frame_buffer_(new uint8_t[frame_size_]),
160 frame_display_count_(frame_repeat_count),
161 current_display_count_(0) {
162 RTC_DCHECK_GT(width, 0);
163 RTC_DCHECK_GT(height, 0);
164 RTC_DCHECK_GT(frame_repeat_count, 0);
165}
166
167YuvFileGenerator::~YuvFileGenerator() {
168 for (FILE* file : files_)
169 fclose(file);
170}
171
172FrameGeneratorInterface::VideoFrameData YuvFileGenerator::NextFrame() {
173 // Empty update by default.
174 VideoFrame::UpdateRect update_rect{0, 0, 0, 0};
175 if (current_display_count_ == 0) {
176 const bool got_new_frame = ReadNextFrame();
177 // Full update on a new frame from file.
178 if (got_new_frame) {
179 update_rect = VideoFrame::UpdateRect{0, 0, static_cast<int>(width_),
180 static_cast<int>(height_)};
perkja8ba1952017-02-27 06:52:10 -0800181 }
perkjfa10b552016-10-02 23:45:26 -0700182 }
Artem Titov33f9d2b2019-12-05 15:59:00 +0100183 if (++current_display_count_ >= frame_display_count_)
184 current_display_count_ = 0;
perkjfa10b552016-10-02 23:45:26 -0700185
Artem Titov33f9d2b2019-12-05 15:59:00 +0100186 return VideoFrameData(last_read_buffer_, update_rect);
187}
pbos@webrtc.org266c7b32013-10-15 09:15:47 +0000188
Artem Titov33f9d2b2019-12-05 15:59:00 +0100189bool YuvFileGenerator::ReadNextFrame() {
190 size_t prev_frame_index = frame_index_;
191 size_t prev_file_index = file_index_;
192 last_read_buffer_ = test::ReadI420Buffer(
193 static_cast<int>(width_), static_cast<int>(height_), files_[file_index_]);
194 ++frame_index_;
195 if (!last_read_buffer_) {
196 // No more frames to read in this file, rewind and move to next file.
197 rewind(files_[file_index_]);
Emircan Uysaler03e6ec92018-03-09 15:03:26 -0800198
Artem Titov33f9d2b2019-12-05 15:59:00 +0100199 frame_index_ = 0;
200 file_index_ = (file_index_ + 1) % files_.size();
nisse115bd152016-09-30 04:14:07 -0700201 last_read_buffer_ =
202 test::ReadI420Buffer(static_cast<int>(width_),
Jonas Olssona4d87372019-07-05 19:08:33 +0200203 static_cast<int>(height_), files_[file_index_]);
Artem Titov33f9d2b2019-12-05 15:59:00 +0100204 RTC_CHECK(last_read_buffer_);
205 }
206 return frame_index_ != prev_frame_index || file_index_ != prev_file_index;
207}
Ilya Nikolaevskiyd0f3d842019-03-04 15:10:44 +0100208
Artem Titov33f9d2b2019-12-05 15:59:00 +0100209SlideGenerator::SlideGenerator(int width, int height, int frame_repeat_count)
210 : width_(width),
211 height_(height),
212 frame_display_count_(frame_repeat_count),
213 current_display_count_(0),
214 random_generator_(1234) {
215 RTC_DCHECK_GT(width, 0);
216 RTC_DCHECK_GT(height, 0);
217 RTC_DCHECK_GT(frame_repeat_count, 0);
218}
219
220FrameGeneratorInterface::VideoFrameData SlideGenerator::NextFrame() {
221 if (current_display_count_ == 0)
222 GenerateNewFrame();
223 if (++current_display_count_ >= frame_display_count_)
224 current_display_count_ = 0;
225
226 return VideoFrameData(buffer_, absl::nullopt);
227}
228
229void SlideGenerator::GenerateNewFrame() {
230 // The squares should have a varying order of magnitude in order
231 // to simulate variation in the slides' complexity.
232 const int kSquareNum = 1 << (4 + (random_generator_.Rand(0, 3) * 2));
233
234 buffer_ = I420Buffer::Create(width_, height_);
235 memset(buffer_->MutableDataY(), 127, height_ * buffer_->StrideY());
236 memset(buffer_->MutableDataU(), 127,
237 buffer_->ChromaHeight() * buffer_->StrideU());
238 memset(buffer_->MutableDataV(), 127,
239 buffer_->ChromaHeight() * buffer_->StrideV());
240
241 for (int i = 0; i < kSquareNum; ++i) {
242 int length = random_generator_.Rand(1, width_ > 4 ? width_ / 4 : 1);
243 // Limit the length of later squares so that they don't overwrite the
244 // previous ones too much.
245 length = (length * (kSquareNum - i)) / kSquareNum;
246
247 int x = random_generator_.Rand(0, width_ - length);
248 int y = random_generator_.Rand(0, height_ - length);
249 uint8_t yuv_y = random_generator_.Rand(0, 255);
250 uint8_t yuv_u = random_generator_.Rand(0, 255);
251 uint8_t yuv_v = random_generator_.Rand(0, 255);
252
253 for (int yy = y; yy < y + length; ++yy) {
254 uint8_t* pos_y = (buffer_->MutableDataY() + x + yy * buffer_->StrideY());
255 memset(pos_y, yuv_y, length);
sprang@webrtc.org131bea82015-02-18 12:46:06 +0000256 }
Artem Titov33f9d2b2019-12-05 15:59:00 +0100257 for (int yy = y; yy < y + length; yy += 2) {
258 uint8_t* pos_u =
259 (buffer_->MutableDataU() + x / 2 + yy / 2 * buffer_->StrideU());
260 memset(pos_u, yuv_u, length / 2);
261 uint8_t* pos_v =
262 (buffer_->MutableDataV() + x / 2 + yy / 2 * buffer_->StrideV());
263 memset(pos_v, yuv_v, length / 2);
erikvarga579de6f2017-08-29 09:12:57 -0700264 }
265 }
perkja49cbd32016-09-16 07:53:41 -0700266}
267
Artem Titov33f9d2b2019-12-05 15:59:00 +0100268ScrollingImageFrameGenerator::ScrollingImageFrameGenerator(
sprangd6358952015-07-29 07:58:13 -0700269 Clock* clock,
Artem Titov33f9d2b2019-12-05 15:59:00 +0100270 const std::vector<FILE*>& files,
sprangd6358952015-07-29 07:58:13 -0700271 size_t source_width,
272 size_t source_height,
273 size_t target_width,
274 size_t target_height,
275 int64_t scroll_time_ms,
Artem Titov33f9d2b2019-12-05 15:59:00 +0100276 int64_t pause_time_ms)
277 : clock_(clock),
278 start_time_(clock->TimeInMilliseconds()),
279 scroll_time_(scroll_time_ms),
280 pause_time_(pause_time_ms),
281 num_frames_(files.size()),
282 target_width_(static_cast<int>(target_width)),
283 target_height_(static_cast<int>(target_height)),
284 current_frame_num_(num_frames_ - 1),
285 prev_frame_not_scrolled_(false),
286 current_source_frame_(nullptr, absl::nullopt),
287 current_frame_(nullptr, absl::nullopt),
288 file_generator_(files, source_width, source_height, 1) {
289 RTC_DCHECK(clock_ != nullptr);
290 RTC_DCHECK_GT(num_frames_, 0);
291 RTC_DCHECK_GE(source_height, target_height);
292 RTC_DCHECK_GE(source_width, target_width);
293 RTC_DCHECK_GE(scroll_time_ms, 0);
294 RTC_DCHECK_GE(pause_time_ms, 0);
295 RTC_DCHECK_GT(scroll_time_ms + pause_time_ms, 0);
296}
sprangd6358952015-07-29 07:58:13 -0700297
Artem Titov33f9d2b2019-12-05 15:59:00 +0100298FrameGeneratorInterface::VideoFrameData
299ScrollingImageFrameGenerator::NextFrame() {
300 const int64_t kFrameDisplayTime = scroll_time_ + pause_time_;
301 const int64_t now = clock_->TimeInMilliseconds();
302 int64_t ms_since_start = now - start_time_;
303
304 size_t frame_num = (ms_since_start / kFrameDisplayTime) % num_frames_;
305 UpdateSourceFrame(frame_num);
306
307 bool cur_frame_not_scrolled;
308
309 double scroll_factor;
310 int64_t time_into_frame = ms_since_start % kFrameDisplayTime;
311 if (time_into_frame < scroll_time_) {
312 scroll_factor = static_cast<double>(time_into_frame) / scroll_time_;
313 cur_frame_not_scrolled = false;
314 } else {
315 scroll_factor = 1.0;
316 cur_frame_not_scrolled = true;
317 }
318 CropSourceToScrolledImage(scroll_factor);
319
320 bool same_scroll_position =
321 prev_frame_not_scrolled_ && cur_frame_not_scrolled;
322 if (!same_scroll_position) {
323 // If scrolling is not finished yet, force full frame update.
324 current_frame_.update_rect =
325 VideoFrame::UpdateRect{0, 0, target_width_, target_height_};
326 }
327 prev_frame_not_scrolled_ = cur_frame_not_scrolled;
328
329 return current_frame_;
330}
331
332void ScrollingImageFrameGenerator::UpdateSourceFrame(size_t frame_num) {
333 VideoFrame::UpdateRect acc_update{0, 0, 0, 0};
334 while (current_frame_num_ != frame_num) {
335 current_source_frame_ = file_generator_.NextFrame();
336 if (current_source_frame_.update_rect) {
337 acc_update.Union(*current_source_frame_.update_rect);
338 }
339 current_frame_num_ = (current_frame_num_ + 1) % num_frames_;
340 }
341 current_source_frame_.update_rect = acc_update;
342}
343
344void ScrollingImageFrameGenerator::CropSourceToScrolledImage(
345 double scroll_factor) {
346 int scroll_margin_x = current_source_frame_.buffer->width() - target_width_;
347 int pixels_scrolled_x =
348 static_cast<int>(scroll_margin_x * scroll_factor + 0.5);
349 int scroll_margin_y = current_source_frame_.buffer->height() - target_height_;
350 int pixels_scrolled_y =
351 static_cast<int>(scroll_margin_y * scroll_factor + 0.5);
352
353 rtc::scoped_refptr<I420BufferInterface> i420_buffer =
354 current_source_frame_.buffer->ToI420();
355 int offset_y =
356 (i420_buffer->StrideY() * pixels_scrolled_y) + pixels_scrolled_x;
357 int offset_u = (i420_buffer->StrideU() * (pixels_scrolled_y / 2)) +
358 (pixels_scrolled_x / 2);
359 int offset_v = (i420_buffer->StrideV() * (pixels_scrolled_y / 2)) +
360 (pixels_scrolled_x / 2);
361
362 VideoFrame::UpdateRect update_rect =
363 current_source_frame_.update_rect->IsEmpty()
364 ? VideoFrame::UpdateRect{0, 0, 0, 0}
365 : VideoFrame::UpdateRect{0, 0, target_width_, target_height_};
366 current_frame_ = VideoFrameData(
367 WrapI420Buffer(target_width_, target_height_,
368 &i420_buffer->DataY()[offset_y], i420_buffer->StrideY(),
369 &i420_buffer->DataU()[offset_u], i420_buffer->StrideU(),
370 &i420_buffer->DataV()[offset_v], i420_buffer->StrideV(),
371 KeepRefUntilDone(i420_buffer)),
372 update_rect);
sprangd6358952015-07-29 07:58:13 -0700373}
374
andresp@webrtc.orgab654952013-09-19 12:14:03 +0000375} // namespace test
376} // namespace webrtc