/*
 *  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/video_codec_tester.h"

#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "api/test/mock_video_decoder.h"
#include "api/test/mock_video_decoder_factory.h"
#include "api/test/mock_video_encoder.h"
#include "api/test/mock_video_encoder_factory.h"
#include "api/units/data_rate.h"
#include "api/units/data_size.h"
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "api/video_codecs/scalability_mode.h"
#include "api/video_codecs/video_decoder.h"
#include "api/video_codecs/video_encoder.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "modules/video_coding/svc/scalability_mode_util.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"

namespace webrtc {
namespace test {

namespace {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAreArray;
using ::testing::Values;

using VideoCodecStats = VideoCodecTester::VideoCodecStats;
using VideoSourceSettings = VideoCodecTester::VideoSourceSettings;
using CodedVideoSource = VideoCodecTester::CodedVideoSource;
using EncodingSettings = VideoCodecTester::EncodingSettings;
using LayerSettings = EncodingSettings::LayerSettings;
using LayerId = VideoCodecTester::LayerId;
using DecoderSettings = VideoCodecTester::DecoderSettings;
using EncoderSettings = VideoCodecTester::EncoderSettings;
using PacingSettings = VideoCodecTester::PacingSettings;
using PacingMode = PacingSettings::PacingMode;
using Filter = VideoCodecStats::Filter;
using Frame = VideoCodecTester::VideoCodecStats::Frame;
using Stream = VideoCodecTester::VideoCodecStats::Stream;

constexpr int kWidth = 2;
constexpr int kHeight = 2;
const DataRate kTargetLayerBitrate = DataRate::BytesPerSec(100);
const Frequency kTargetFramerate = Frequency::Hertz(30);
constexpr Frequency k90kHz = Frequency::Hertz(90000);

rtc::scoped_refptr<I420Buffer> CreateYuvBuffer(uint8_t y = 0,
                                               uint8_t u = 0,
                                               uint8_t v = 0) {
  rtc::scoped_refptr<I420Buffer> buffer(I420Buffer::Create(2, 2));

  libyuv::I420Rect(buffer->MutableDataY(), buffer->StrideY(),
                   buffer->MutableDataU(), buffer->StrideU(),
                   buffer->MutableDataV(), buffer->StrideV(), 0, 0,
                   buffer->width(), buffer->height(), y, u, v);
  return buffer;
}

// TODO(ssilkin): Wrap this into a class that removes file in dtor.
std::string CreateYuvFile(int width, int height, int num_frames) {
  std::string path = webrtc::test::TempFilename(webrtc::test::OutputPath(),
                                                "video_codec_tester_unittest");
  FILE* file = fopen(path.c_str(), "wb");
  for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
    // For purposes of testing quality estimation, we need Y, U, V values in
    // source and decoded video to be unique and deterministic. In source video
    // we make them functions of frame number. The test decoder makes them
    // functions of encoded frame size in decoded video.
    uint8_t y = (frame_num * 3 + 0) & 255;
    uint8_t u = (frame_num * 3 + 1) & 255;
    uint8_t v = (frame_num * 3 + 2) & 255;
    rtc::scoped_refptr<I420Buffer> buffer = CreateYuvBuffer(y, u, v);
    fwrite(buffer->DataY(), 1, width * height, file);
    int chroma_size_bytes = (width + 1) / 2 * (height + 1) / 2;
    fwrite(buffer->DataU(), 1, chroma_size_bytes, file);
    fwrite(buffer->DataV(), 1, chroma_size_bytes, file);
  }
  fclose(file);
  return path;
}

class TestVideoEncoder : public MockVideoEncoder {
 public:
  TestVideoEncoder(ScalabilityMode scalability_mode,
                   std::vector<std::vector<Frame>> encoded_frames)
      : scalability_mode_(scalability_mode), encoded_frames_(encoded_frames) {}
  int32_t Encode(const VideoFrame& input_frame,
                 const std::vector<VideoFrameType>*) override {
    for (const Frame& frame : encoded_frames_[num_encoded_frames_]) {
      if (frame.frame_size.IsZero()) {
        continue;  // Frame drop.
      }
      EncodedImage encoded_frame;
      encoded_frame._encodedWidth = frame.width;
      encoded_frame._encodedHeight = frame.height;
      encoded_frame.SetFrameType(frame.keyframe
                                     ? VideoFrameType::kVideoFrameKey
                                     : VideoFrameType::kVideoFrameDelta);
      encoded_frame.SetRtpTimestamp(input_frame.timestamp());
      encoded_frame.SetSpatialIndex(frame.layer_id.spatial_idx);
      encoded_frame.SetTemporalIndex(frame.layer_id.temporal_idx);
      encoded_frame.SetEncodedData(
          EncodedImageBuffer::Create(frame.frame_size.bytes()));
      CodecSpecificInfo codec_specific_info;
      codec_specific_info.scalability_mode = scalability_mode_;
      callback_->OnEncodedImage(encoded_frame, &codec_specific_info);
    }
    ++num_encoded_frames_;
    return WEBRTC_VIDEO_CODEC_OK;
  }

  int32_t RegisterEncodeCompleteCallback(
      EncodedImageCallback* callback) override {
    callback_ = callback;
    return WEBRTC_VIDEO_CODEC_OK;
  }

 private:
  ScalabilityMode scalability_mode_;
  std::vector<std::vector<Frame>> encoded_frames_;
  int num_encoded_frames_ = 0;
  EncodedImageCallback* callback_;
};

class TestVideoDecoder : public MockVideoDecoder {
 public:
  int32_t Decode(const EncodedImage& encoded_frame, int64_t) {
    uint8_t y = (encoded_frame.size() + 0) & 255;
    uint8_t u = (encoded_frame.size() + 2) & 255;
    uint8_t v = (encoded_frame.size() + 4) & 255;
    rtc::scoped_refptr<I420Buffer> frame_buffer = CreateYuvBuffer(y, u, v);
    VideoFrame decoded_frame =
        VideoFrame::Builder()
            .set_video_frame_buffer(frame_buffer)
            .set_timestamp_rtp(encoded_frame.RtpTimestamp())
            .build();
    callback_->Decoded(decoded_frame);
    frame_sizes_.push_back(DataSize::Bytes(encoded_frame.size()));
    return WEBRTC_VIDEO_CODEC_OK;
  }

  int32_t RegisterDecodeCompleteCallback(DecodedImageCallback* callback) {
    callback_ = callback;
    return WEBRTC_VIDEO_CODEC_OK;
  }

  const std::vector<DataSize>& frame_sizes() const { return frame_sizes_; }

 private:
  DecodedImageCallback* callback_;
  std::vector<DataSize> frame_sizes_;
};

class VideoCodecTesterTest : public ::testing::Test {
 public:
  std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
      std::string codec_type,
      ScalabilityMode scalability_mode,
      std::vector<std::vector<Frame>> encoded_frames) {
    int num_frames = encoded_frames.size();
    std::string yuv_path = CreateYuvFile(kWidth, kHeight, num_frames);
    VideoSourceSettings video_source_settings{
        .file_path = yuv_path,
        .resolution = {.width = kWidth, .height = kHeight},
        .framerate = kTargetFramerate};

    NiceMock<MockVideoEncoderFactory> encoder_factory;
    ON_CALL(encoder_factory, CreateVideoEncoder)
        .WillByDefault([&](const SdpVideoFormat&) {
          return std::make_unique<NiceMock<TestVideoEncoder>>(scalability_mode,
                                                              encoded_frames);
        });

    NiceMock<MockVideoDecoderFactory> decoder_factory;
    ON_CALL(decoder_factory, CreateVideoDecoder)
        .WillByDefault([&](const SdpVideoFormat&) {
          // Video codec tester destroyes decoder at the end of test. Test
          // decoder collects stats which we need to access after test. To keep
          // the decode alive we wrap it into a wrapper and pass the wrapper to
          // the tester.
          class DecoderWrapper : public TestVideoDecoder {
           public:
            explicit DecoderWrapper(TestVideoDecoder* decoder)
                : decoder_(decoder) {}
            int32_t Decode(const EncodedImage& encoded_frame,
                           int64_t render_time_ms) {
              return decoder_->Decode(encoded_frame, render_time_ms);
            }
            int32_t RegisterDecodeCompleteCallback(
                DecodedImageCallback* callback) {
              return decoder_->RegisterDecodeCompleteCallback(callback);
            }
            TestVideoDecoder* decoder_;
          };
          decoders_.push_back(std::make_unique<NiceMock<TestVideoDecoder>>());
          return std::make_unique<NiceMock<DecoderWrapper>>(
              decoders_.back().get());
        });

    int num_spatial_layers =
        ScalabilityModeToNumSpatialLayers(scalability_mode);
    int num_temporal_layers =
        ScalabilityModeToNumTemporalLayers(scalability_mode);
    std::map<uint32_t, EncodingSettings> encoding_settings;
    for (int frame_num = 0; frame_num < num_frames; ++frame_num) {
      std::map<LayerId, LayerSettings> layers_settings;
      for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
        for (int tidx = 0; tidx < num_temporal_layers; ++tidx) {
          layers_settings.emplace(
              LayerId{.spatial_idx = sidx, .temporal_idx = tidx},
              LayerSettings{
                  .resolution = {.width = kWidth, .height = kHeight},
                  .framerate = kTargetFramerate /
                               (1 << (num_temporal_layers - 1 - tidx)),
                  .bitrate = kTargetLayerBitrate});
        }
      }
      encoding_settings.emplace(
          encoded_frames[frame_num].front().timestamp_rtp,
          EncodingSettings{.sdp_video_format = SdpVideoFormat(codec_type),
                           .scalability_mode = scalability_mode,
                           .layers_settings = layers_settings});
    }

    std::unique_ptr<VideoCodecStats> stats =
        VideoCodecTester::RunEncodeDecodeTest(
            video_source_settings, &encoder_factory, &decoder_factory,
            EncoderSettings{}, DecoderSettings{}, encoding_settings);

    remove(yuv_path.c_str());
    return stats;
  }

 protected:
  std::vector<std::unique_ptr<TestVideoDecoder>> decoders_;
};

EncodedImage CreateEncodedImage(uint32_t timestamp_rtp) {
  EncodedImage encoded_image;
  encoded_image.SetRtpTimestamp(timestamp_rtp);
  return encoded_image;
}

class MockCodedVideoSource : public CodedVideoSource {
 public:
  MockCodedVideoSource(int num_frames, Frequency framerate)
      : num_frames_(num_frames), frame_num_(0), framerate_(framerate) {}

  absl::optional<EncodedImage> PullFrame() override {
    if (frame_num_ >= num_frames_) {
      return absl::nullopt;
    }
    uint32_t timestamp_rtp = frame_num_ * k90kHz / framerate_;
    ++frame_num_;
    return CreateEncodedImage(timestamp_rtp);
  }

 private:
  int num_frames_;
  int frame_num_;
  Frequency framerate_;
};

}  // namespace

TEST_F(VideoCodecTesterTest, Slice) {
  std::unique_ptr<VideoCodecStats> stats =
      RunEncodeDecodeTest("VP9", ScalabilityMode::kL2T2,
                          {{{.timestamp_rtp = 0,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
                             .frame_size = DataSize::Bytes(1)},
                            {.timestamp_rtp = 0,
                             .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
                             .frame_size = DataSize::Bytes(2)}},
                           {{.timestamp_rtp = 1,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
                             .frame_size = DataSize::Bytes(3)}}});
  std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/false);
  EXPECT_THAT(slice,
              ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1)),
                          Field(&Frame::frame_size, DataSize::Bytes(2)),
                          Field(&Frame::frame_size, DataSize::Bytes(3)),
                          Field(&Frame::frame_size, DataSize::Bytes(0))));

  slice = stats->Slice({.min_timestamp_rtp = 1}, /*merge=*/false);
  EXPECT_THAT(slice,
              ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(3)),
                          Field(&Frame::frame_size, DataSize::Bytes(0))));

  slice = stats->Slice({.max_timestamp_rtp = 0}, /*merge=*/false);
  EXPECT_THAT(slice,
              ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1)),
                          Field(&Frame::frame_size, DataSize::Bytes(2))));

  slice = stats->Slice({.layer_id = {{.spatial_idx = 0, .temporal_idx = 0}}},
                       /*merge=*/false);
  EXPECT_THAT(slice,
              ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1))));

  slice = stats->Slice({.layer_id = {{.spatial_idx = 0, .temporal_idx = 1}}},
                       /*merge=*/false);
  EXPECT_THAT(slice,
              ElementsAre(Field(&Frame::frame_size, DataSize::Bytes(1)),
                          Field(&Frame::frame_size, DataSize::Bytes(3))));
}

