Bug: chromium:328598314 Change-Id: I132b4c30f132ace2bbef6359edd994c1ad75c9ad Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/362620 Reviewed-by: Johannes Kron <kron@webrtc.org> Commit-Queue: Sergey Silkin <ssilkin@webrtc.org> Cr-Commit-Position: refs/heads/main@{#43035}
207 lines
7.1 KiB
C++
207 lines
7.1 KiB
C++
/*
|
|
* 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
|