| /* |
| * 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 <algorithm> |
| #include <numeric> |
| |
| #include "rtc_base/checks.h" |
| #include "rtc_base/experiments/struct_parameters_parser.h" |
| |
| namespace webrtc { |
| namespace { |
| constexpr size_t kDefaultRecentWindowLength = 6; |
| constexpr size_t kDefaultPastWindowLength = 6; |
| constexpr float kDefaultAlpha = 0.06; |
| |
| struct DynamicDetectionConfig { |
| bool enabled = false; |
| // alpha is a percentage of the codec-specific max QP value that is used to |
| // determine the dynamic QP threshold: |
| // dynamic_qp_threshold = static_min_qp_threshold + alpha * max_QP |
| // Please note that although the static threshold is overridden, the dynamic |
| // threshold is calculated from static_min_qp_threshold reported by the |
| // encoder. |
| double alpha = kDefaultAlpha; |
| int recent_length = kDefaultRecentWindowLength; |
| int past_length = kDefaultPastWindowLength; |
| std::unique_ptr<StructParametersParser> Parser(); |
| }; |
| |
| std::unique_ptr<StructParametersParser> DynamicDetectionConfig::Parser() { |
| // The empty comments ensures that each pair is on a separate line. |
| return StructParametersParser::Create("enabled", &enabled, // |
| "alpha", &alpha, // |
| "recent_length", &recent_length, // |
| "past_length", &past_length); |
| } |
| |
| QualityConvergenceMonitor::Parameters GetParameters( |
| int static_qp_threshold, |
| VideoCodecType codec, |
| const FieldTrialsView& trials) { |
| QualityConvergenceMonitor::Parameters params; |
| params.static_qp_threshold = static_qp_threshold; |
| |
| DynamicDetectionConfig dynamic_config; |
| // Apply codec specific settings. |
| int max_qp = 0; |
| switch (codec) { |
| case kVideoCodecVP8: |
| dynamic_config.Parser()->Parse(trials.Lookup("WebRTC-QCM-Dynamic-VP8")); |
| max_qp = 127; |
| break; |
| case kVideoCodecVP9: |
| // Change to enabled by default for VP9. |
| dynamic_config.enabled = true; |
| dynamic_config.Parser()->Parse(trials.Lookup("WebRTC-QCM-Dynamic-VP9")); |
| max_qp = 255; |
| break; |
| case kVideoCodecAV1: |
| // Change to enabled by default for AV1. |
| dynamic_config.enabled = true; |
| dynamic_config.Parser()->Parse(trials.Lookup("WebRTC-QCM-Dynamic-AV1")); |
| max_qp = 255; |
| break; |
| case kVideoCodecGeneric: |
| case kVideoCodecH264: |
| case kVideoCodecH265: |
| break; |
| } |
| |
| if (dynamic_config.enabled) { |
| params.dynamic_detection_enabled = dynamic_config.enabled; |
| params.dynamic_qp_threshold = |
| static_qp_threshold + max_qp * dynamic_config.alpha; |
| params.recent_window_length = dynamic_config.recent_length; |
| params.past_window_length = dynamic_config.past_length; |
| } |
| return params; |
| } |
| } // namespace |
| |
| QualityConvergenceMonitor::QualityConvergenceMonitor(const Parameters& params) |
| : params_(params) { |
| RTC_CHECK( |
| !params_.dynamic_detection_enabled || |
| (params_.past_window_length > 0 && params_.recent_window_length > 0)); |
| } |
| |
| // Adds the sample to the algorithms detection window and runs the following |
| // convergence detection algorithm to determine if the time series of QP |
| // values indicates that the encoded video has reached "target quality". |
| // |
| // Definitions |
| // |
| // - Let x[n] be the pixel data of a video frame. |
| // - Let e[n] be the encoded representation of x[n]. |
| // - Let qp[n] be the corresponding QP value of the encoded video frame e[n]. |
| // - x[n] is a refresh frame if x[n] = x[n-1]. |
| // - qp_window is a list (or queue) of stored QP values, with size |
| // L <= past_window_length + recent_window_length. |
| // - qp_window can be partioned into: |
| // qp_past = qp_window[ 0:end-recent_window_length ] and |
| // qp_recent = qp_window[ -recent_window_length:end ]. |
| // - Let dynamic_qp_threshold be a maximum QP value for which convergence |
| // is accepted. |
| // |
| // Algorithm |
| // |
| // For each encoded video frame e[n], take the corresponding qp[n] and do the |
| // following: |
| // 0. Check Static Threshold: if qp[n] < static_qp_threshold, return true. |
| // 1. Check for Refresh Frame: If x[n] is not a refresh frame: |
| // - Clear Q. |
| // - Return false. |
| // 2. Check Previous Convergence: If x[n] is a refresh frame AND true was |
| // returned for x[n-1], return true. |
| // 3. Update QP History: Append qp[n] to qp_window. If qp_window's length |
| // exceeds past_window_length + recent_window_length, remove the first |
| // element. |
| // 4. Check for Sufficient Data: If L <= recent_window_length, return false. |
| // 5. Calculate Average QP: Calculate avg(qp_past) and avg(ap_recent). |
| // 6. Determine Convergence: If avg(qp_past) <= dynamic_qp_threshold AND |
| // avg(qp_past) <= avg(qp_recent), return true. Otherwise, return false. |
| // |
| void QualityConvergenceMonitor::AddSample(int qp, bool is_refresh_frame) { |
| // Invalid QP. |
| if (qp < 0) { |
| qp_window_.clear(); |
| at_target_quality_ = false; |
| return; |
| } |
| |
| // 0. Check static threshold. |
| if (qp <= params_.static_qp_threshold) { |
| at_target_quality_ = true; |
| return; |
| } |
| |
| // 1. Check for refresh frame and if dynamic detection is disabled. |
| if (!is_refresh_frame || !params_.dynamic_detection_enabled) { |
| qp_window_.clear(); |
| at_target_quality_ = false; |
| return; |
| } |
| |
| // 2. Check previous convergence. |
| RTC_CHECK(is_refresh_frame); |
| if (at_target_quality_) { |
| // No need to update state. |
| return; |
| } |
| |
| // 3. Update QP history. |
| qp_window_.push_back(qp); |
| if (qp_window_.size() > |
| params_.recent_window_length + params_.past_window_length) { |
| qp_window_.pop_front(); |
| } |
| |
| // 4. Check for sufficient data. |
| if (qp_window_.size() <= params_.recent_window_length) { |
| // No need to update state. |
| RTC_CHECK(at_target_quality_ == false); |
| return; |
| } |
| |
| // 5. Calculate average QP. |
| float qp_past_average = |
| std::accumulate(qp_window_.begin(), |
| qp_window_.end() - params_.recent_window_length, 0.0) / |
| (qp_window_.size() - params_.recent_window_length); |
| float qp_recent_average = |
| std::accumulate(qp_window_.end() - params_.recent_window_length, |
| qp_window_.end(), 0.0) / |
| params_.recent_window_length; |
| // 6. Determine convergence. |
| if (qp_past_average <= params_.dynamic_qp_threshold && |
| qp_past_average <= qp_recent_average) { |
| at_target_quality_ = true; |
| } |
| } |
| |
| bool QualityConvergenceMonitor::AtTargetQuality() const { |
| return at_target_quality_; |
| } |
| |
| // Static |
| std::unique_ptr<QualityConvergenceMonitor> QualityConvergenceMonitor::Create( |
| int static_qp_threshold, |
| VideoCodecType codec, |
| const FieldTrialsView& trials) { |
| Parameters params = GetParameters(static_qp_threshold, codec, trials); |
| return std::unique_ptr<QualityConvergenceMonitor>( |
| new QualityConvergenceMonitor(params)); |
| } |
| |
| } // namespace webrtc |