/*
 *  Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "test/pc/e2e/analyzer/video/analyzing_video_sink.h"

#include <stdio.h>

#include <string>
#include <vector>

#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/scoped_refptr.h"
#include "api/test/create_frame_generator.h"
#include "api/test/frame_generator_interface.h"
#include "api/test/pclf/media_configuration.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/pc/e2e/analyzer/video/example_video_quality_analyzer.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
#include "test/time_controller/simulated_time_controller.h"

namespace webrtc {
namespace webrtc_pc_e2e {
namespace {

using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Test;

// Remove files and directories in a directory non-recursively.
void CleanDir(absl::string_view dir, size_t expected_output_files_count) {
  absl::optional<std::vector<std::string>> dir_content =
      test::ReadDirectory(dir);
  if (expected_output_files_count == 0) {
    ASSERT_TRUE(!dir_content.has_value() || dir_content->empty())
        << "Empty directory is expected";
  } else {
    ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!";
    EXPECT_EQ(dir_content->size(), expected_output_files_count);
    for (const auto& entry : *dir_content) {
      if (test::DirExists(entry)) {
        EXPECT_TRUE(test::RemoveDir(entry))
            << "Failed to remove sub directory: " << entry;
      } else if (test::FileExists(entry)) {
        EXPECT_TRUE(test::RemoveFile(entry))
            << "Failed to remove file: " << entry;
      } else {
        FAIL() << "Can't remove unknown file type: " << entry;
      }
    }
  }
  EXPECT_TRUE(test::RemoveDir(dir)) << "Failed to remove directory: " << dir;
}

VideoFrame CreateFrame(test::FrameGeneratorInterface& frame_generator) {
  test::FrameGeneratorInterface::VideoFrameData frame_data =
      frame_generator.NextFrame();
  return VideoFrame::Builder()
      .set_video_frame_buffer(frame_data.buffer)
      .set_update_rect(frame_data.update_rect)
      .build();
}

std::unique_ptr<test::FrameGeneratorInterface> CreateFrameGenerator(
    size_t width,
    size_t height) {
  return test::CreateSquareFrameGenerator(width, height,
                                          /*type=*/absl::nullopt,
                                          /*num_squares=*/absl::nullopt);
}

void AssertFrameIdsAre(const std::string& filename,
                       std::vector<std::string> expected_ids) {
  FILE* file = fopen(filename.c_str(), "r");
  ASSERT_TRUE(file != nullptr) << "Failed to open frame ids file: " << filename;
  std::vector<std::string> actual_ids;
  char buffer[8];
  while (fgets(buffer, sizeof buffer, file) != nullptr) {
    std::string current_id(buffer);
    EXPECT_GE(current_id.size(), 2lu)
        << "Found invalid frame id: [" << current_id << "]";
    if (current_id.size() < 2) {
      continue;
    }
    // Trim "\n" at the end.
    actual_ids.push_back(current_id.substr(0, current_id.size() - 1));
  }
  fclose(file);
  EXPECT_THAT(actual_ids, ElementsAreArray(expected_ids));
}

class AnalyzingVideoSinkTest : public Test {
 protected:
  ~AnalyzingVideoSinkTest() override = default;

  void SetUp() override {
    // Create an empty temporary directory for this test.
    test_directory_ = test::JoinFilename(
        test::OutputPath(),
        "TestDir_AnalyzingVideoSinkTest_" +
            std::string(
                testing::UnitTest::GetInstance()->current_test_info()->name()));
    test::CreateDir(test_directory_);
  }

  void TearDown() override {
    CleanDir(test_directory_, expected_output_files_count_);
  }

  void ExpectOutputFilesCount(size_t count) {
    expected_output_files_count_ = count;
  }

  std::string test_directory_;
  size_t expected_output_files_count_ = 0;
};

TEST_F(AnalyzingVideoSinkTest, VideoFramesAreDumpedCorrectly) {
  VideoSubscription subscription;
  subscription.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720,
                           /*fps=*/30);
  video_config.output_dump_options = VideoDumpOptions(test_directory_);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/1280, /*height=*/720);
  VideoFrame frame = CreateFrame(*frame_generator);
  frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription, /*report_infra_stats=*/false);
    sink.OnFrame(frame);
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));

  test::Y4mFrameReaderImpl frame_reader(
      test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
      /*width=*/640,
      /*height=*/360);
  ASSERT_TRUE(frame_reader.Init());
  EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
  rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
  rtc::scoped_refptr<I420BufferInterface> expected_frame =
      frame.video_frame_buffer()->ToI420();
  double psnr = I420PSNR(*expected_frame, *actual_frame);
  double ssim = I420SSIM(*expected_frame, *actual_frame);
  // Actual should be downscaled version of expected.
  EXPECT_GT(ssim, 0.98);
  EXPECT_GT(psnr, 38);

  ExpectOutputFilesCount(1);
}

