blob: d2fa95352d67c9a921fa03f4a14665dc896fb8a5 [file] [log] [blame]
andresp@webrtc.orgab654952013-09-19 12:14:031/*
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 04:47:3110#include "test/frame_generator.h"
andresp@webrtc.orgab654952013-09-19 12:14:0311
pbos@webrtc.org266c7b32013-10-15 09:15:4712#include <string.h>
Jonas Olssona4d87372019-07-05 17:08:3313
Yves Gerey3e707812018-11-28 15:47:4914#include <cstdint>
15#include <cstdio>
kwibergbfefb032016-05-01 21:53:4616#include <memory>
17
Emircan Uysaler0823eec2018-07-14 00:10:0018#include "api/video/i010_buffer.h"
Evan Shrubsole55c17862020-09-28 08:16:0019#include "api/video/nv12_buffer.h"
Yves Gerey3e707812018-11-28 15:47:4920#include "api/video/video_rotation.h"
Mirko Bonadei92ea95e2017-09-15 04:47:3121#include "common_video/include/video_frame_buffer.h"
22#include "common_video/libyuv/include/webrtc_libyuv.h"
23#include "rtc_base/checks.h"
Mirko Bonadei92ea95e2017-09-15 04:47:3124#include "test/frame_utils.h"
andresp@webrtc.orgab654952013-09-19 12:14:0325
26namespace webrtc {
27namespace test {
Artem Titov33f9d2b2019-12-05 14:59:0028
29SquareGenerator::SquareGenerator(int width,
30 int height,
31 OutputType type,
32 int num_squares)
33 : type_(type) {
34 ChangeResolution(width, height);
35 for (int i = 0; i < num_squares; ++i) {
36 squares_.emplace_back(new Square(width, height, i + 1));
37 }
38}
39
40void SquareGenerator::ChangeResolution(size_t width, size_t height) {
Markus Handella5a4be12020-07-08 14:09:2141 MutexLock lock(&mutex_);
Artem Titov33f9d2b2019-12-05 14:59:0042 width_ = static_cast<int>(width);
43 height_ = static_cast<int>(height);
44 RTC_CHECK(width_ > 0);
45 RTC_CHECK(height_ > 0);
46}
47
Mirko Bonadeif1e39222023-02-17 12:49:5848FrameGeneratorInterface::Resolution SquareGenerator::GetResolution() const {
49 MutexLock lock(&mutex_);
50 return {.width = static_cast<size_t>(width_),
51 .height = static_cast<size_t>(height_)};
52}
53
Artem Titov33f9d2b2019-12-05 14:59:0054rtc::scoped_refptr<I420Buffer> SquareGenerator::CreateI420Buffer(int width,
55 int height) {
56 rtc::scoped_refptr<I420Buffer> buffer(I420Buffer::Create(width, height));
57 memset(buffer->MutableDataY(), 127, height * buffer->StrideY());
58 memset(buffer->MutableDataU(), 127,
59 buffer->ChromaHeight() * buffer->StrideU());
60 memset(buffer->MutableDataV(), 127,
61 buffer->ChromaHeight() * buffer->StrideV());
62 return buffer;
63}
64
65FrameGeneratorInterface::VideoFrameData SquareGenerator::NextFrame() {
Markus Handella5a4be12020-07-08 14:09:2166 MutexLock lock(&mutex_);
Artem Titov33f9d2b2019-12-05 14:59:0067
68 rtc::scoped_refptr<VideoFrameBuffer> buffer = nullptr;
69 switch (type_) {
70 case OutputType::kI420:
Evan Shrubsole55c17862020-09-28 08:16:0071 case OutputType::kI010:
72 case OutputType::kNV12: {
Artem Titov33f9d2b2019-12-05 14:59:0073 buffer = CreateI420Buffer(width_, height_);
74 break;
75 }
76 case OutputType::kI420A: {
77 rtc::scoped_refptr<I420Buffer> yuv_buffer =
78 CreateI420Buffer(width_, height_);
79 rtc::scoped_refptr<I420Buffer> axx_buffer =
80 CreateI420Buffer(width_, height_);
Niels Möllera68bfc52021-01-11 12:26:3581 buffer = WrapI420ABuffer(yuv_buffer->width(), yuv_buffer->height(),
82 yuv_buffer->DataY(), yuv_buffer->StrideY(),
83 yuv_buffer->DataU(), yuv_buffer->StrideU(),
84 yuv_buffer->DataV(), yuv_buffer->StrideV(),
85 axx_buffer->DataY(), axx_buffer->StrideY(),
86 // To keep references alive.
87 [yuv_buffer, axx_buffer] {});
Artem Titov33f9d2b2019-12-05 14:59:0088 break;
89 }
90 default:
Artem Titovd3251962021-11-15 15:57:0791 RTC_DCHECK_NOTREACHED() << "The given output format is not supported.";
Artem Titov33f9d2b2019-12-05 14:59:0092 }
93
94 for (const auto& square : squares_)
95 square->Draw(buffer);
96
97 if (type_ == OutputType::kI010) {
98 buffer = I010Buffer::Copy(*buffer->ToI420());
Evan Shrubsole55c17862020-09-28 08:16:0099 } else if (type_ == OutputType::kNV12) {
100 buffer = NV12Buffer::Copy(*buffer->ToI420());
Artem Titov33f9d2b2019-12-05 14:59:00101 }
102
103 return VideoFrameData(buffer, absl::nullopt);
104}
105
106SquareGenerator::Square::Square(int width, int height, int seed)
107 : random_generator_(seed),
108 x_(random_generator_.Rand(0, width)),
109 y_(random_generator_.Rand(0, height)),
110 length_(random_generator_.Rand(1, width > 4 ? width / 4 : 1)),
111 yuv_y_(random_generator_.Rand(0, 255)),
112 yuv_u_(random_generator_.Rand(0, 255)),
113 yuv_v_(random_generator_.Rand(0, 255)),
114 yuv_a_(random_generator_.Rand(0, 255)) {}
115
116void SquareGenerator::Square::Draw(
117 const rtc::scoped_refptr<VideoFrameBuffer>& frame_buffer) {
118 RTC_DCHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420 ||
119 frame_buffer->type() == VideoFrameBuffer::Type::kI420A);
120 rtc::scoped_refptr<I420BufferInterface> buffer = frame_buffer->ToI420();
Sebastian Janssond35a6862020-03-09 18:18:14121 int length_cap = std::min(buffer->height(), buffer->width()) / 4;
122 int length = std::min(length_, length_cap);
123 x_ = (x_ + random_generator_.Rand(0, 4)) % (buffer->width() - length);
124 y_ = (y_ + random_generator_.Rand(0, 4)) % (buffer->height() - length);
125 for (int y = y_; y < y_ + length; ++y) {
Artem Titov33f9d2b2019-12-05 14:59:00126 uint8_t* pos_y =
127 (const_cast<uint8_t*>(buffer->DataY()) + x_ + y * buffer->StrideY());
Sebastian Janssond35a6862020-03-09 18:18:14128 memset(pos_y, yuv_y_, length);
Artem Titov33f9d2b2019-12-05 14:59:00129 }
130
Sebastian Janssond35a6862020-03-09 18:18:14131 for (int y = y_; y < y_ + length; y = y + 2) {
Artem Titov33f9d2b2019-12-05 14:59:00132 uint8_t* pos_u = (const_cast<uint8_t*>(buffer->DataU()) + x_ / 2 +
133 y / 2 * buffer->StrideU());
Sebastian Janssond35a6862020-03-09 18:18:14134 memset(pos_u, yuv_u_, length / 2);
Artem Titov33f9d2b2019-12-05 14:59:00135 uint8_t* pos_v = (const_cast<uint8_t*>(buffer->DataV()) + x_ / 2 +
136 y / 2 * buffer->StrideV());
Sebastian Janssond35a6862020-03-09 18:18:14137 memset(pos_v, yuv_v_, length / 2);
Artem Titov33f9d2b2019-12-05 14:59:00138 }
139
140 if (frame_buffer->type() == VideoFrameBuffer::Type::kI420)
141 return;
142
143 // Optionally draw on alpha plane if given.
144 const webrtc::I420ABufferInterface* yuva_buffer = frame_buffer->GetI420A();
Sebastian Janssond35a6862020-03-09 18:18:14145 for (int y = y_; y < y_ + length; ++y) {
Artem Titov33f9d2b2019-12-05 14:59:00146 uint8_t* pos_y = (const_cast<uint8_t*>(yuva_buffer->DataA()) + x_ +
147 y * yuva_buffer->StrideA());
Sebastian Janssond35a6862020-03-09 18:18:14148 memset(pos_y, yuv_a_, length);
Artem Titov33f9d2b2019-12-05 14:59:00149 }
150}
151
152YuvFileGenerator::YuvFileGenerator(std::vector<FILE*> files,
153 size_t width,
154 size_t height,
155 int frame_repeat_count)
156 : file_index_(0),
157 frame_index_(std::numeric_limits<size_t>::max()),
158 files_(files),
159 width_(width),
160 height_(height),
161 frame_size_(CalcBufferSize(VideoType::kI420,
162 static_cast<int>(width_),
163 static_cast<int>(height_))),
164 frame_buffer_(new uint8_t[frame_size_]),
165 frame_display_count_(frame_repeat_count),
166 current_display_count_(0) {
167 RTC_DCHECK_GT(width, 0);
168 RTC_DCHECK_GT(height, 0);
169 RTC_DCHECK_GT(frame_repeat_count, 0);
170}
171
172YuvFileGenerator::~YuvFileGenerator() {
173 for (FILE* file : files_)
174 fclose(file);
175}
176
177FrameGeneratorInterface::VideoFrameData YuvFileGenerator::NextFrame() {
178 // Empty update by default.
179 VideoFrame::UpdateRect update_rect{0, 0, 0, 0};
180 if (current_display_count_ == 0) {
181 const bool got_new_frame = ReadNextFrame();
182 // Full update on a new frame from file.
183 if (got_new_frame) {
184 update_rect = VideoFrame::UpdateRect{0, 0, static_cast<int>(width_),
185 static_cast<int>(height_)};
perkja8ba1952017-02-27 14:52:10186 }
perkjfa10b552016-10-03 06:45:26187 }
Artem Titov33f9d2b2019-12-05 14:59:00188 if (++current_display_count_ >= frame_display_count_)
189 current_display_count_ = 0;
perkjfa10b552016-10-03 06:45:26190
Artem Titov33f9d2b2019-12-05 14:59:00191 return VideoFrameData(last_read_buffer_, update_rect);
192}
pbos@webrtc.org266c7b32013-10-15 09:15:47193
Artem Titov33f9d2b2019-12-05 14:59:00194bool YuvFileGenerator::ReadNextFrame() {
195 size_t prev_frame_index = frame_index_;
196 size_t prev_file_index = file_index_;
197 last_read_buffer_ = test::ReadI420Buffer(
198 static_cast<int>(width_), static_cast<int>(height_), files_[file_index_]);
199 ++frame_index_;
200 if (!last_read_buffer_) {
201 // No more frames to read in this file, rewind and move to next file.
202 rewind(files_[file_index_]);
Emircan Uysaler03e6ec92018-03-09 23:03:26203
Artem Titov33f9d2b2019-12-05 14:59:00204 frame_index_ = 0;
205 file_index_ = (file_index_ + 1) % files_.size();
nisse115bd152016-09-30 11:14:07206 last_read_buffer_ =
207 test::ReadI420Buffer(static_cast<int>(width_),
Jonas Olssona4d87372019-07-05 17:08:33208 static_cast<int>(height_), files_[file_index_]);
Artem Titov33f9d2b2019-12-05 14:59:00209 RTC_CHECK(last_read_buffer_);
210 }
211 return frame_index_ != prev_frame_index || file_index_ != prev_file_index;
212}
Ilya Nikolaevskiyd0f3d842019-03-04 14:10:44213
Mirko Bonadeif1e39222023-02-17 12:49:58214FrameGeneratorInterface::Resolution YuvFileGenerator::GetResolution() const {
215 return {.width = width_, .height = height_};
216}
217
Artem Titov9b731592022-10-07 13:01:32218NV12FileGenerator::NV12FileGenerator(std::vector<FILE*> files,
219 size_t width,
220 size_t height,
221 int frame_repeat_count)
222 : file_index_(0),
223 frame_index_(std::numeric_limits<size_t>::max()),
224 files_(files),
225 width_(width),
226 height_(height),
227 frame_size_(CalcBufferSize(VideoType::kNV12,
228 static_cast<int>(width_),
229 static_cast<int>(height_))),
230 frame_buffer_(new uint8_t[frame_size_]),
231 frame_display_count_(frame_repeat_count),
232 current_display_count_(0) {
233 RTC_DCHECK_GT(width, 0);
234 RTC_DCHECK_GT(height, 0);
235 RTC_DCHECK_GT(frame_repeat_count, 0);
236}
237
238NV12FileGenerator::~NV12FileGenerator() {
239 for (FILE* file : files_)
240 fclose(file);
241}
242
243FrameGeneratorInterface::VideoFrameData NV12FileGenerator::NextFrame() {
244 // Empty update by default.
245 VideoFrame::UpdateRect update_rect{0, 0, 0, 0};
246 if (current_display_count_ == 0) {
247 const bool got_new_frame = ReadNextFrame();
248 // Full update on a new frame from file.
249 if (got_new_frame) {
250 update_rect = VideoFrame::UpdateRect{0, 0, static_cast<int>(width_),
251 static_cast<int>(height_)};
252 }
253 }
254 if (++current_display_count_ >= frame_display_count_)
255 current_display_count_ = 0;
256
257 return VideoFrameData(last_read_buffer_, update_rect);
258}
259
Mirko Bonadeif1e39222023-02-17 12:49:58260FrameGeneratorInterface::Resolution NV12FileGenerator::GetResolution() const {
261 return {.width = width_, .height = height_};
262}
263
Artem Titov9b731592022-10-07 13:01:32264bool NV12FileGenerator::ReadNextFrame() {
265 size_t prev_frame_index = frame_index_;
266 size_t prev_file_index = file_index_;
267 last_read_buffer_ = test::ReadNV12Buffer(
268 static_cast<int>(width_), static_cast<int>(height_), files_[file_index_]);
269 ++frame_index_;
270 if (!last_read_buffer_) {
271 // No more frames to read in this file, rewind and move to next file.
272 rewind(files_[file_index_]);
273
274 frame_index_ = 0;
275 file_index_ = (file_index_ + 1) % files_.size();
276 last_read_buffer_ =
277 test::ReadNV12Buffer(static_cast<int>(width_),
278 static_cast<int>(height_), files_[file_index_]);
279 RTC_CHECK(last_read_buffer_);
280 }
281 return frame_index_ != prev_frame_index || file_index_ != prev_file_index;
282}
283
Artem Titov33f9d2b2019-12-05 14:59:00284SlideGenerator::SlideGenerator(int width, int height, int frame_repeat_count)
285 : width_(width),
286 height_(height),
287 frame_display_count_(frame_repeat_count),
288 current_display_count_(0),
289 random_generator_(1234) {
290 RTC_DCHECK_GT(width, 0);
291 RTC_DCHECK_GT(height, 0);
292 RTC_DCHECK_GT(frame_repeat_count, 0);
293}
294
295FrameGeneratorInterface::VideoFrameData SlideGenerator::NextFrame() {
296 if (current_display_count_ == 0)
297 GenerateNewFrame();
298 if (++current_display_count_ >= frame_display_count_)
299 current_display_count_ = 0;
300
301 return VideoFrameData(buffer_, absl::nullopt);
302}
303
Mirko Bonadeif1e39222023-02-17 12:49:58304FrameGeneratorInterface::Resolution SlideGenerator::GetResolution() const {
305 return {.width = static_cast<size_t>(width_),
306 .height = static_cast<size_t>(height_)};
307}
308
Artem Titov33f9d2b2019-12-05 14:59:00309void SlideGenerator::GenerateNewFrame() {
310 // The squares should have a varying order of magnitude in order
311 // to simulate variation in the slides' complexity.
312 const int kSquareNum = 1 << (4 + (random_generator_.Rand(0, 3) * 2));
313
314 buffer_ = I420Buffer::Create(width_, height_);
315 memset(buffer_->MutableDataY(), 127, height_ * buffer_->StrideY());
316 memset(buffer_->MutableDataU(), 127,
317 buffer_->ChromaHeight() * buffer_->StrideU());
318 memset(buffer_->MutableDataV(), 127,
319 buffer_->ChromaHeight() * buffer_->StrideV());
320
321 for (int i = 0; i < kSquareNum; ++i) {
322 int length = random_generator_.Rand(1, width_ > 4 ? width_ / 4 : 1);
323 // Limit the length of later squares so that they don't overwrite the
324 // previous ones too much.
325 length = (length * (kSquareNum - i)) / kSquareNum;
326
327 int x = random_generator_.Rand(0, width_ - length);
328 int y = random_generator_.Rand(0, height_ - length);
329 uint8_t yuv_y = random_generator_.Rand(0, 255);
330 uint8_t yuv_u = random_generator_.Rand(0, 255);
331 uint8_t yuv_v = random_generator_.Rand(0, 255);
332
333 for (int yy = y; yy < y + length; ++yy) {
334 uint8_t* pos_y = (buffer_->MutableDataY() + x + yy * buffer_->StrideY());
335 memset(pos_y, yuv_y, length);
sprang@webrtc.org131bea82015-02-18 12:46:06336 }
Artem Titov33f9d2b2019-12-05 14:59:00337 for (int yy = y; yy < y + length; yy += 2) {
338 uint8_t* pos_u =
339 (buffer_->MutableDataU() + x / 2 + yy / 2 * buffer_->StrideU());
340 memset(pos_u, yuv_u, length / 2);
341 uint8_t* pos_v =
342 (buffer_->MutableDataV() + x / 2 + yy / 2 * buffer_->StrideV());
343 memset(pos_v, yuv_v, length / 2);
erikvarga579de6f2017-08-29 16:12:57344 }
345 }
perkja49cbd32016-09-16 14:53:41346}
347
Artem Titov33f9d2b2019-12-05 14:59:00348ScrollingImageFrameGenerator::ScrollingImageFrameGenerator(
sprangd6358952015-07-29 14:58:13349 Clock* clock,
Artem Titov33f9d2b2019-12-05 14:59:00350 const std::vector<FILE*>& files,
sprangd6358952015-07-29 14:58:13351 size_t source_width,
352 size_t source_height,
353 size_t target_width,
354 size_t target_height,
355 int64_t scroll_time_ms,
Artem Titov33f9d2b2019-12-05 14:59:00356 int64_t pause_time_ms)
357 : clock_(clock),
358 start_time_(clock->TimeInMilliseconds()),
359 scroll_time_(scroll_time_ms),
360 pause_time_(pause_time_ms),
361 num_frames_(files.size()),
362 target_width_(static_cast<int>(target_width)),
363 target_height_(static_cast<int>(target_height)),
364 current_frame_num_(num_frames_ - 1),
365 prev_frame_not_scrolled_(false),
366 current_source_frame_(nullptr, absl::nullopt),
367 current_frame_(nullptr, absl::nullopt),
368 file_generator_(files, source_width, source_height, 1) {
369 RTC_DCHECK(clock_ != nullptr);
370 RTC_DCHECK_GT(num_frames_, 0);
371 RTC_DCHECK_GE(source_height, target_height);
372 RTC_DCHECK_GE(source_width, target_width);
373 RTC_DCHECK_GE(scroll_time_ms, 0);
374 RTC_DCHECK_GE(pause_time_ms, 0);
375 RTC_DCHECK_GT(scroll_time_ms + pause_time_ms, 0);
376}
sprangd6358952015-07-29 14:58:13377
Artem Titov33f9d2b2019-12-05 14:59:00378FrameGeneratorInterface::VideoFrameData
379ScrollingImageFrameGenerator::NextFrame() {
380 const int64_t kFrameDisplayTime = scroll_time_ + pause_time_;
381 const int64_t now = clock_->TimeInMilliseconds();
382 int64_t ms_since_start = now - start_time_;
383
384 size_t frame_num = (ms_since_start / kFrameDisplayTime) % num_frames_;
385 UpdateSourceFrame(frame_num);
386
387 bool cur_frame_not_scrolled;
388
389 double scroll_factor;
390 int64_t time_into_frame = ms_since_start % kFrameDisplayTime;
391 if (time_into_frame < scroll_time_) {
392 scroll_factor = static_cast<double>(time_into_frame) / scroll_time_;
393 cur_frame_not_scrolled = false;
394 } else {
395 scroll_factor = 1.0;
396 cur_frame_not_scrolled = true;
397 }
398 CropSourceToScrolledImage(scroll_factor);
399
400 bool same_scroll_position =
401 prev_frame_not_scrolled_ && cur_frame_not_scrolled;
402 if (!same_scroll_position) {
403 // If scrolling is not finished yet, force full frame update.
404 current_frame_.update_rect =
405 VideoFrame::UpdateRect{0, 0, target_width_, target_height_};
406 }
407 prev_frame_not_scrolled_ = cur_frame_not_scrolled;
408
409 return current_frame_;
410}
411
Mirko Bonadeif1e39222023-02-17 12:49:58412FrameGeneratorInterface::Resolution
413ScrollingImageFrameGenerator::GetResolution() const {
414 return {.width = static_cast<size_t>(target_width_),
415 .height = static_cast<size_t>(target_height_)};
416}
417
Artem Titov33f9d2b2019-12-05 14:59:00418void ScrollingImageFrameGenerator::UpdateSourceFrame(size_t frame_num) {
419 VideoFrame::UpdateRect acc_update{0, 0, 0, 0};
420 while (current_frame_num_ != frame_num) {
421 current_source_frame_ = file_generator_.NextFrame();
422 if (current_source_frame_.update_rect) {
423 acc_update.Union(*current_source_frame_.update_rect);
424 }
425 current_frame_num_ = (current_frame_num_ + 1) % num_frames_;
426 }
427 current_source_frame_.update_rect = acc_update;
428}
429
430void ScrollingImageFrameGenerator::CropSourceToScrolledImage(
431 double scroll_factor) {
432 int scroll_margin_x = current_source_frame_.buffer->width() - target_width_;
433 int pixels_scrolled_x =
434 static_cast<int>(scroll_margin_x * scroll_factor + 0.5);
435 int scroll_margin_y = current_source_frame_.buffer->height() - target_height_;
436 int pixels_scrolled_y =
437 static_cast<int>(scroll_margin_y * scroll_factor + 0.5);
438
439 rtc::scoped_refptr<I420BufferInterface> i420_buffer =
440 current_source_frame_.buffer->ToI420();
441 int offset_y =
442 (i420_buffer->StrideY() * pixels_scrolled_y) + pixels_scrolled_x;
443 int offset_u = (i420_buffer->StrideU() * (pixels_scrolled_y / 2)) +
444 (pixels_scrolled_x / 2);
445 int offset_v = (i420_buffer->StrideV() * (pixels_scrolled_y / 2)) +
446 (pixels_scrolled_x / 2);
447
448 VideoFrame::UpdateRect update_rect =
449 current_source_frame_.update_rect->IsEmpty()
450 ? VideoFrame::UpdateRect{0, 0, 0, 0}
451 : VideoFrame::UpdateRect{0, 0, target_width_, target_height_};
452 current_frame_ = VideoFrameData(
453 WrapI420Buffer(target_width_, target_height_,
454 &i420_buffer->DataY()[offset_y], i420_buffer->StrideY(),
455 &i420_buffer->DataU()[offset_u], i420_buffer->StrideU(),
456 &i420_buffer->DataV()[offset_v], i420_buffer->StrideV(),
Niels Möllerf4e3e2b2021-02-02 10:37:39457 // To keep reference alive.
458 [i420_buffer] {}),
Artem Titov33f9d2b2019-12-05 14:59:00459 update_rect);
sprangd6358952015-07-29 14:58:13460}
461
andresp@webrtc.orgab654952013-09-19 12:14:03462} // namespace test
463} // namespace webrtc