TEST_F(VideoCodecTesterTest, Merge) {
  std::unique_ptr<VideoCodecStats> stats =
      RunEncodeDecodeTest("VP8", ScalabilityMode::kL2T2_KEY,
                          {{{.timestamp_rtp = 0,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
                             .frame_size = DataSize::Bytes(1),
                             .keyframe = true},
                            {.timestamp_rtp = 0,
                             .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
                             .frame_size = DataSize::Bytes(2)}},
                           {{.timestamp_rtp = 1,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
                             .frame_size = DataSize::Bytes(4)},
                            {.timestamp_rtp = 1,
                             .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
                             .frame_size = DataSize::Bytes(8)}}});

  std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/true);
  EXPECT_THAT(
      slice,
      ElementsAre(
          AllOf(Field(&Frame::timestamp_rtp, 0), Field(&Frame::keyframe, true),
                Field(&Frame::frame_size, DataSize::Bytes(3))),
          AllOf(Field(&Frame::timestamp_rtp, 1), Field(&Frame::keyframe, false),
                Field(&Frame::frame_size, DataSize::Bytes(12)))));
}

struct AggregationTestParameters {
  Filter filter;
  double expected_keyframe_sum;
  double expected_encoded_bitrate_kbps;
  double expected_encoded_framerate_fps;
  double expected_bitrate_mismatch_pct;
  double expected_framerate_mismatch_pct;
};