TEST_F(AnalyzingVideoSinkTest,
       FallbackOnConfigResolutionIfNoSubscriptionProvided) {
  VideoSubscription subscription;
  VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
                           /*fps=*/30);
  video_config.output_dump_options = VideoDumpOptions(test_directory_);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/320, /*height=*/240);
  VideoFrame frame = CreateFrame(*frame_generator);
  frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription, /*report_infra_stats=*/false);
    sink.OnFrame(frame);
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));

  test::Y4mFrameReaderImpl frame_reader(
      test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m"),
      /*width=*/320,
      /*height=*/240);
  ASSERT_TRUE(frame_reader.Init());
  EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
  rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
  rtc::scoped_refptr<I420BufferInterface> expected_frame =
      frame.video_frame_buffer()->ToI420();
  double psnr = I420PSNR(*expected_frame, *actual_frame);
  double ssim = I420SSIM(*expected_frame, *actual_frame);
  // Frames should be equal.
  EXPECT_DOUBLE_EQ(ssim, 1.00);
  EXPECT_DOUBLE_EQ(psnr, 48);

  ExpectOutputFilesCount(1);
}

TEST_F(AnalyzingVideoSinkTest,
       FallbackOnConfigResolutionIfNoSubscriptionIsNotResolved) {
  VideoSubscription subscription;
  subscription.SubscribeToAllPeers(
      VideoResolution(VideoResolution::Spec::kMaxFromSender));
  VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
                           /*fps=*/30);
  video_config.output_dump_options = VideoDumpOptions(test_directory_);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/320, /*height=*/240);
  VideoFrame frame = CreateFrame(*frame_generator);
  frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription, /*report_infra_stats=*/false);
    sink.OnFrame(frame);
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));

  test::Y4mFrameReaderImpl frame_reader(
      test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m"),
      /*width=*/320,
      /*height=*/240);
  ASSERT_TRUE(frame_reader.Init());
  EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
  rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
  rtc::scoped_refptr<I420BufferInterface> expected_frame =
      frame.video_frame_buffer()->ToI420();
  double psnr = I420PSNR(*expected_frame, *actual_frame);
  double ssim = I420SSIM(*expected_frame, *actual_frame);
  // Frames should be equal.
  EXPECT_DOUBLE_EQ(ssim, 1.00);
  EXPECT_DOUBLE_EQ(psnr, 48);

  ExpectOutputFilesCount(1);
}

TEST_F(AnalyzingVideoSinkTest,
       VideoFramesAreDumpedCorrectlyWhenSubscriptionChanged) {
  VideoSubscription subscription_before;
  subscription_before.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
  VideoSubscription subscription_after;
  subscription_after.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720,
                           /*fps=*/30);
  video_config.output_dump_options = VideoDumpOptions(test_directory_);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/1280, /*height=*/720);
  VideoFrame frame_before = CreateFrame(*frame_generator);
  frame_before.set_id(
      analyzer.OnFrameCaptured("alice", "alice_video", frame_before));
  VideoFrame frame_after = CreateFrame(*frame_generator);
  frame_after.set_id(
      analyzer.OnFrameCaptured("alice", "alice_video", frame_after));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription_before, /*report_infra_stats=*/false);
    sink.OnFrame(frame_before);

    sink.UpdateSubscription(subscription_after);
    sink.OnFrame(frame_after);
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));

  {
    test::Y4mFrameReaderImpl frame_reader(
        test::JoinFilename(test_directory_, "alice_video_bob_1280x720_30.y4m"),
        /*width=*/1280,
        /*height=*/720);
    ASSERT_TRUE(frame_reader.Init());
    EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
    rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
    rtc::scoped_refptr<I420BufferInterface> expected_frame =
        frame_before.video_frame_buffer()->ToI420();
    double psnr = I420PSNR(*expected_frame, *actual_frame);
    double ssim = I420SSIM(*expected_frame, *actual_frame);
    // Frames should be equal.
    EXPECT_DOUBLE_EQ(ssim, 1.00);
    EXPECT_DOUBLE_EQ(psnr, 48);
  }
  {
    test::Y4mFrameReaderImpl frame_reader(
        test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
        /*width=*/640,
        /*height=*/360);
    ASSERT_TRUE(frame_reader.Init());
    EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
    rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
    rtc::scoped_refptr<I420BufferInterface> expected_frame =
        frame_after.video_frame_buffer()->ToI420();
    double psnr = I420PSNR(*expected_frame, *actual_frame);
    double ssim = I420SSIM(*expected_frame, *actual_frame);
    // Actual should be downscaled version of expected.
    EXPECT_GT(ssim, 0.98);
    EXPECT_GT(psnr, 38);
  }

  ExpectOutputFilesCount(2);
}

