• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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