class VideoCodecTesterTestAggregation
    : public VideoCodecTesterTest,
      public ::testing::WithParamInterface<AggregationTestParameters> {};

TEST_P(VideoCodecTesterTestAggregation, Aggregate) {
  AggregationTestParameters test_params = GetParam();
  std::unique_ptr<VideoCodecStats> stats =
      RunEncodeDecodeTest("VP8", ScalabilityMode::kL2T2_KEY,
                          {{// L0T0
                            {.timestamp_rtp = 0,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
                             .frame_size = DataSize::Bytes(1),
                             .keyframe = true},
                            // L1T0
                            {.timestamp_rtp = 0,
                             .layer_id = {.spatial_idx = 1, .temporal_idx = 0},
                             .frame_size = DataSize::Bytes(2)}},
                           // Emulate frame drop (frame_size = 0).
                           {{.timestamp_rtp = 3000,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 0},
                             .frame_size = DataSize::Zero()}},
                           {// L0T1
                            {.timestamp_rtp = 87000,
                             .layer_id = {.spatial_idx = 0, .temporal_idx = 1},
                             .frame_size = DataSize::Bytes(4)},
                            // L1T1
                            {.timestamp_rtp = 87000,
                             .layer_id = {.spatial_idx = 1, .temporal_idx = 1},
                             .frame_size = DataSize::Bytes(8)}}});

  Stream stream = stats->Aggregate(test_params.filter);
  EXPECT_EQ(stream.keyframe.GetSum(), test_params.expected_keyframe_sum);
  EXPECT_EQ(stream.encoded_bitrate_kbps.GetAverage(),
            test_params.expected_encoded_bitrate_kbps);
  EXPECT_EQ(stream.encoded_framerate_fps.GetAverage(),
            test_params.expected_encoded_framerate_fps);
  EXPECT_EQ(stream.bitrate_mismatch_pct.GetAverage(),
            test_params.expected_bitrate_mismatch_pct);
  EXPECT_EQ(stream.framerate_mismatch_pct.GetAverage(),
            test_params.expected_framerate_mismatch_pct);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    VideoCodecTesterTestAggregation,
    Values(
        // No filtering.
        AggregationTestParameters{
            .filter = {},
            .expected_keyframe_sum = 1,
            .expected_encoded_bitrate_kbps =
                DataRate::BytesPerSec(15).kbps<double>(),
            .expected_encoded_framerate_fps = 2,
            .expected_bitrate_mismatch_pct =
                100 * (15.0 / (kTargetLayerBitrate.bytes_per_sec() * 4) - 1),
            .expected_framerate_mismatch_pct =
                100 * (2.0 / kTargetFramerate.hertz() - 1)},
        // L0T0
        AggregationTestParameters{
            .filter = {.layer_id = {{.spatial_idx = 0, .temporal_idx = 0}}},
            .expected_keyframe_sum = 1,
            .expected_encoded_bitrate_kbps =
                DataRate::BytesPerSec(1).kbps<double>(),
            .expected_encoded_framerate_fps = 1,
            .expected_bitrate_mismatch_pct =
                100 * (1.0 / kTargetLayerBitrate.bytes_per_sec() - 1),
            .expected_framerate_mismatch_pct =
                100 * (1.0 / (kTargetFramerate.hertz() / 2) - 1)},
        // L0T1
        AggregationTestParameters{
            .filter = {.layer_id = {{.spatial_idx = 0, .temporal_idx = 1}}},
            .expected_keyframe_sum = 1,
            .expected_encoded_bitrate_kbps =
                DataRate::BytesPerSec(5).kbps<double>(),
            .expected_encoded_framerate_fps = 2,
            .expected_bitrate_mismatch_pct =
                100 * (5.0 / (kTargetLayerBitrate.bytes_per_sec() * 2) - 1),
            .expected_framerate_mismatch_pct =
                100 * (2.0 / kTargetFramerate.hertz() - 1)},
        // L1T0
        AggregationTestParameters{
            .filter = {.layer_id = {{.spatial_idx = 1, .temporal_idx = 0}}},
            .expected_keyframe_sum = 1,
            .expected_encoded_bitrate_kbps =
                DataRate::BytesPerSec(3).kbps<double>(),
            .expected_encoded_framerate_fps = 1,
            .expected_bitrate_mismatch_pct =
                100 * (3.0 / kTargetLayerBitrate.bytes_per_sec() - 1),
            .expected_framerate_mismatch_pct =
                100 * (1.0 / (kTargetFramerate.hertz() / 2) - 1)},
        // L1T1
        AggregationTestParameters{
            .filter = {.layer_id = {{.spatial_idx = 1, .temporal_idx = 1}}},
            .expected_keyframe_sum = 1,
            .expected_encoded_bitrate_kbps =
                DataRate::BytesPerSec(11).kbps<double>(),
            .expected_encoded_framerate_fps = 2,
            .expected_bitrate_mismatch_pct =
                100 * (11.0 / (kTargetLayerBitrate.bytes_per_sec() * 2) - 1),
            .expected_framerate_mismatch_pct =
                100 * (2.0 / kTargetFramerate.hertz() - 1)}));