TEST_F(AnalyzingVideoSinkTest,
       VideoFramesAreDumpedCorrectlyWhenSubscriptionChangedOnTheSameOne) {
  VideoSubscription subscription_before;
  subscription_before.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
  VideoSubscription subscription_after;
  subscription_after.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
                           /*fps=*/30);
  video_config.output_dump_options = VideoDumpOptions(test_directory_);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/640, /*height=*/360);
  VideoFrame frame_before = CreateFrame(*frame_generator);
  frame_before.set_id(
      analyzer.OnFrameCaptured("alice", "alice_video", frame_before));
  VideoFrame frame_after = CreateFrame(*frame_generator);
  frame_after.set_id(
      analyzer.OnFrameCaptured("alice", "alice_video", frame_after));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription_before, /*report_infra_stats=*/false);
    sink.OnFrame(frame_before);

    sink.UpdateSubscription(subscription_after);
    sink.OnFrame(frame_after);
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));

  {
    test::Y4mFrameReaderImpl frame_reader(
        test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
        /*width=*/640,
        /*height=*/360);
    ASSERT_TRUE(frame_reader.Init());
    EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(2));
    // Read the first frame.
    rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
    rtc::scoped_refptr<I420BufferInterface> expected_frame =
        frame_before.video_frame_buffer()->ToI420();
    // Frames should be equal.
    EXPECT_DOUBLE_EQ(I420SSIM(*expected_frame, *actual_frame), 1.00);
    EXPECT_DOUBLE_EQ(I420PSNR(*expected_frame, *actual_frame), 48);
    // Read the second frame.
    actual_frame = frame_reader.ReadFrame();
    expected_frame = frame_after.video_frame_buffer()->ToI420();
    // Frames should be equal.
    EXPECT_DOUBLE_EQ(I420SSIM(*expected_frame, *actual_frame), 1.00);
    EXPECT_DOUBLE_EQ(I420PSNR(*expected_frame, *actual_frame), 48);
  }

  ExpectOutputFilesCount(1);
}

TEST_F(AnalyzingVideoSinkTest, SmallDiviationsInAspectRationAreAllowed) {
  VideoSubscription subscription;
  subscription.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/480, /*height=*/270, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/480, /*height=*/270,
                           /*fps=*/30);
  video_config.output_dump_options = VideoDumpOptions(test_directory_);

  ExampleVideoQualityAnalyzer analyzer;
  // Generator produces downscaled frames with a bit different aspect ration.
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/240, /*height=*/136);
  VideoFrame frame = CreateFrame(*frame_generator);
  frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription, /*report_infra_stats=*/false);
    sink.OnFrame(frame);
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));

  {
    test::Y4mFrameReaderImpl frame_reader(
        test::JoinFilename(test_directory_, "alice_video_bob_480x270_30.y4m"),
        /*width=*/480,
        /*height=*/270);
    ASSERT_TRUE(frame_reader.Init());
    EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
    // Read the first frame.
    rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
    rtc::scoped_refptr<I420BufferInterface> expected_frame =
        frame.video_frame_buffer()->ToI420();
    // Actual frame is upscaled version of the expected. But because rendered
    // resolution is equal to the actual frame size we need to upscale expected
    // during comparison and then they have to be the same.
    EXPECT_DOUBLE_EQ(I420SSIM(*actual_frame, *expected_frame), 1);
    EXPECT_DOUBLE_EQ(I420PSNR(*actual_frame, *expected_frame), 48);
  }

  ExpectOutputFilesCount(1);
}

TEST_F(AnalyzingVideoSinkTest, VideoFramesIdsAreDumpedWhenRequested) {
  VideoSubscription subscription;
  subscription.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
                           /*fps=*/30);
  video_config.output_dump_options =
      VideoDumpOptions(test_directory_, /*export_frame_ids=*/true);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/320, /*height=*/240);

  std::vector<std::string> expected_frame_ids;
  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                            subscription, /*report_infra_stats=*/false);
    for (int i = 0; i < 10; ++i) {
      VideoFrame frame = CreateFrame(*frame_generator);
      frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
      expected_frame_ids.push_back(std::to_string(frame.id()));
      sink.OnFrame(frame);
    }
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(10)));

  AssertFrameIdsAre(
      test::JoinFilename(test_directory_,
                         "alice_video_bob_320x240_30.frame_ids.txt"),
      expected_frame_ids);

  ExpectOutputFilesCount(2);
}

