|  | 
 | /* | 
 |  *  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 <vector> | 
 |  | 
 | #include "test/gtest.h" | 
 | #include "test/scoped_key_value_config.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) { | 
 |   test::ScopedKeyValueConfig field_trials; | 
 |   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) { | 
 |   test::ScopedKeyValueConfig field_trials( | 
 |       "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) { | 
 |   test::ScopedKeyValueConfig field_trials( | 
 |       "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) { | 
 |   test::ScopedKeyValueConfig field_trials( | 
 |       "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) { | 
 |   test::ScopedKeyValueConfig field_trials("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) { | 
 |   test::ScopedKeyValueConfig field_trials("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 |