TEST_F(VideoCodecTesterTest, Psnr) {
  std::unique_ptr<VideoCodecStats> stats = RunEncodeDecodeTest(
      "VP8", ScalabilityMode::kL1T1,
      {{{.timestamp_rtp = 0, .frame_size = DataSize::Bytes(2)}},
       {{.timestamp_rtp = 3000, .frame_size = DataSize::Bytes(6)}}});

  std::vector<Frame> slice = stats->Slice(Filter{}, /*merge=*/false);
  ASSERT_THAT(slice, SizeIs(2));
  ASSERT_TRUE(slice[0].psnr.has_value());
  ASSERT_TRUE(slice[1].psnr.has_value());
  EXPECT_NEAR(slice[0].psnr->y, 42, 1);
  EXPECT_NEAR(slice[0].psnr->u, 38, 1);
  EXPECT_NEAR(slice[0].psnr->v, 36, 1);
  EXPECT_NEAR(slice[1].psnr->y, 38, 1);
  EXPECT_NEAR(slice[1].psnr->u, 36, 1);
  EXPECT_NEAR(slice[1].psnr->v, 34, 1);
}

struct ScalabilityTestParameters {
  std::string codec_type;
  ScalabilityMode scalability_mode;
  // Temporal unit -> spatial layer -> frame size.
  std::vector<std::map<int, DataSize>> encoded_frame_sizes;
  std::vector<DataSize> expected_decode_frame_sizes;
};

