-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathquality_convergence_monitor.cc
206 lines (186 loc) · 7.06 KB
/
quality_convergence_monitor.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
* 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