TEST_F(AnalyzingVideoSinkTest,
       VideoFramesAndIdsAreDumpedWithFixedFpsWhenRequested) {
  GlobalSimulatedTimeController simulated_time(Timestamp::Seconds(100000));

  VideoSubscription subscription;
  subscription.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/10));
  VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
                           /*fps=*/10);
  video_config.output_dump_options =
      VideoDumpOptions(test_directory_, /*export_frame_ids=*/true);
  video_config.output_dump_use_fixed_framerate = true;

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/320, /*height=*/240);

  VideoFrame frame1 = CreateFrame(*frame_generator);
  frame1.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame1));
  VideoFrame frame2 = CreateFrame(*frame_generator);
  frame2.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame2));

  {
    // `helper` and `sink` has to be destroyed so all frames will be written
    // to the disk.
    AnalyzingVideoSinksHelper helper;
    helper.AddConfig("alice", video_config);
    AnalyzingVideoSink sink("bob", simulated_time.GetClock(), analyzer, helper,
                            subscription, /*report_infra_stats=*/false);
    sink.OnFrame(frame1);
    // Advance almost 1 second, so the first frame has to be repeated 9 time
    // more.
    simulated_time.AdvanceTime(TimeDelta::Millis(990));
    sink.OnFrame(frame2);
    simulated_time.AdvanceTime(TimeDelta::Millis(100));
  }

  EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));

  test::Y4mFrameReaderImpl frame_reader(
      test::JoinFilename(test_directory_, "alice_video_bob_320x240_10.y4m"),
      /*width=*/320,
      /*height=*/240);
  ASSERT_TRUE(frame_reader.Init());
  EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(11));
  for (int i = 0; i < 10; ++i) {
    rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
    rtc::scoped_refptr<I420BufferInterface> expected_frame =
        frame1.video_frame_buffer()->ToI420();
    double psnr = I420PSNR(*expected_frame, *actual_frame);
    double ssim = I420SSIM(*expected_frame, *actual_frame);
    // Frames should be equal.
    EXPECT_DOUBLE_EQ(ssim, 1.00);
    EXPECT_DOUBLE_EQ(psnr, 48);
  }
  rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
  rtc::scoped_refptr<I420BufferInterface> expected_frame =
      frame2.video_frame_buffer()->ToI420();
  double psnr = I420PSNR(*expected_frame, *actual_frame);
  double ssim = I420SSIM(*expected_frame, *actual_frame);
  // Frames should be equal.
  EXPECT_DOUBLE_EQ(ssim, 1.00);
  EXPECT_DOUBLE_EQ(psnr, 48);

  AssertFrameIdsAre(
      test::JoinFilename(test_directory_,
                         "alice_video_bob_320x240_10.frame_ids.txt"),
      {std::to_string(frame1.id()), std::to_string(frame1.id()),
       std::to_string(frame1.id()), std::to_string(frame1.id()),
       std::to_string(frame1.id()), std::to_string(frame1.id()),
       std::to_string(frame1.id()), std::to_string(frame1.id()),
       std::to_string(frame1.id()), std::to_string(frame1.id()),
       std::to_string(frame2.id())});

  ExpectOutputFilesCount(2);
}

TEST_F(AnalyzingVideoSinkTest, InfraMetricsCollectedWhenRequested) {
  VideoSubscription subscription;
  subscription.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
                           /*fps=*/30);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/640, /*height=*/360);
  VideoFrame frame = CreateFrame(*frame_generator);
  frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));

  AnalyzingVideoSinksHelper helper;
  helper.AddConfig("alice", video_config);
  AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                          subscription, /*report_infra_stats=*/true);
  sink.OnFrame(frame);

  AnalyzingVideoSink::Stats stats = sink.stats();
  EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(1));
  EXPECT_THAT(stats.scaling_tims_ms.GetAverage(), Ge(0));
  EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(1));
  EXPECT_THAT(stats.analyzing_sink_processing_time_ms.GetAverage(),
              Ge(stats.scaling_tims_ms.GetAverage()));

  ExpectOutputFilesCount(0);
}

TEST_F(AnalyzingVideoSinkTest, InfraMetricsNotCollectedWhenNotRequested) {
  VideoSubscription subscription;
  subscription.SubscribeToPeer(
      "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
  VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
                           /*fps=*/30);

  ExampleVideoQualityAnalyzer analyzer;
  std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
      CreateFrameGenerator(/*width=*/640, /*height=*/360);
  VideoFrame frame = CreateFrame(*frame_generator);
  frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));

  AnalyzingVideoSinksHelper helper;
  helper.AddConfig("alice", video_config);
  AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
                          subscription, /*report_infra_stats=*/false);
  sink.OnFrame(frame);

  AnalyzingVideoSink::Stats stats = sink.stats();
  EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(0));
  EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(0));

  ExpectOutputFilesCount(0);
}

}  // namespace
}  // namespace webrtc_pc_e2e
}  // namespace webrtc