class VideoCodecTesterTestScalability
    : public VideoCodecTesterTest,
      public ::testing::WithParamInterface<ScalabilityTestParameters> {};

TEST_P(VideoCodecTesterTestScalability, EncodeDecode) {
  ScalabilityTestParameters test_params = GetParam();
  std::vector<std::vector<Frame>> frames;
  for (size_t frame_num = 0; frame_num < test_params.encoded_frame_sizes.size();
       ++frame_num) {
    std::vector<Frame> temporal_unit;
    for (auto [sidx, frame_size] : test_params.encoded_frame_sizes[frame_num]) {
      temporal_unit.push_back(
          Frame{.timestamp_rtp = static_cast<uint32_t>(3000 * frame_num),
                .layer_id = {.spatial_idx = sidx, .temporal_idx = 0},
                .frame_size = frame_size,
                .keyframe = (frame_num == 0 && sidx == 0)});
    }
    frames.push_back(temporal_unit);
  }
  RunEncodeDecodeTest(test_params.codec_type, test_params.scalability_mode,
                      frames);

  size_t num_spatial_layers =
      ScalabilityModeToNumSpatialLayers(test_params.scalability_mode);
  EXPECT_EQ(num_spatial_layers, decoders_.size());

  // Collect input frame sizes from all decoders.
  std::vector<DataSize> decode_frame_sizes;
  for (const auto& decoder : decoders_) {
    const auto& frame_sizes = decoder->frame_sizes();
    decode_frame_sizes.insert(decode_frame_sizes.end(), frame_sizes.begin(),
                              frame_sizes.end());
  }
  EXPECT_THAT(decode_frame_sizes, UnorderedElementsAreArray(
                                      test_params.expected_decode_frame_sizes));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    VideoCodecTesterTestScalability,
    Values(
        ScalabilityTestParameters{
            .codec_type = "VP8",
            .scalability_mode = ScalabilityMode::kS2T1,
            .encoded_frame_sizes = {{{0, DataSize::Bytes(1)},
                                     {1, DataSize::Bytes(2)}},
                                    {{0, DataSize::Bytes(4)},
                                     // Emulate frame drop.
                                     {1, DataSize::Bytes(0)}}},
            .expected_decode_frame_sizes = {DataSize::Bytes(1),
                                            DataSize::Bytes(2),
                                            DataSize::Bytes(4)},
        },
        ScalabilityTestParameters{
            .codec_type = "VP9",
            .scalability_mode = ScalabilityMode::kL2T1,
            .encoded_frame_sizes =
                {{{0, DataSize::Bytes(1)}, {1, DataSize::Bytes(2)}},
                 {{0, DataSize::Bytes(4)}, {1, DataSize::Bytes(8)}},
                 {{0, DataSize::Bytes(16)},
                  // Emulate frame drop.
                  {1, DataSize::Bytes(0)}}},
            .expected_decode_frame_sizes =
                {DataSize::Bytes(1), DataSize::Bytes(3), DataSize::Bytes(4),
                 DataSize::Bytes(12), DataSize::Bytes(16), DataSize::Bytes(16)},
        },
        ScalabilityTestParameters{
            .codec_type = "VP9",
            .scalability_mode = ScalabilityMode::kL2T1_KEY,
            .encoded_frame_sizes =
                {{{0, DataSize::Bytes(1)}, {1, DataSize::Bytes(2)}},
                 {{0, DataSize::Bytes(4)}, {1, DataSize::Bytes(8)}},
                 {{0, DataSize::Bytes(16)},
                  // Emulate frame drop.
                  {1, DataSize::Bytes(0)}}},
            .expected_decode_frame_sizes =
                {DataSize::Bytes(1), DataSize::Bytes(3), DataSize::Bytes(4),
                 DataSize::Bytes(8), DataSize::Bytes(16)},
        },
        ScalabilityTestParameters{
            .codec_type = "VP9",
            .scalability_mode = ScalabilityMode::kS2T1,
            .encoded_frame_sizes =
                {{{0, DataSize::Bytes(1)}, {1, DataSize::Bytes(2)}},
                 {{0, DataSize::Bytes(4)}, {1, DataSize::Bytes(8)}},
                 {{0, DataSize::Bytes(16)},
                  // Emulate frame drop.
                  {1, DataSize::Bytes(0)}}},
            .expected_decode_frame_sizes =
                {DataSize::Bytes(1), DataSize::Bytes(2), DataSize::Bytes(4),
                 DataSize::Bytes(8), DataSize::Bytes(16)},
        }));

