
/*
 *  Copyright (c) 2024 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 "video/quality_convergence_monitor.h"

#include <cstddef>
#include <memory>

#include "api/field_trials.h"
#include "api/video/video_codec_type.h"
#include "test/create_test_field_trials.h"
#include "test/gtest.h"

namespace webrtc {
namespace {
constexpr int kStaticQpThreshold = 13;

constexpr QualityConvergenceMonitor::Parameters kParametersOnlyStaticThreshold =
    {.static_qp_threshold = kStaticQpThreshold,
     .dynamic_detection_enabled = false};
constexpr QualityConvergenceMonitor::Parameters
    kParametersWithDynamicDetection = {
        .static_qp_threshold = kStaticQpThreshold,
        .dynamic_detection_enabled = true,
        .recent_window_length = 3,
        .past_window_length = 9,
        .dynamic_qp_threshold = 24};

// Test the basics of the algorithm.

TEST(QualityConvergenceMonitorAlgorithm, StaticThreshold) {
  QualityConvergenceMonitor::Parameters p = kParametersOnlyStaticThreshold;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
  ASSERT_TRUE(monitor);

  for (bool is_refresh_frame : {false, true}) {
    // Ramp down from 100. Not at target quality until qp <= static threshold.
    for (int qp = 100; qp > p.static_qp_threshold; --qp) {
      monitor->AddSample(qp, is_refresh_frame);
      EXPECT_FALSE(monitor->AtTargetQuality());
    }

    monitor->AddSample(p.static_qp_threshold, is_refresh_frame);
    EXPECT_TRUE(monitor->AtTargetQuality());

    // 100 samples just above the threshold is not at target quality.
    for (int i = 0; i < 100; ++i) {
      monitor->AddSample(p.static_qp_threshold + 1, is_refresh_frame);
      EXPECT_FALSE(monitor->AtTargetQuality());
    }
  }
}

TEST(QualityConvergenceMonitorAlgorithm,
     StaticThresholdWithDynamicDetectionEnabled) {
  QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
  ASSERT_TRUE(monitor);

  for (bool is_refresh_frame : {false, true}) {
    // Clear buffer.
    monitor->AddSample(-1, /*is_refresh_frame=*/false);
    EXPECT_FALSE(monitor->AtTargetQuality());

    // Ramp down from 100. Not at target quality until qp <= static threshold.
    for (int qp = 100; qp > p.static_qp_threshold; --qp) {
      monitor->AddSample(qp, is_refresh_frame);
      EXPECT_FALSE(monitor->AtTargetQuality());
    }

    // A single frame at the static QP threshold is considered to be at target
    // quality regardless of if it's a refresh frame or not.
    monitor->AddSample(p.static_qp_threshold, is_refresh_frame);
    EXPECT_TRUE(monitor->AtTargetQuality());
  }

  // 100 samples just above the threshold is not at target quality if it's not a
  // refresh frame.
  for (int i = 0; i < 100; ++i) {
    monitor->AddSample(p.static_qp_threshold + 1, /*is_refresh_frame=*/false);
    EXPECT_FALSE(monitor->AtTargetQuality());
  }
}

TEST(QualityConvergenceMonitorAlgorithm, ConvergenceAtDynamicThreshold) {
  QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
  ASSERT_TRUE(monitor);

  // `recent_window_length` + `past_window_length` refresh frames at the dynamic
  // threshold must mean we're at target quality.
  for (size_t i = 0; i < p.recent_window_length + p.past_window_length; ++i) {
    monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
  }
  EXPECT_TRUE(monitor->AtTargetQuality());
}

TEST(QualityConvergenceMonitorAlgorithm, NoConvergenceAboveDynamicThreshold) {
  QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
  ASSERT_TRUE(monitor);

  // 100 samples just above the threshold must imply that we're not at target
  // quality.
  for (int i = 0; i < 100; ++i) {
    monitor->AddSample(p.dynamic_qp_threshold + 1, /*is_refresh_frame=*/true);
    EXPECT_FALSE(monitor->AtTargetQuality());
  }
}

