1 /*
2 * Copyright (c) 2022 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 */
10 #include "test/pc/e2e/analyzer/video/analyzing_video_sink.h"
11
12 #include <stdio.h>
13
14 #include <string>
15 #include <vector>
16
17 #include "absl/strings/string_view.h"
18 #include "absl/types/optional.h"
19 #include "api/scoped_refptr.h"
20 #include "api/test/create_frame_generator.h"
21 #include "api/test/frame_generator_interface.h"
22 #include "api/test/pclf/media_configuration.h"
23 #include "api/units/time_delta.h"
24 #include "api/units/timestamp.h"
25 #include "api/video/i420_buffer.h"
26 #include "api/video/video_frame.h"
27 #include "common_video/libyuv/include/webrtc_libyuv.h"
28 #include "rtc_base/time_utils.h"
29 #include "system_wrappers/include/clock.h"
30 #include "test/gmock.h"
31 #include "test/gtest.h"
32 #include "test/pc/e2e/analyzer/video/example_video_quality_analyzer.h"
33 #include "test/testsupport/file_utils.h"
34 #include "test/testsupport/frame_reader.h"
35 #include "test/time_controller/simulated_time_controller.h"
36
37 namespace webrtc {
38 namespace webrtc_pc_e2e {
39 namespace {
40
41 using ::testing::ElementsAreArray;
42 using ::testing::Eq;
43 using ::testing::Ge;
44 using ::testing::Test;
45
46 // Remove files and directories in a directory non-recursively.
CleanDir(absl::string_view dir,size_t expected_output_files_count)47 void CleanDir(absl::string_view dir, size_t expected_output_files_count) {
48 absl::optional<std::vector<std::string>> dir_content =
49 test::ReadDirectory(dir);
50 if (expected_output_files_count == 0) {
51 ASSERT_TRUE(!dir_content.has_value() || dir_content->empty())
52 << "Empty directory is expected";
53 } else {
54 ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!";
55 EXPECT_EQ(dir_content->size(), expected_output_files_count);
56 for (const auto& entry : *dir_content) {
57 if (test::DirExists(entry)) {
58 EXPECT_TRUE(test::RemoveDir(entry))
59 << "Failed to remove sub directory: " << entry;
60 } else if (test::FileExists(entry)) {
61 EXPECT_TRUE(test::RemoveFile(entry))
62 << "Failed to remove file: " << entry;
63 } else {
64 FAIL() << "Can't remove unknown file type: " << entry;
65 }
66 }
67 }
68 EXPECT_TRUE(test::RemoveDir(dir)) << "Failed to remove directory: " << dir;
69 }
70
CreateFrame(test::FrameGeneratorInterface & frame_generator)71 VideoFrame CreateFrame(test::FrameGeneratorInterface& frame_generator) {
72 test::FrameGeneratorInterface::VideoFrameData frame_data =
73 frame_generator.NextFrame();
74 return VideoFrame::Builder()
75 .set_video_frame_buffer(frame_data.buffer)
76 .set_update_rect(frame_data.update_rect)
77 .build();
78 }
79
CreateFrameGenerator(size_t width,size_t height)80 std::unique_ptr<test::FrameGeneratorInterface> CreateFrameGenerator(
81 size_t width,
82 size_t height) {
83 return test::CreateSquareFrameGenerator(width, height,
84 /*type=*/absl::nullopt,
85 /*num_squares=*/absl::nullopt);
86 }
87
AssertFrameIdsAre(const std::string & filename,std::vector<std::string> expected_ids)88 void AssertFrameIdsAre(const std::string& filename,
89 std::vector<std::string> expected_ids) {
90 FILE* file = fopen(filename.c_str(), "r");
91 ASSERT_TRUE(file != nullptr) << "Failed to open frame ids file: " << filename;
92 std::vector<std::string> actual_ids;
93 char buffer[8];
94 while (fgets(buffer, sizeof buffer, file) != nullptr) {
95 std::string current_id(buffer);
96 EXPECT_GE(current_id.size(), 2lu)
97 << "Found invalid frame id: [" << current_id << "]";
98 if (current_id.size() < 2) {
99 continue;
100 }
101 // Trim "\n" at the end.
102 actual_ids.push_back(current_id.substr(0, current_id.size() - 1));
103 }
104 fclose(file);
105 EXPECT_THAT(actual_ids, ElementsAreArray(expected_ids));
106 }
107
108 class AnalyzingVideoSinkTest : public Test {
109 protected:
110 ~AnalyzingVideoSinkTest() override = default;
111
SetUp()112 void SetUp() override {
113 // Create an empty temporary directory for this test.
114 test_directory_ = test::JoinFilename(
115 test::OutputPath(),
116 "TestDir_AnalyzingVideoSinkTest_" +
117 std::string(
118 testing::UnitTest::GetInstance()->current_test_info()->name()));
119 test::CreateDir(test_directory_);
120 }
121
TearDown()122 void TearDown() override {
123 CleanDir(test_directory_, expected_output_files_count_);
124 }
125
ExpectOutputFilesCount(size_t count)126 void ExpectOutputFilesCount(size_t count) {
127 expected_output_files_count_ = count;
128 }
129
130 std::string test_directory_;
131 size_t expected_output_files_count_ = 0;
132 };
133
TEST_F(AnalyzingVideoSinkTest,VideoFramesAreDumpedCorrectly)134 TEST_F(AnalyzingVideoSinkTest, VideoFramesAreDumpedCorrectly) {
135 VideoSubscription subscription;
136 subscription.SubscribeToPeer(
137 "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
138 VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720,
139 /*fps=*/30);
140 video_config.output_dump_options = VideoDumpOptions(test_directory_);
141
142 ExampleVideoQualityAnalyzer analyzer;
143 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
144 CreateFrameGenerator(/*width=*/1280, /*height=*/720);
145 VideoFrame frame = CreateFrame(*frame_generator);
146 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
147
148 {
149 // `helper` and `sink` has to be destroyed so all frames will be written
150 // to the disk.
151 AnalyzingVideoSinksHelper helper;
152 helper.AddConfig("alice", video_config);
153 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
154 subscription, /*report_infra_stats=*/false);
155 sink.OnFrame(frame);
156 }
157
158 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));
159
160 test::Y4mFrameReaderImpl frame_reader(
161 test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
162 /*width=*/640,
163 /*height=*/360);
164 ASSERT_TRUE(frame_reader.Init());
165 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
166 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
167 rtc::scoped_refptr<I420BufferInterface> expected_frame =
168 frame.video_frame_buffer()->ToI420();
169 double psnr = I420PSNR(*expected_frame, *actual_frame);
170 double ssim = I420SSIM(*expected_frame, *actual_frame);
171 // Actual should be downscaled version of expected.
172 EXPECT_GT(ssim, 0.98);
173 EXPECT_GT(psnr, 38);
174
175 ExpectOutputFilesCount(1);
176 }
177
TEST_F(AnalyzingVideoSinkTest,FallbackOnConfigResolutionIfNoSubscriptionProvided)178 TEST_F(AnalyzingVideoSinkTest,
179 FallbackOnConfigResolutionIfNoSubscriptionProvided) {
180 VideoSubscription subscription;
181 VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
182 /*fps=*/30);
183 video_config.output_dump_options = VideoDumpOptions(test_directory_);
184
185 ExampleVideoQualityAnalyzer analyzer;
186 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
187 CreateFrameGenerator(/*width=*/320, /*height=*/240);
188 VideoFrame frame = CreateFrame(*frame_generator);
189 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
190
191 {
192 // `helper` and `sink` has to be destroyed so all frames will be written
193 // to the disk.
194 AnalyzingVideoSinksHelper helper;
195 helper.AddConfig("alice", video_config);
196 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
197 subscription, /*report_infra_stats=*/false);
198 sink.OnFrame(frame);
199 }
200
201 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));
202
203 test::Y4mFrameReaderImpl frame_reader(
204 test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m"),
205 /*width=*/320,
206 /*height=*/240);
207 ASSERT_TRUE(frame_reader.Init());
208 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
209 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
210 rtc::scoped_refptr<I420BufferInterface> expected_frame =
211 frame.video_frame_buffer()->ToI420();
212 double psnr = I420PSNR(*expected_frame, *actual_frame);
213 double ssim = I420SSIM(*expected_frame, *actual_frame);
214 // Frames should be equal.
215 EXPECT_DOUBLE_EQ(ssim, 1.00);
216 EXPECT_DOUBLE_EQ(psnr, 48);
217
218 ExpectOutputFilesCount(1);
219 }
220
TEST_F(AnalyzingVideoSinkTest,FallbackOnConfigResolutionIfNoSubscriptionIsNotResolved)221 TEST_F(AnalyzingVideoSinkTest,
222 FallbackOnConfigResolutionIfNoSubscriptionIsNotResolved) {
223 VideoSubscription subscription;
224 subscription.SubscribeToAllPeers(
225 VideoResolution(VideoResolution::Spec::kMaxFromSender));
226 VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
227 /*fps=*/30);
228 video_config.output_dump_options = VideoDumpOptions(test_directory_);
229
230 ExampleVideoQualityAnalyzer analyzer;
231 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
232 CreateFrameGenerator(/*width=*/320, /*height=*/240);
233 VideoFrame frame = CreateFrame(*frame_generator);
234 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
235
236 {
237 // `helper` and `sink` has to be destroyed so all frames will be written
238 // to the disk.
239 AnalyzingVideoSinksHelper helper;
240 helper.AddConfig("alice", video_config);
241 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
242 subscription, /*report_infra_stats=*/false);
243 sink.OnFrame(frame);
244 }
245
246 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));
247
248 test::Y4mFrameReaderImpl frame_reader(
249 test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m"),
250 /*width=*/320,
251 /*height=*/240);
252 ASSERT_TRUE(frame_reader.Init());
253 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
254 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
255 rtc::scoped_refptr<I420BufferInterface> expected_frame =
256 frame.video_frame_buffer()->ToI420();
257 double psnr = I420PSNR(*expected_frame, *actual_frame);
258 double ssim = I420SSIM(*expected_frame, *actual_frame);
259 // Frames should be equal.
260 EXPECT_DOUBLE_EQ(ssim, 1.00);
261 EXPECT_DOUBLE_EQ(psnr, 48);
262
263 ExpectOutputFilesCount(1);
264 }
265
TEST_F(AnalyzingVideoSinkTest,VideoFramesAreDumpedCorrectlyWhenSubscriptionChanged)266 TEST_F(AnalyzingVideoSinkTest,
267 VideoFramesAreDumpedCorrectlyWhenSubscriptionChanged) {
268 VideoSubscription subscription_before;
269 subscription_before.SubscribeToPeer(
270 "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
271 VideoSubscription subscription_after;
272 subscription_after.SubscribeToPeer(
273 "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
274 VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720,
275 /*fps=*/30);
276 video_config.output_dump_options = VideoDumpOptions(test_directory_);
277
278 ExampleVideoQualityAnalyzer analyzer;
279 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
280 CreateFrameGenerator(/*width=*/1280, /*height=*/720);
281 VideoFrame frame_before = CreateFrame(*frame_generator);
282 frame_before.set_id(
283 analyzer.OnFrameCaptured("alice", "alice_video", frame_before));
284 VideoFrame frame_after = CreateFrame(*frame_generator);
285 frame_after.set_id(
286 analyzer.OnFrameCaptured("alice", "alice_video", frame_after));
287
288 {
289 // `helper` and `sink` has to be destroyed so all frames will be written
290 // to the disk.
291 AnalyzingVideoSinksHelper helper;
292 helper.AddConfig("alice", video_config);
293 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
294 subscription_before, /*report_infra_stats=*/false);
295 sink.OnFrame(frame_before);
296
297 sink.UpdateSubscription(subscription_after);
298 sink.OnFrame(frame_after);
299 }
300
301 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));
302
303 {
304 test::Y4mFrameReaderImpl frame_reader(
305 test::JoinFilename(test_directory_, "alice_video_bob_1280x720_30.y4m"),
306 /*width=*/1280,
307 /*height=*/720);
308 ASSERT_TRUE(frame_reader.Init());
309 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
310 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
311 rtc::scoped_refptr<I420BufferInterface> expected_frame =
312 frame_before.video_frame_buffer()->ToI420();
313 double psnr = I420PSNR(*expected_frame, *actual_frame);
314 double ssim = I420SSIM(*expected_frame, *actual_frame);
315 // Frames should be equal.
316 EXPECT_DOUBLE_EQ(ssim, 1.00);
317 EXPECT_DOUBLE_EQ(psnr, 48);
318 }
319 {
320 test::Y4mFrameReaderImpl frame_reader(
321 test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
322 /*width=*/640,
323 /*height=*/360);
324 ASSERT_TRUE(frame_reader.Init());
325 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
326 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
327 rtc::scoped_refptr<I420BufferInterface> expected_frame =
328 frame_after.video_frame_buffer()->ToI420();
329 double psnr = I420PSNR(*expected_frame, *actual_frame);
330 double ssim = I420SSIM(*expected_frame, *actual_frame);
331 // Actual should be downscaled version of expected.
332 EXPECT_GT(ssim, 0.98);
333 EXPECT_GT(psnr, 38);
334 }
335
336 ExpectOutputFilesCount(2);
337 }
338
TEST_F(AnalyzingVideoSinkTest,VideoFramesAreDumpedCorrectlyWhenSubscriptionChangedOnTheSameOne)339 TEST_F(AnalyzingVideoSinkTest,
340 VideoFramesAreDumpedCorrectlyWhenSubscriptionChangedOnTheSameOne) {
341 VideoSubscription subscription_before;
342 subscription_before.SubscribeToPeer(
343 "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
344 VideoSubscription subscription_after;
345 subscription_after.SubscribeToPeer(
346 "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
347 VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
348 /*fps=*/30);
349 video_config.output_dump_options = VideoDumpOptions(test_directory_);
350
351 ExampleVideoQualityAnalyzer analyzer;
352 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
353 CreateFrameGenerator(/*width=*/640, /*height=*/360);
354 VideoFrame frame_before = CreateFrame(*frame_generator);
355 frame_before.set_id(
356 analyzer.OnFrameCaptured("alice", "alice_video", frame_before));
357 VideoFrame frame_after = CreateFrame(*frame_generator);
358 frame_after.set_id(
359 analyzer.OnFrameCaptured("alice", "alice_video", frame_after));
360
361 {
362 // `helper` and `sink` has to be destroyed so all frames will be written
363 // to the disk.
364 AnalyzingVideoSinksHelper helper;
365 helper.AddConfig("alice", video_config);
366 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
367 subscription_before, /*report_infra_stats=*/false);
368 sink.OnFrame(frame_before);
369
370 sink.UpdateSubscription(subscription_after);
371 sink.OnFrame(frame_after);
372 }
373
374 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));
375
376 {
377 test::Y4mFrameReaderImpl frame_reader(
378 test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
379 /*width=*/640,
380 /*height=*/360);
381 ASSERT_TRUE(frame_reader.Init());
382 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(2));
383 // Read the first frame.
384 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
385 rtc::scoped_refptr<I420BufferInterface> expected_frame =
386 frame_before.video_frame_buffer()->ToI420();
387 // Frames should be equal.
388 EXPECT_DOUBLE_EQ(I420SSIM(*expected_frame, *actual_frame), 1.00);
389 EXPECT_DOUBLE_EQ(I420PSNR(*expected_frame, *actual_frame), 48);
390 // Read the second frame.
391 actual_frame = frame_reader.ReadFrame();
392 expected_frame = frame_after.video_frame_buffer()->ToI420();
393 // Frames should be equal.
394 EXPECT_DOUBLE_EQ(I420SSIM(*expected_frame, *actual_frame), 1.00);
395 EXPECT_DOUBLE_EQ(I420PSNR(*expected_frame, *actual_frame), 48);
396 }
397
398 ExpectOutputFilesCount(1);
399 }
400
TEST_F(AnalyzingVideoSinkTest,SmallDiviationsInAspectRationAreAllowed)401 TEST_F(AnalyzingVideoSinkTest, SmallDiviationsInAspectRationAreAllowed) {
402 VideoSubscription subscription;
403 subscription.SubscribeToPeer(
404 "alice", VideoResolution(/*width=*/480, /*height=*/270, /*fps=*/30));
405 VideoConfig video_config("alice_video", /*width=*/480, /*height=*/270,
406 /*fps=*/30);
407 video_config.output_dump_options = VideoDumpOptions(test_directory_);
408
409 ExampleVideoQualityAnalyzer analyzer;
410 // Generator produces downscaled frames with a bit different aspect ration.
411 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
412 CreateFrameGenerator(/*width=*/240, /*height=*/136);
413 VideoFrame frame = CreateFrame(*frame_generator);
414 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
415
416 {
417 // `helper` and `sink` has to be destroyed so all frames will be written
418 // to the disk.
419 AnalyzingVideoSinksHelper helper;
420 helper.AddConfig("alice", video_config);
421 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
422 subscription, /*report_infra_stats=*/false);
423 sink.OnFrame(frame);
424 }
425
426 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));
427
428 {
429 test::Y4mFrameReaderImpl frame_reader(
430 test::JoinFilename(test_directory_, "alice_video_bob_480x270_30.y4m"),
431 /*width=*/480,
432 /*height=*/270);
433 ASSERT_TRUE(frame_reader.Init());
434 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
435 // Read the first frame.
436 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
437 rtc::scoped_refptr<I420BufferInterface> expected_frame =
438 frame.video_frame_buffer()->ToI420();
439 // Actual frame is upscaled version of the expected. But because rendered
440 // resolution is equal to the actual frame size we need to upscale expected
441 // during comparison and then they have to be the same.
442 EXPECT_DOUBLE_EQ(I420SSIM(*actual_frame, *expected_frame), 1);
443 EXPECT_DOUBLE_EQ(I420PSNR(*actual_frame, *expected_frame), 48);
444 }
445
446 ExpectOutputFilesCount(1);
447 }
448
TEST_F(AnalyzingVideoSinkTest,VideoFramesIdsAreDumpedWhenRequested)449 TEST_F(AnalyzingVideoSinkTest, VideoFramesIdsAreDumpedWhenRequested) {
450 VideoSubscription subscription;
451 subscription.SubscribeToPeer(
452 "alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/30));
453 VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
454 /*fps=*/30);
455 video_config.output_dump_options =
456 VideoDumpOptions(test_directory_, /*export_frame_ids=*/true);
457
458 ExampleVideoQualityAnalyzer analyzer;
459 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
460 CreateFrameGenerator(/*width=*/320, /*height=*/240);
461
462 std::vector<std::string> expected_frame_ids;
463 {
464 // `helper` and `sink` has to be destroyed so all frames will be written
465 // to the disk.
466 AnalyzingVideoSinksHelper helper;
467 helper.AddConfig("alice", video_config);
468 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
469 subscription, /*report_infra_stats=*/false);
470 for (int i = 0; i < 10; ++i) {
471 VideoFrame frame = CreateFrame(*frame_generator);
472 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
473 expected_frame_ids.push_back(std::to_string(frame.id()));
474 sink.OnFrame(frame);
475 }
476 }
477
478 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(10)));
479
480 AssertFrameIdsAre(
481 test::JoinFilename(test_directory_,
482 "alice_video_bob_320x240_30.frame_ids.txt"),
483 expected_frame_ids);
484
485 ExpectOutputFilesCount(2);
486 }
487
TEST_F(AnalyzingVideoSinkTest,VideoFramesAndIdsAreDumpedWithFixedFpsWhenRequested)488 TEST_F(AnalyzingVideoSinkTest,
489 VideoFramesAndIdsAreDumpedWithFixedFpsWhenRequested) {
490 GlobalSimulatedTimeController simulated_time(Timestamp::Seconds(100000));
491
492 VideoSubscription subscription;
493 subscription.SubscribeToPeer(
494 "alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/10));
495 VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
496 /*fps=*/10);
497 video_config.output_dump_options =
498 VideoDumpOptions(test_directory_, /*export_frame_ids=*/true);
499 video_config.output_dump_use_fixed_framerate = true;
500
501 ExampleVideoQualityAnalyzer analyzer;
502 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
503 CreateFrameGenerator(/*width=*/320, /*height=*/240);
504
505 VideoFrame frame1 = CreateFrame(*frame_generator);
506 frame1.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame1));
507 VideoFrame frame2 = CreateFrame(*frame_generator);
508 frame2.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame2));
509
510 {
511 // `helper` and `sink` has to be destroyed so all frames will be written
512 // to the disk.
513 AnalyzingVideoSinksHelper helper;
514 helper.AddConfig("alice", video_config);
515 AnalyzingVideoSink sink("bob", simulated_time.GetClock(), analyzer, helper,
516 subscription, /*report_infra_stats=*/false);
517 sink.OnFrame(frame1);
518 // Advance almost 1 second, so the first frame has to be repeated 9 time
519 // more.
520 simulated_time.AdvanceTime(TimeDelta::Millis(990));
521 sink.OnFrame(frame2);
522 simulated_time.AdvanceTime(TimeDelta::Millis(100));
523 }
524
525 EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));
526
527 test::Y4mFrameReaderImpl frame_reader(
528 test::JoinFilename(test_directory_, "alice_video_bob_320x240_10.y4m"),
529 /*width=*/320,
530 /*height=*/240);
531 ASSERT_TRUE(frame_reader.Init());
532 EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(11));
533 for (int i = 0; i < 10; ++i) {
534 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
535 rtc::scoped_refptr<I420BufferInterface> expected_frame =
536 frame1.video_frame_buffer()->ToI420();
537 double psnr = I420PSNR(*expected_frame, *actual_frame);
538 double ssim = I420SSIM(*expected_frame, *actual_frame);
539 // Frames should be equal.
540 EXPECT_DOUBLE_EQ(ssim, 1.00);
541 EXPECT_DOUBLE_EQ(psnr, 48);
542 }
543 rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
544 rtc::scoped_refptr<I420BufferInterface> expected_frame =
545 frame2.video_frame_buffer()->ToI420();
546 double psnr = I420PSNR(*expected_frame, *actual_frame);
547 double ssim = I420SSIM(*expected_frame, *actual_frame);
548 // Frames should be equal.
549 EXPECT_DOUBLE_EQ(ssim, 1.00);
550 EXPECT_DOUBLE_EQ(psnr, 48);
551
552 AssertFrameIdsAre(
553 test::JoinFilename(test_directory_,
554 "alice_video_bob_320x240_10.frame_ids.txt"),
555 {std::to_string(frame1.id()), std::to_string(frame1.id()),
556 std::to_string(frame1.id()), std::to_string(frame1.id()),
557 std::to_string(frame1.id()), std::to_string(frame1.id()),
558 std::to_string(frame1.id()), std::to_string(frame1.id()),
559 std::to_string(frame1.id()), std::to_string(frame1.id()),
560 std::to_string(frame2.id())});
561
562 ExpectOutputFilesCount(2);
563 }
564
TEST_F(AnalyzingVideoSinkTest,InfraMetricsCollectedWhenRequested)565 TEST_F(AnalyzingVideoSinkTest, InfraMetricsCollectedWhenRequested) {
566 VideoSubscription subscription;
567 subscription.SubscribeToPeer(
568 "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
569 VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
570 /*fps=*/30);
571
572 ExampleVideoQualityAnalyzer analyzer;
573 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
574 CreateFrameGenerator(/*width=*/640, /*height=*/360);
575 VideoFrame frame = CreateFrame(*frame_generator);
576 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
577
578 AnalyzingVideoSinksHelper helper;
579 helper.AddConfig("alice", video_config);
580 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
581 subscription, /*report_infra_stats=*/true);
582 sink.OnFrame(frame);
583
584 AnalyzingVideoSink::Stats stats = sink.stats();
585 EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(1));
586 EXPECT_THAT(stats.scaling_tims_ms.GetAverage(), Ge(0));
587 EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(1));
588 EXPECT_THAT(stats.analyzing_sink_processing_time_ms.GetAverage(),
589 Ge(stats.scaling_tims_ms.GetAverage()));
590
591 ExpectOutputFilesCount(0);
592 }
593
TEST_F(AnalyzingVideoSinkTest,InfraMetricsNotCollectedWhenNotRequested)594 TEST_F(AnalyzingVideoSinkTest, InfraMetricsNotCollectedWhenNotRequested) {
595 VideoSubscription subscription;
596 subscription.SubscribeToPeer(
597 "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
598 VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
599 /*fps=*/30);
600
601 ExampleVideoQualityAnalyzer analyzer;
602 std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
603 CreateFrameGenerator(/*width=*/640, /*height=*/360);
604 VideoFrame frame = CreateFrame(*frame_generator);
605 frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
606
607 AnalyzingVideoSinksHelper helper;
608 helper.AddConfig("alice", video_config);
609 AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
610 subscription, /*report_infra_stats=*/false);
611 sink.OnFrame(frame);
612
613 AnalyzingVideoSink::Stats stats = sink.stats();
614 EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(0));
615 EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(0));
616
617 ExpectOutputFilesCount(0);
618 }
619
620 } // namespace
621 } // namespace webrtc_pc_e2e
622 } // namespace webrtc
623