class VideoCodecTesterTestPacing
    : public ::testing::TestWithParam<std::tuple<PacingSettings, int>> {
 public:
  const int kSourceWidth = 2;
  const int kSourceHeight = 2;
  const int kNumFrames = 3;
  const int kTargetLayerBitrateKbps = 128;
  const Frequency kTargetFramerate = Frequency::Hertz(10);

  void SetUp() override {
    source_yuv_file_path_ = CreateYuvFile(kSourceWidth, kSourceHeight, 1);
  }

  void TearDown() override { remove(source_yuv_file_path_.c_str()); }

 protected:
  std::string source_yuv_file_path_;
};

TEST_P(VideoCodecTesterTestPacing, PaceEncode) {
  auto [pacing_settings, expected_delta_ms] = GetParam();
  VideoSourceSettings video_source{
      .file_path = source_yuv_file_path_,
      .resolution = {.width = kSourceWidth, .height = kSourceHeight},
      .framerate = kTargetFramerate};

  NiceMock<MockVideoEncoderFactory> encoder_factory;
  ON_CALL(encoder_factory, CreateVideoEncoder(_))
      .WillByDefault([](const SdpVideoFormat&) {
        return std::make_unique<NiceMock<MockVideoEncoder>>();
      });

  std::map<uint32_t, EncodingSettings> encoding_settings =
      VideoCodecTester::CreateEncodingSettings(
          "VP8", "L1T1", kSourceWidth, kSourceHeight, {kTargetLayerBitrateKbps},
          kTargetFramerate.hertz(), kNumFrames);

  EncoderSettings encoder_settings;
  encoder_settings.pacing_settings = pacing_settings;
  std::vector<Frame> frames =
      VideoCodecTester::RunEncodeTest(video_source, &encoder_factory,
                                      encoder_settings, encoding_settings)
          ->Slice(/*filter=*/{}, /*merge=*/false);
  ASSERT_THAT(frames, SizeIs(kNumFrames));
  EXPECT_NEAR((frames[1].encode_start - frames[0].encode_start).ms(),
              expected_delta_ms, 10);
  EXPECT_NEAR((frames[2].encode_start - frames[1].encode_start).ms(),
              expected_delta_ms, 10);
}