TEST(QualityConvergenceMonitorAlgorithm,
     MaintainAtTargetQualityForRefreshFrames) {
  QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
  ASSERT_TRUE(monitor);

  // `recent_window_length` + `past_window_length` refresh frames at the dynamic
  // threshold must mean we're at target quality.
  for (size_t i = 0; i < p.recent_window_length + p.past_window_length; ++i) {
    monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
  }
  EXPECT_TRUE(monitor->AtTargetQuality());

  int qp = p.dynamic_qp_threshold;
  for (int i = 0; i < 100; ++i) {
    monitor->AddSample(qp++, /*is_refresh_frame=*/true);
    EXPECT_TRUE(monitor->AtTargetQuality());
  }

  // Reset state for first frame that is not a refresh frame.
  monitor->AddSample(qp, /*is_refresh_frame=*/false);
  EXPECT_FALSE(monitor->AtTargetQuality());
}

// Test corner cases.

TEST(QualityConvergenceMonitorAlgorithm, SufficientData) {
  QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
  ASSERT_TRUE(monitor);

  // Less than `recent_window_length + 1` refresh frame QP values at the dynamic
  // threshold is not sufficient.
  for (size_t i = 0; i < p.recent_window_length; ++i) {
    monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
    // Not sufficient data
    EXPECT_FALSE(monitor->AtTargetQuality());
  }

  // However, `recent_window_length + 1` QP values are sufficient.
  monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
  EXPECT_TRUE(monitor->AtTargetQuality());
}

TEST(QualityConvergenceMonitorAlgorithm,
     AtTargetIfQpPastLessThanOrEqualToQpRecent) {
  QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
  p.past_window_length = 3;
  p.recent_window_length = 3;
  auto monitor = std::make_unique<QualityConvergenceMonitor>(p);

  // Sequence for which QP_past > QP_recent.
  for (int qp : {23, 21, 21, 21, 21, 22}) {
    monitor->AddSample(qp, /*is_refresh_frame=*/true);
    EXPECT_FALSE(monitor->AtTargetQuality());
  }

  // Reset QP window.
  monitor->AddSample(-1, /*is_refresh_frame=*/false);
  EXPECT_FALSE(monitor->AtTargetQuality());

  // Sequence for which one additional sample of 22 will make QP_past ==
  // QP_recent.
  for (int qp : {22, 21, 21, 21, 21}) {
    monitor->AddSample(qp, /*is_refresh_frame=*/true);
    EXPECT_FALSE(monitor->AtTargetQuality());
  }
  monitor->AddSample(22, /*is_refresh_frame=*/true);
  EXPECT_TRUE(monitor->AtTargetQuality());

  // Reset QP window.
  monitor->AddSample(-1, /*is_refresh_frame=*/false);
  EXPECT_FALSE(monitor->AtTargetQuality());

  // Sequence for which one additional sample of 23 will make QP_past <
  // QP_recent.
  for (int qp : {22, 21, 21, 21, 21}) {
    monitor->AddSample(qp, /*is_refresh_frame=*/true);
    EXPECT_FALSE(monitor->AtTargetQuality());
  }
  monitor->AddSample(23, /*is_refresh_frame=*/true);
  EXPECT_TRUE(monitor->AtTargetQuality());
}

// Test default values and that they can be overridden with field trials.