TEST_P(VideoCodecTesterTestPacing, PaceDecode) {
  auto [pacing_settings, expected_delta_ms] = GetParam();
  MockCodedVideoSource video_source(kNumFrames, kTargetFramerate);

  NiceMock<MockVideoDecoderFactory> decoder_factory;
  ON_CALL(decoder_factory, CreateVideoDecoder(_))
      .WillByDefault([](const SdpVideoFormat&) {
        return std::make_unique<NiceMock<MockVideoDecoder>>();
      });

  DecoderSettings decoder_settings;
  decoder_settings.pacing_settings = pacing_settings;
  std::vector<Frame> frames =
      VideoCodecTester::RunDecodeTest(&video_source, &decoder_factory,
                                      decoder_settings, SdpVideoFormat("VP8"))
          ->Slice(/*filter=*/{}, /*merge=*/false);
  ASSERT_THAT(frames, SizeIs(kNumFrames));
  EXPECT_NEAR((frames[1].decode_start - frames[0].decode_start).ms(),
              expected_delta_ms, 10);
  EXPECT_NEAR((frames[2].decode_start - frames[1].decode_start).ms(),
              expected_delta_ms, 10);
}

INSTANTIATE_TEST_SUITE_P(
    DISABLED_All,
    VideoCodecTesterTestPacing,
    Values(
        // No pacing.
        std::make_tuple(PacingSettings{.mode = PacingMode::kNoPacing},
                        /*expected_delta_ms=*/0),
        // Real-time pacing.
        std::make_tuple(PacingSettings{.mode = PacingMode::kRealTime},
                        /*expected_delta_ms=*/100),
        // Pace with specified constant rate.
        std::make_tuple(PacingSettings{.mode = PacingMode::kConstantRate,
                                       .constant_rate = Frequency::Hertz(20)},
                        /*expected_delta_ms=*/50)));
}  // namespace test
}  // namespace webrtc