TEST(QualityConvergenceMonitorSetup, DefaultParameters) {
  FieldTrials field_trials = CreateTestFieldTrials();
  auto monitor = QualityConvergenceMonitor::Create(
      kStaticQpThreshold, kVideoCodecVP8, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters vp8_parameters =
      monitor->GetParametersForTesting();
  EXPECT_EQ(vp8_parameters.static_qp_threshold, kStaticQpThreshold);
  EXPECT_TRUE(vp8_parameters.dynamic_detection_enabled);
  EXPECT_EQ(vp8_parameters.dynamic_qp_threshold, 20);  // 13 + 7.
  EXPECT_EQ(vp8_parameters.recent_window_length, 6u);
  EXPECT_EQ(vp8_parameters.past_window_length, 6u);

  monitor = QualityConvergenceMonitor::Create(kStaticQpThreshold,
                                              kVideoCodecVP9, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters vp9_parameters =
      monitor->GetParametersForTesting();
  EXPECT_EQ(vp9_parameters.static_qp_threshold, kStaticQpThreshold);
  EXPECT_TRUE(vp9_parameters.dynamic_detection_enabled);
  EXPECT_EQ(vp9_parameters.dynamic_qp_threshold, 28);  // 13 + 15.
  EXPECT_EQ(vp9_parameters.recent_window_length, 6u);
  EXPECT_EQ(vp9_parameters.past_window_length, 6u);

  monitor = QualityConvergenceMonitor::Create(kStaticQpThreshold,
                                              kVideoCodecAV1, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters av1_parameters =
      monitor->GetParametersForTesting();
  EXPECT_EQ(av1_parameters.static_qp_threshold, kStaticQpThreshold);
  EXPECT_TRUE(av1_parameters.dynamic_detection_enabled);
  EXPECT_EQ(av1_parameters.dynamic_qp_threshold, 28);  // 13 + 15.
  EXPECT_EQ(av1_parameters.recent_window_length, 6u);
  EXPECT_EQ(av1_parameters.past_window_length, 6u);
}

TEST(QualityConvergenceMonitorSetup, OverrideVp8Parameters) {
  FieldTrials field_trials = CreateTestFieldTrials(
      "WebRTC-QCM-Dynamic-VP8/"
      "enabled:1,alpha:0.08,recent_length:6,past_length:4/");

  auto monitor = QualityConvergenceMonitor::Create(
      kStaticQpThreshold, kVideoCodecVP8, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
  EXPECT_EQ(p.static_qp_threshold, kStaticQpThreshold);
  EXPECT_TRUE(p.dynamic_detection_enabled);
  EXPECT_EQ(p.dynamic_qp_threshold, 23);  // 13 + 10.
  EXPECT_EQ(p.recent_window_length, 6u);
  EXPECT_EQ(p.past_window_length, 4u);
}

TEST(QualityConvergenceMonitorSetup, OverrideVp9Parameters) {
  FieldTrials field_trials = CreateTestFieldTrials(
      "WebRTC-QCM-Dynamic-VP9/"
      "enabled:1,alpha:0.08,recent_length:6,past_length:4/");

  auto monitor = QualityConvergenceMonitor::Create(
      kStaticQpThreshold, kVideoCodecVP9, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
  EXPECT_EQ(p.static_qp_threshold, kStaticQpThreshold);
  EXPECT_TRUE(p.dynamic_detection_enabled);
  EXPECT_EQ(p.dynamic_qp_threshold, 33);  // 13 + 20.
  EXPECT_EQ(p.recent_window_length, 6u);
  EXPECT_EQ(p.past_window_length, 4u);
}

TEST(QualityConvergenceMonitorSetup, OverrideAv1Parameters) {
  FieldTrials field_trials = CreateTestFieldTrials(
      "WebRTC-QCM-Dynamic-AV1/"
      "enabled:1,alpha:0.10,recent_length:8,past_length:8/");

  auto monitor = QualityConvergenceMonitor::Create(
      kStaticQpThreshold, kVideoCodecAV1, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
  EXPECT_EQ(p.static_qp_threshold, kStaticQpThreshold);
  EXPECT_TRUE(p.dynamic_detection_enabled);
  EXPECT_EQ(p.dynamic_qp_threshold, 38);  // 13 + 25.
  EXPECT_EQ(p.recent_window_length, 8u);
  EXPECT_EQ(p.past_window_length, 8u);
}

TEST(QualityConvergenceMonitorSetup, DisableVp9Dynamic) {
  FieldTrials field_trials =
      CreateTestFieldTrials("WebRTC-QCM-Dynamic-VP9/enabled:0/");

  auto monitor = QualityConvergenceMonitor::Create(
      kStaticQpThreshold, kVideoCodecVP9, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
  EXPECT_FALSE(p.dynamic_detection_enabled);
}

TEST(QualityConvergenceMonitorSetup, DisableAv1Dynamic) {
  FieldTrials field_trials =
      CreateTestFieldTrials("WebRTC-QCM-Dynamic-AV1/enabled:0/");

  auto monitor = QualityConvergenceMonitor::Create(
      kStaticQpThreshold, kVideoCodecAV1, field_trials);
  ASSERT_TRUE(monitor);
  QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
  EXPECT_FALSE(p.dynamic_detection_enabled);
}

}  // namespace
}  // namespace webrtc
