blob: 9da42bb33c6e51652b972f4353d3818654cc737d [file] [log] [blame]
niklase@google.com470e71d2011-07-07 08:21:25 +00001/*
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +00002 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
niklase@google.com470e71d2011-07-07 08:21:25 +00003 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Henrik Kjellander2557b862015-11-18 22:00:21 +010011#include "webrtc/modules/video_coding/qm_select.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000012
13#include <math.h>
14
Henrik Kjellanderff761fb2015-11-04 08:31:52 +010015#include "webrtc/modules/include/module_common_types.h"
Henrik Kjellander2557b862015-11-18 22:00:21 +010016#include "webrtc/modules/video_coding/include/video_coding_defines.h"
17#include "webrtc/modules/video_coding/internal_defines.h"
18#include "webrtc/modules/video_coding/qm_select_data.h"
Henrik Kjellander98f53512015-10-28 18:17:40 +010019#include "webrtc/system_wrappers/include/trace.h"
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000020
niklase@google.com470e71d2011-07-07 08:21:25 +000021namespace webrtc {
22
marpan@google.com86548c62011-07-12 17:12:57 +000023// QM-METHOD class
24
25VCMQmMethod::VCMQmMethod()
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000026 : content_metrics_(NULL),
27 width_(0),
28 height_(0),
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +000029 user_frame_rate_(0.0f),
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000030 native_width_(0),
31 native_height_(0),
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +000032 native_frame_rate_(0.0f),
marpan@webrtc.orgcb8050c2012-09-11 20:34:15 +000033 image_type_(kVGA),
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +000034 framerate_level_(kFrameRateHigh),
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000035 init_(false) {
marpan@webrtc.org6584e582012-02-06 19:02:54 +000036 ResetQM();
niklase@google.com470e71d2011-07-07 08:21:25 +000037}
38
philipel9d3ab612015-12-21 04:12:39 -080039VCMQmMethod::~VCMQmMethod() {}
niklase@google.com470e71d2011-07-07 08:21:25 +000040
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000041void VCMQmMethod::ResetQM() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000042 aspect_ratio_ = 1.0f;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000043 motion_.Reset();
44 spatial_.Reset();
45 content_class_ = 0;
niklase@google.com470e71d2011-07-07 08:21:25 +000046}
47
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000048uint8_t VCMQmMethod::ComputeContentClass() {
49 ComputeMotionNFD();
50 ComputeSpatial();
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000051 return content_class_ = 3 * motion_.level + spatial_.level;
marpan@google.com86548c62011-07-12 17:12:57 +000052}
53
philipel9d3ab612015-12-21 04:12:39 -080054void VCMQmMethod::UpdateContent(const VideoContentMetrics* contentMetrics) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000055 content_metrics_ = contentMetrics;
marpan@google.com86548c62011-07-12 17:12:57 +000056}
57
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000058void VCMQmMethod::ComputeMotionNFD() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000059 if (content_metrics_) {
60 motion_.value = content_metrics_->motion_magnitude;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000061 }
62 // Determine motion level.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000063 if (motion_.value < kLowMotionNfd) {
64 motion_.level = kLow;
65 } else if (motion_.value > kHighMotionNfd) {
philipel9d3ab612015-12-21 04:12:39 -080066 motion_.level = kHigh;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000067 } else {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000068 motion_.level = kDefault;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000069 }
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +000070}
71
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000072void VCMQmMethod::ComputeSpatial() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000073 float spatial_err = 0.0;
74 float spatial_err_h = 0.0;
75 float spatial_err_v = 0.0;
76 if (content_metrics_) {
philipel9d3ab612015-12-21 04:12:39 -080077 spatial_err = content_metrics_->spatial_pred_err;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000078 spatial_err_h = content_metrics_->spatial_pred_err_h;
79 spatial_err_v = content_metrics_->spatial_pred_err_v;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000080 }
81 // Spatial measure: take average of 3 prediction errors.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000082 spatial_.value = (spatial_err + spatial_err_h + spatial_err_v) / 3.0f;
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +000083
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000084 // Reduce thresholds for large scenes/higher pixel correlation.
85 float scale2 = image_type_ > kVGA ? kScaleTexture : 1.0;
marpan@google.com86548c62011-07-12 17:12:57 +000086
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000087 if (spatial_.value > scale2 * kHighTexture) {
88 spatial_.level = kHigh;
89 } else if (spatial_.value < scale2 * kLowTexture) {
90 spatial_.level = kLow;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000091 } else {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000092 spatial_.level = kDefault;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +000093 }
marpan@google.com86548c62011-07-12 17:12:57 +000094}
95
philipel9d3ab612015-12-21 04:12:39 -080096ImageType VCMQmMethod::GetImageType(uint16_t width, uint16_t height) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +000097 // Get the image type for the encoder frame size.
98 uint32_t image_size = width * height;
99 if (image_size == kSizeOfImageType[kQCIF]) {
100 return kQCIF;
101 } else if (image_size == kSizeOfImageType[kHCIF]) {
102 return kHCIF;
103 } else if (image_size == kSizeOfImageType[kQVGA]) {
104 return kQVGA;
105 } else if (image_size == kSizeOfImageType[kCIF]) {
106 return kCIF;
107 } else if (image_size == kSizeOfImageType[kHVGA]) {
108 return kHVGA;
109 } else if (image_size == kSizeOfImageType[kVGA]) {
110 return kVGA;
111 } else if (image_size == kSizeOfImageType[kQFULLHD]) {
112 return kQFULLHD;
113 } else if (image_size == kSizeOfImageType[kWHD]) {
114 return kWHD;
115 } else if (image_size == kSizeOfImageType[kFULLHD]) {
116 return kFULLHD;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000117 } else {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000118 // No exact match, find closet one.
119 return FindClosestImageType(width, height);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000120 }
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000121}
122
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000123ImageType VCMQmMethod::FindClosestImageType(uint16_t width, uint16_t height) {
124 float size = static_cast<float>(width * height);
125 float min = size;
126 int isel = 0;
127 for (int i = 0; i < kNumImageTypes; ++i) {
128 float dist = fabs(size - kSizeOfImageType[i]);
129 if (dist < min) {
130 min = dist;
131 isel = i;
132 }
133 }
134 return static_cast<ImageType>(isel);
135}
136
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000137FrameRateLevelClass VCMQmMethod::FrameRateLevel(float avg_framerate) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000138 if (avg_framerate <= kLowFrameRate) {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000139 return kFrameRateLow;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000140 } else if (avg_framerate <= kMiddleFrameRate) {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000141 return kFrameRateMiddle1;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000142 } else if (avg_framerate <= kHighFrameRate) {
philipel9d3ab612015-12-21 04:12:39 -0800143 return kFrameRateMiddle2;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000144 } else {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000145 return kFrameRateHigh;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000146 }
marpan@google.com86548c62011-07-12 17:12:57 +0000147}
148
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000149// RESOLUTION CLASS
marpan@google.com86548c62011-07-12 17:12:57 +0000150
philipel9d3ab612015-12-21 04:12:39 -0800151VCMQmResolution::VCMQmResolution() : qm_(new VCMResolutionScale()) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000152 Reset();
marpan@google.com86548c62011-07-12 17:12:57 +0000153}
154
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000155VCMQmResolution::~VCMQmResolution() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000156 delete qm_;
marpan@google.com86548c62011-07-12 17:12:57 +0000157}
158
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000159void VCMQmResolution::ResetRates() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000160 sum_target_rate_ = 0.0f;
161 sum_incoming_framerate_ = 0.0f;
162 sum_rate_MM_ = 0.0f;
163 sum_rate_MM_sgn_ = 0.0f;
164 sum_packet_loss_ = 0.0f;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000165 buffer_level_ = kInitBufferLevel * target_bitrate_;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000166 frame_cnt_ = 0;
167 frame_cnt_delta_ = 0;
168 low_buffer_cnt_ = 0;
169 update_rate_cnt_ = 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000170}
171
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000172void VCMQmResolution::ResetDownSamplingState() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000173 state_dec_factor_spatial_ = 1.0;
philipel9d3ab612015-12-21 04:12:39 -0800174 state_dec_factor_temporal_ = 1.0;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000175 for (int i = 0; i < kDownActionHistorySize; i++) {
176 down_action_history_[i].spatial = kNoChangeSpatial;
177 down_action_history_[i].temporal = kNoChangeTemporal;
178 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000179}
180
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000181void VCMQmResolution::Reset() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000182 target_bitrate_ = 0.0f;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000183 incoming_framerate_ = 0.0f;
184 buffer_level_ = 0.0f;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000185 per_frame_bandwidth_ = 0.0f;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000186 avg_target_rate_ = 0.0f;
187 avg_incoming_framerate_ = 0.0f;
188 avg_ratio_buffer_low_ = 0.0f;
189 avg_rate_mismatch_ = 0.0f;
190 avg_rate_mismatch_sgn_ = 0.0f;
191 avg_packet_loss_ = 0.0f;
192 encoder_state_ = kStableEncoding;
193 num_layers_ = 1;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000194 ResetRates();
195 ResetDownSamplingState();
196 ResetQM();
197}
niklase@google.com470e71d2011-07-07 08:21:25 +0000198
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000199EncoderState VCMQmResolution::GetEncoderState() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000200 return encoder_state_;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000201}
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000202
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000203// Initialize state after re-initializing the encoder,
204// i.e., after SetEncodingData() in mediaOpt.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000205int VCMQmResolution::Initialize(float bitrate,
206 float user_framerate,
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000207 uint16_t width,
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000208 uint16_t height,
209 int num_layers) {
210 if (user_framerate == 0.0f || width == 0 || height == 0) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000211 return VCM_PARAMETER_ERROR;
212 }
213 Reset();
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000214 target_bitrate_ = bitrate;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000215 incoming_framerate_ = user_framerate;
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000216 UpdateCodecParameters(user_framerate, width, height);
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000217 native_width_ = width;
218 native_height_ = height;
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000219 native_frame_rate_ = user_framerate;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000220 num_layers_ = num_layers;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000221 // Initial buffer level.
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000222 buffer_level_ = kInitBufferLevel * target_bitrate_;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000223 // Per-frame bandwidth.
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000224 per_frame_bandwidth_ = target_bitrate_ / user_framerate;
philipel9d3ab612015-12-21 04:12:39 -0800225 init_ = true;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000226 return VCM_OK;
227}
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000228
philipel9d3ab612015-12-21 04:12:39 -0800229void VCMQmResolution::UpdateCodecParameters(float frame_rate,
230 uint16_t width,
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000231 uint16_t height) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000232 width_ = width;
233 height_ = height;
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000234 // |user_frame_rate| is the target frame rate for VPM frame dropper.
235 user_frame_rate_ = frame_rate;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000236 image_type_ = GetImageType(width, height);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000237}
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000238
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000239// Update rate data after every encoded frame.
pbos@webrtc.org273a4142014-12-01 15:23:21 +0000240void VCMQmResolution::UpdateEncodedSize(size_t encoded_size) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000241 frame_cnt_++;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000242 // Convert to Kbps.
pkasting@chromium.org4591fbd2014-11-20 22:28:14 +0000243 float encoded_size_kbits = 8.0f * static_cast<float>(encoded_size) / 1000.0f;
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000244
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000245 // Update the buffer level:
246 // Note this is not the actual encoder buffer level.
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000247 // |buffer_level_| is reset to an initial value after SelectResolution is
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000248 // called, and does not account for frame dropping by encoder or VCM.
249 buffer_level_ += per_frame_bandwidth_ - encoded_size_kbits;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000250
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000251 // Counter for occurrences of low buffer level:
252 // low/negative values means encoder is likely dropping frames.
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000253 if (buffer_level_ <= kPercBufferThr * kInitBufferLevel * target_bitrate_) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000254 low_buffer_cnt_++;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000255 }
256}
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000257
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000258// Update various quantities after SetTargetRates in MediaOpt.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000259void VCMQmResolution::UpdateRates(float target_bitrate,
260 float encoder_sent_rate,
261 float incoming_framerate,
262 uint8_t packet_loss) {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000263 // Sum the target bitrate: this is the encoder rate from previous update
264 // (~1sec), i.e, before the update for next ~1sec.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000265 sum_target_rate_ += target_bitrate_;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000266 update_rate_cnt_++;
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000267
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000268 // Sum the received (from RTCP reports) packet loss rates.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000269 sum_packet_loss_ += static_cast<float>(packet_loss / 255.0);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000270
271 // Sum the sequence rate mismatch:
272 // Mismatch here is based on the difference between the target rate
273 // used (in previous ~1sec) and the average actual encoding rate measured
274 // at previous ~1sec.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000275 float diff = target_bitrate_ - encoder_sent_rate;
276 if (target_bitrate_ > 0.0)
277 sum_rate_MM_ += fabs(diff) / target_bitrate_;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000278 int sgnDiff = diff > 0 ? 1 : (diff < 0 ? -1 : 0);
279 // To check for consistent under(+)/over_shooting(-) of target rate.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000280 sum_rate_MM_sgn_ += sgnDiff;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000281
282 // Update with the current new target and frame rate:
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000283 // these values are ones the encoder will use for the current/next ~1sec.
philipel9d3ab612015-12-21 04:12:39 -0800284 target_bitrate_ = target_bitrate;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000285 incoming_framerate_ = incoming_framerate;
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000286 sum_incoming_framerate_ += incoming_framerate_;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000287 // Update the per_frame_bandwidth:
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000288 // this is the per_frame_bw for the current/next ~1sec.
philipel9d3ab612015-12-21 04:12:39 -0800289 per_frame_bandwidth_ = 0.0f;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000290 if (incoming_framerate_ > 0.0f) {
291 per_frame_bandwidth_ = target_bitrate_ / incoming_framerate_;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000292 }
293}
294
295// Select the resolution factors: frame size and frame rate change (qm scales).
296// Selection is for going down in resolution, or for going back up
297// (if a previous down-sampling action was taken).
298
299// In the current version the following constraints are imposed:
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000300// 1) We only allow for one action, either down or up, at a given time.
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000301// 2) The possible down-sampling actions are: spatial by 1/2x1/2, 3/4x3/4;
302// temporal/frame rate reduction by 1/2 and 2/3.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000303// 3) The action for going back up is the reverse of last (spatial or temporal)
304// down-sampling action. The list of down-sampling actions from the
305// Initialize() state are kept in |down_action_history_|.
306// 4) The total amount of down-sampling (spatial and/or temporal) from the
307// Initialize() state (native resolution) is limited by various factors.
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000308int VCMQmResolution::SelectResolution(VCMResolutionScale** qm) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000309 if (!init_) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000310 return VCM_UNINITIALIZED;
311 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000312 if (content_metrics_ == NULL) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000313 Reset();
philipel9d3ab612015-12-21 04:12:39 -0800314 *qm = qm_;
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000315 return VCM_OK;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000316 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000317
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000318 // Check conditions on down-sampling state.
319 assert(state_dec_factor_spatial_ >= 1.0f);
320 assert(state_dec_factor_temporal_ >= 1.0f);
321 assert(state_dec_factor_spatial_ <= kMaxSpatialDown);
322 assert(state_dec_factor_temporal_ <= kMaxTempDown);
323 assert(state_dec_factor_temporal_ * state_dec_factor_spatial_ <=
324 kMaxTotalDown);
325
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000326 // Compute content class for selection.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000327 content_class_ = ComputeContentClass();
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000328 // Compute various rate quantities for selection.
329 ComputeRatesForSelection();
niklase@google.com470e71d2011-07-07 08:21:25 +0000330
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000331 // Get the encoder state.
332 ComputeEncoderState();
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000333
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000334 // Default settings: no action.
335 SetDefaultAction();
336 *qm = qm_;
337
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000338 // Check for going back up in resolution, if we have had some down-sampling
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000339 // relative to native state in Initialize().
340 if (down_action_history_[0].spatial != kNoChangeSpatial ||
341 down_action_history_[0].temporal != kNoChangeTemporal) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000342 if (GoingUpResolution()) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000343 *qm = qm_;
niklase@google.com470e71d2011-07-07 08:21:25 +0000344 return VCM_OK;
345 }
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000346 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000347
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000348 // Check for going down in resolution.
349 if (GoingDownResolution()) {
350 *qm = qm_;
351 return VCM_OK;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000352 }
353 return VCM_OK;
niklase@google.com470e71d2011-07-07 08:21:25 +0000354}
355
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000356void VCMQmResolution::SetDefaultAction() {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000357 qm_->codec_width = width_;
358 qm_->codec_height = height_;
359 qm_->frame_rate = user_frame_rate_;
360 qm_->change_resolution_spatial = false;
361 qm_->change_resolution_temporal = false;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000362 qm_->spatial_width_fact = 1.0f;
363 qm_->spatial_height_fact = 1.0f;
364 qm_->temporal_fact = 1.0f;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000365 action_.spatial = kNoChangeSpatial;
366 action_.temporal = kNoChangeTemporal;
367}
368
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000369void VCMQmResolution::ComputeRatesForSelection() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000370 avg_target_rate_ = 0.0f;
371 avg_incoming_framerate_ = 0.0f;
372 avg_ratio_buffer_low_ = 0.0f;
373 avg_rate_mismatch_ = 0.0f;
374 avg_rate_mismatch_sgn_ = 0.0f;
375 avg_packet_loss_ = 0.0f;
376 if (frame_cnt_ > 0) {
philipel9d3ab612015-12-21 04:12:39 -0800377 avg_ratio_buffer_low_ =
378 static_cast<float>(low_buffer_cnt_) / static_cast<float>(frame_cnt_);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000379 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000380 if (update_rate_cnt_ > 0) {
philipel9d3ab612015-12-21 04:12:39 -0800381 avg_rate_mismatch_ =
382 static_cast<float>(sum_rate_MM_) / static_cast<float>(update_rate_cnt_);
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000383 avg_rate_mismatch_sgn_ = static_cast<float>(sum_rate_MM_sgn_) /
philipel9d3ab612015-12-21 04:12:39 -0800384 static_cast<float>(update_rate_cnt_);
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000385 avg_target_rate_ = static_cast<float>(sum_target_rate_) /
philipel9d3ab612015-12-21 04:12:39 -0800386 static_cast<float>(update_rate_cnt_);
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000387 avg_incoming_framerate_ = static_cast<float>(sum_incoming_framerate_) /
philipel9d3ab612015-12-21 04:12:39 -0800388 static_cast<float>(update_rate_cnt_);
389 avg_packet_loss_ = static_cast<float>(sum_packet_loss_) /
390 static_cast<float>(update_rate_cnt_);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000391 }
392 // For selection we may want to weight some quantities more heavily
393 // with the current (i.e., next ~1sec) rate values.
philipel9d3ab612015-12-21 04:12:39 -0800394 avg_target_rate_ =
395 kWeightRate * avg_target_rate_ + (1.0 - kWeightRate) * target_bitrate_;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000396 avg_incoming_framerate_ = kWeightRate * avg_incoming_framerate_ +
philipel9d3ab612015-12-21 04:12:39 -0800397 (1.0 - kWeightRate) * incoming_framerate_;
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000398 // Use base layer frame rate for temporal layers: this will favor spatial.
marpan@webrtc.org3fe32522012-03-20 22:13:24 +0000399 assert(num_layers_ > 0);
philipel9d3ab612015-12-21 04:12:39 -0800400 framerate_level_ = FrameRateLevel(avg_incoming_framerate_ /
401 static_cast<float>(1 << (num_layers_ - 1)));
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000402}
niklase@google.com470e71d2011-07-07 08:21:25 +0000403
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000404void VCMQmResolution::ComputeEncoderState() {
405 // Default.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000406 encoder_state_ = kStableEncoding;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000407
408 // Assign stressed state if:
409 // 1) occurrences of low buffer levels is high, or
410 // 2) rate mis-match is high, and consistent over-shooting by encoder.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000411 if ((avg_ratio_buffer_low_ > kMaxBufferLow) ||
412 ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
philipel9d3ab612015-12-21 04:12:39 -0800413 (avg_rate_mismatch_sgn_ < -kRateOverShoot))) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000414 encoder_state_ = kStressedEncoding;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000415 }
416 // Assign easy state if:
417 // 1) rate mis-match is high, and
418 // 2) consistent under-shooting by encoder.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000419 if ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
420 (avg_rate_mismatch_sgn_ > kRateUnderShoot)) {
421 encoder_state_ = kEasyEncoding;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000422 }
423}
424
425bool VCMQmResolution::GoingUpResolution() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000426 // For going up, we check for undoing the previous down-sampling action.
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000427
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000428 float fac_width = kFactorWidthSpatial[down_action_history_[0].spatial];
429 float fac_height = kFactorHeightSpatial[down_action_history_[0].spatial];
430 float fac_temp = kFactorTemporal[down_action_history_[0].temporal];
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000431 // For going up spatially, we allow for going up by 3/4x3/4 at each stage.
432 // So if the last spatial action was 1/2x1/2 it would be undone in 2 stages.
433 // Modify the fac_width/height for this case.
434 if (down_action_history_[0].spatial == kOneQuarterSpatialUniform) {
435 fac_width = kFactorWidthSpatial[kOneQuarterSpatialUniform] /
philipel9d3ab612015-12-21 04:12:39 -0800436 kFactorWidthSpatial[kOneHalfSpatialUniform];
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000437 fac_height = kFactorHeightSpatial[kOneQuarterSpatialUniform] /
philipel9d3ab612015-12-21 04:12:39 -0800438 kFactorHeightSpatial[kOneHalfSpatialUniform];
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000439 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000440
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000441 // Check if we should go up both spatially and temporally.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000442 if (down_action_history_[0].spatial != kNoChangeSpatial &&
443 down_action_history_[0].temporal != kNoChangeTemporal) {
444 if (ConditionForGoingUp(fac_width, fac_height, fac_temp,
445 kTransRateScaleUpSpatialTemp)) {
446 action_.spatial = down_action_history_[0].spatial;
447 action_.temporal = down_action_history_[0].temporal;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000448 UpdateDownsamplingState(kUpResolution);
449 return true;
450 }
451 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000452 // Check if we should go up either spatially or temporally.
453 bool selected_up_spatial = false;
454 bool selected_up_temporal = false;
455 if (down_action_history_[0].spatial != kNoChangeSpatial) {
456 selected_up_spatial = ConditionForGoingUp(fac_width, fac_height, 1.0f,
457 kTransRateScaleUpSpatial);
458 }
459 if (down_action_history_[0].temporal != kNoChangeTemporal) {
philipel9d3ab612015-12-21 04:12:39 -0800460 selected_up_temporal =
461 ConditionForGoingUp(1.0f, 1.0f, fac_temp, kTransRateScaleUpTemp);
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000462 }
463 if (selected_up_spatial && !selected_up_temporal) {
464 action_.spatial = down_action_history_[0].spatial;
465 action_.temporal = kNoChangeTemporal;
466 UpdateDownsamplingState(kUpResolution);
467 return true;
468 } else if (!selected_up_spatial && selected_up_temporal) {
469 action_.spatial = kNoChangeSpatial;
470 action_.temporal = down_action_history_[0].temporal;
471 UpdateDownsamplingState(kUpResolution);
472 return true;
473 } else if (selected_up_spatial && selected_up_temporal) {
474 PickSpatialOrTemporal();
475 UpdateDownsamplingState(kUpResolution);
476 return true;
477 }
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000478 return false;
479}
480
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000481bool VCMQmResolution::ConditionForGoingUp(float fac_width,
482 float fac_height,
483 float fac_temp,
484 float scale_fac) {
philipel9d3ab612015-12-21 04:12:39 -0800485 float estimated_transition_rate_up =
486 GetTransitionRate(fac_width, fac_height, fac_temp, scale_fac);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000487 // Go back up if:
488 // 1) target rate is above threshold and current encoder state is stable, or
489 // 2) encoder state is easy (encoder is significantly under-shooting target).
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000490 if (((avg_target_rate_ > estimated_transition_rate_up) &&
philipel9d3ab612015-12-21 04:12:39 -0800491 (encoder_state_ == kStableEncoding)) ||
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000492 (encoder_state_ == kEasyEncoding)) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000493 return true;
494 } else {
495 return false;
496 }
497}
498
499bool VCMQmResolution::GoingDownResolution() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000500 float estimated_transition_rate_down =
501 GetTransitionRate(1.0f, 1.0f, 1.0f, 1.0f);
502 float max_rate = kFrameRateFac[framerate_level_] * kMaxRateQm[image_type_];
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000503 // Resolution reduction if:
504 // (1) target rate is below transition rate, or
505 // (2) encoder is in stressed state and target rate below a max threshold.
philipel9d3ab612015-12-21 04:12:39 -0800506 if ((avg_target_rate_ < estimated_transition_rate_down) ||
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000507 (encoder_state_ == kStressedEncoding && avg_target_rate_ < max_rate)) {
508 // Get the down-sampling action: based on content class, and how low
509 // average target rate is relative to transition rate.
510 uint8_t spatial_fact =
511 kSpatialAction[content_class_ +
512 9 * RateClass(estimated_transition_rate_down)];
513 uint8_t temp_fact =
514 kTemporalAction[content_class_ +
515 9 * RateClass(estimated_transition_rate_down)];
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000516
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000517 switch (spatial_fact) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000518 case 4: {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000519 action_.spatial = kOneQuarterSpatialUniform;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000520 break;
521 }
522 case 2: {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000523 action_.spatial = kOneHalfSpatialUniform;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000524 break;
525 }
526 case 1: {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000527 action_.spatial = kNoChangeSpatial;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000528 break;
529 }
philipel9d3ab612015-12-21 04:12:39 -0800530 default: { assert(false); }
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000531 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000532 switch (temp_fact) {
533 case 3: {
534 action_.temporal = kTwoThirdsTemporal;
535 break;
536 }
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000537 case 2: {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000538 action_.temporal = kOneHalfTemporal;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000539 break;
540 }
541 case 1: {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000542 action_.temporal = kNoChangeTemporal;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000543 break;
544 }
philipel9d3ab612015-12-21 04:12:39 -0800545 default: { assert(false); }
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000546 }
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000547 // Only allow for one action (spatial or temporal) at a given time.
548 assert(action_.temporal == kNoChangeTemporal ||
549 action_.spatial == kNoChangeSpatial);
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000550
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000551 // Adjust cases not captured in tables, mainly based on frame rate, and
552 // also check for odd frame sizes.
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000553 AdjustAction();
554
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000555 // Update down-sampling state.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000556 if (action_.spatial != kNoChangeSpatial ||
557 action_.temporal != kNoChangeTemporal) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000558 UpdateDownsamplingState(kDownResolution);
559 return true;
560 }
561 }
562 return false;
niklase@google.com470e71d2011-07-07 08:21:25 +0000563}
564
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000565float VCMQmResolution::GetTransitionRate(float fac_width,
566 float fac_height,
567 float fac_temp,
568 float scale_fac) {
philipel9d3ab612015-12-21 04:12:39 -0800569 ImageType image_type =
570 GetImageType(static_cast<uint16_t>(fac_width * width_),
571 static_cast<uint16_t>(fac_height * height_));
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000572
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000573 FrameRateLevelClass framerate_level =
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000574 FrameRateLevel(fac_temp * avg_incoming_framerate_);
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000575 // If we are checking for going up temporally, and this is the last
576 // temporal action, then use native frame rate.
577 if (down_action_history_[1].temporal == kNoChangeTemporal &&
578 fac_temp > 1.0f) {
579 framerate_level = FrameRateLevel(native_frame_rate_);
580 }
marpan@google.com86548c62011-07-12 17:12:57 +0000581
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000582 // The maximum allowed rate below which down-sampling is allowed:
583 // Nominal values based on image format (frame size and frame rate).
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000584 float max_rate = kFrameRateFac[framerate_level] * kMaxRateQm[image_type];
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000585
philipel9d3ab612015-12-21 04:12:39 -0800586 uint8_t image_class = image_type > kVGA ? 1 : 0;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000587 uint8_t table_index = image_class * 9 + content_class_;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000588 // Scale factor for down-sampling transition threshold:
589 // factor based on the content class and the image size.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000590 float scaleTransRate = kScaleTransRateQm[table_index];
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000591 // Threshold bitrate for resolution action.
philipel9d3ab612015-12-21 04:12:39 -0800592 return static_cast<float>(scale_fac * scaleTransRate * max_rate);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000593}
594
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000595void VCMQmResolution::UpdateDownsamplingState(UpDownAction up_down) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000596 if (up_down == kUpResolution) {
597 qm_->spatial_width_fact = 1.0f / kFactorWidthSpatial[action_.spatial];
598 qm_->spatial_height_fact = 1.0f / kFactorHeightSpatial[action_.spatial];
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000599 // If last spatial action was 1/2x1/2, we undo it in two steps, so the
600 // spatial scale factor in this first step is modified as (4.0/3.0 / 2.0).
601 if (action_.spatial == kOneQuarterSpatialUniform) {
philipel9d3ab612015-12-21 04:12:39 -0800602 qm_->spatial_width_fact = 1.0f *
603 kFactorWidthSpatial[kOneHalfSpatialUniform] /
604 kFactorWidthSpatial[kOneQuarterSpatialUniform];
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000605 qm_->spatial_height_fact =
606 1.0f * kFactorHeightSpatial[kOneHalfSpatialUniform] /
607 kFactorHeightSpatial[kOneQuarterSpatialUniform];
608 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000609 qm_->temporal_fact = 1.0f / kFactorTemporal[action_.temporal];
610 RemoveLastDownAction();
611 } else if (up_down == kDownResolution) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000612 ConstrainAmountOfDownSampling();
613 ConvertSpatialFractionalToWhole();
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000614 qm_->spatial_width_fact = kFactorWidthSpatial[action_.spatial];
615 qm_->spatial_height_fact = kFactorHeightSpatial[action_.spatial];
616 qm_->temporal_fact = kFactorTemporal[action_.temporal];
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000617 InsertLatestDownAction();
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000618 } else {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000619 // This function should only be called if either the Up or Down action
620 // has been selected.
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000621 assert(false);
622 }
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000623 UpdateCodecResolution();
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000624 state_dec_factor_spatial_ = state_dec_factor_spatial_ *
philipel9d3ab612015-12-21 04:12:39 -0800625 qm_->spatial_width_fact *
626 qm_->spatial_height_fact;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000627 state_dec_factor_temporal_ = state_dec_factor_temporal_ * qm_->temporal_fact;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000628}
629
philipel9d3ab612015-12-21 04:12:39 -0800630void VCMQmResolution::UpdateCodecResolution() {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000631 if (action_.spatial != kNoChangeSpatial) {
632 qm_->change_resolution_spatial = true;
philipel9d3ab612015-12-21 04:12:39 -0800633 qm_->codec_width =
634 static_cast<uint16_t>(width_ / qm_->spatial_width_fact + 0.5f);
635 qm_->codec_height =
636 static_cast<uint16_t>(height_ / qm_->spatial_height_fact + 0.5f);
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000637 // Size should not exceed native sizes.
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000638 assert(qm_->codec_width <= native_width_);
639 assert(qm_->codec_height <= native_height_);
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000640 // New sizes should be multiple of 2, otherwise spatial should not have
641 // been selected.
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000642 assert(qm_->codec_width % 2 == 0);
643 assert(qm_->codec_height % 2 == 0);
644 }
645 if (action_.temporal != kNoChangeTemporal) {
646 qm_->change_resolution_temporal = true;
647 // Update the frame rate based on the average incoming frame rate.
648 qm_->frame_rate = avg_incoming_framerate_ / qm_->temporal_fact + 0.5f;
649 if (down_action_history_[0].temporal == 0) {
650 // When we undo the last temporal-down action, make sure we go back up
651 // to the native frame rate. Since the incoming frame rate may
652 // fluctuate over time, |avg_incoming_framerate_| scaled back up may
653 // be smaller than |native_frame rate_|.
654 qm_->frame_rate = native_frame_rate_;
655 }
656 }
657}
658
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000659uint8_t VCMQmResolution::RateClass(float transition_rate) {
philipel9d3ab612015-12-21 04:12:39 -0800660 return avg_target_rate_ < (kFacLowRate * transition_rate)
661 ? 0
662 : (avg_target_rate_ >= transition_rate ? 2 : 1);
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000663}
664
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000665// TODO(marpan): Would be better to capture these frame rate adjustments by
666// extending the table data (qm_select_data.h).
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000667void VCMQmResolution::AdjustAction() {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000668 // If the spatial level is default state (neither low or high), motion level
669 // is not high, and spatial action was selected, switch to 2/3 frame rate
670 // reduction if the average incoming frame rate is high.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000671 if (spatial_.level == kDefault && motion_.level != kHigh &&
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000672 action_.spatial != kNoChangeSpatial &&
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000673 framerate_level_ == kFrameRateHigh) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000674 action_.spatial = kNoChangeSpatial;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000675 action_.temporal = kTwoThirdsTemporal;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000676 }
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000677 // If both motion and spatial level are low, and temporal down action was
678 // selected, switch to spatial 3/4x3/4 if the frame rate is not above the
679 // lower middle level (|kFrameRateMiddle1|).
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000680 if (motion_.level == kLow && spatial_.level == kLow &&
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000681 framerate_level_ <= kFrameRateMiddle1 &&
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000682 action_.temporal != kNoChangeTemporal) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000683 action_.spatial = kOneHalfSpatialUniform;
684 action_.temporal = kNoChangeTemporal;
685 }
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000686 // If spatial action is selected, and there has been too much spatial
687 // reduction already (i.e., 1/4), then switch to temporal action if the
688 // average frame rate is not low.
689 if (action_.spatial != kNoChangeSpatial &&
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000690 down_action_history_[0].spatial == kOneQuarterSpatialUniform &&
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000691 framerate_level_ != kFrameRateLow) {
692 action_.spatial = kNoChangeSpatial;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000693 action_.temporal = kTwoThirdsTemporal;
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000694 }
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000695 // Never use temporal action if number of temporal layers is above 2.
696 if (num_layers_ > 2) {
philipel9d3ab612015-12-21 04:12:39 -0800697 if (action_.temporal != kNoChangeTemporal) {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000698 action_.spatial = kOneHalfSpatialUniform;
699 }
700 action_.temporal = kNoChangeTemporal;
701 }
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000702 // If spatial action was selected, we need to make sure the frame sizes
703 // are multiples of two. Otherwise switch to 2/3 temporal.
philipel9d3ab612015-12-21 04:12:39 -0800704 if (action_.spatial != kNoChangeSpatial && !EvenFrameSize()) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000705 action_.spatial = kNoChangeSpatial;
706 // Only one action (spatial or temporal) is allowed at a given time, so need
707 // to check whether temporal action is currently selected.
708 action_.temporal = kTwoThirdsTemporal;
709 }
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000710}
711
712void VCMQmResolution::ConvertSpatialFractionalToWhole() {
713 // If 3/4 spatial is selected, check if there has been another 3/4,
714 // and if so, combine them into 1/2. 1/2 scaling is more efficient than 9/16.
715 // Note we define 3/4x3/4 spatial as kOneHalfSpatialUniform.
716 if (action_.spatial == kOneHalfSpatialUniform) {
717 bool found = false;
718 int isel = kDownActionHistorySize;
719 for (int i = 0; i < kDownActionHistorySize; ++i) {
philipel9d3ab612015-12-21 04:12:39 -0800720 if (down_action_history_[i].spatial == kOneHalfSpatialUniform) {
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000721 isel = i;
722 found = true;
723 break;
724 }
725 }
726 if (found) {
philipel9d3ab612015-12-21 04:12:39 -0800727 action_.spatial = kOneQuarterSpatialUniform;
728 state_dec_factor_spatial_ =
729 state_dec_factor_spatial_ /
730 (kFactorWidthSpatial[kOneHalfSpatialUniform] *
731 kFactorHeightSpatial[kOneHalfSpatialUniform]);
732 // Check if switching to 1/2x1/2 (=1/4) spatial is allowed.
733 ConstrainAmountOfDownSampling();
734 if (action_.spatial == kNoChangeSpatial) {
735 // Not allowed. Go back to 3/4x3/4 spatial.
736 action_.spatial = kOneHalfSpatialUniform;
737 state_dec_factor_spatial_ =
738 state_dec_factor_spatial_ *
739 kFactorWidthSpatial[kOneHalfSpatialUniform] *
740 kFactorHeightSpatial[kOneHalfSpatialUniform];
741 } else {
742 // Switching is allowed. Remove 3/4x3/4 from the history, and update
743 // the frame size.
744 for (int i = isel; i < kDownActionHistorySize - 1; ++i) {
745 down_action_history_[i].spatial = down_action_history_[i + 1].spatial;
746 }
747 width_ = width_ * kFactorWidthSpatial[kOneHalfSpatialUniform];
748 height_ = height_ * kFactorHeightSpatial[kOneHalfSpatialUniform];
749 }
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000750 }
751 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000752}
753
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000754// Returns false if the new frame sizes, under the current spatial action,
755// are not multiples of two.
756bool VCMQmResolution::EvenFrameSize() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000757 if (action_.spatial == kOneHalfSpatialUniform) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000758 if ((width_ * 3 / 4) % 2 != 0 || (height_ * 3 / 4) % 2 != 0) {
759 return false;
760 }
761 } else if (action_.spatial == kOneQuarterSpatialUniform) {
762 if ((width_ * 1 / 2) % 2 != 0 || (height_ * 1 / 2) % 2 != 0) {
763 return false;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000764 }
765 }
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000766 return true;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000767}
768
769void VCMQmResolution::InsertLatestDownAction() {
770 if (action_.spatial != kNoChangeSpatial) {
771 for (int i = kDownActionHistorySize - 1; i > 0; --i) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000772 down_action_history_[i].spatial = down_action_history_[i - 1].spatial;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000773 }
774 down_action_history_[0].spatial = action_.spatial;
775 }
776 if (action_.temporal != kNoChangeTemporal) {
777 for (int i = kDownActionHistorySize - 1; i > 0; --i) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000778 down_action_history_[i].temporal = down_action_history_[i - 1].temporal;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000779 }
780 down_action_history_[0].temporal = action_.temporal;
781 }
782}
783
784void VCMQmResolution::RemoveLastDownAction() {
785 if (action_.spatial != kNoChangeSpatial) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000786 // If the last spatial action was 1/2x1/2 we replace it with 3/4x3/4.
787 if (action_.spatial == kOneQuarterSpatialUniform) {
788 down_action_history_[0].spatial = kOneHalfSpatialUniform;
789 } else {
790 for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
791 down_action_history_[i].spatial = down_action_history_[i + 1].spatial;
792 }
793 down_action_history_[kDownActionHistorySize - 1].spatial =
794 kNoChangeSpatial;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000795 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000796 }
797 if (action_.temporal != kNoChangeTemporal) {
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000798 for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
799 down_action_history_[i].temporal = down_action_history_[i + 1].temporal;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000800 }
801 down_action_history_[kDownActionHistorySize - 1].temporal =
802 kNoChangeTemporal;
803 }
804}
805
806void VCMQmResolution::ConstrainAmountOfDownSampling() {
807 // Sanity checks on down-sampling selection:
808 // override the settings for too small image size and/or frame rate.
809 // Also check the limit on current down-sampling states.
810
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000811 float spatial_width_fact = kFactorWidthSpatial[action_.spatial];
812 float spatial_height_fact = kFactorHeightSpatial[action_.spatial];
813 float temporal_fact = kFactorTemporal[action_.temporal];
philipel9d3ab612015-12-21 04:12:39 -0800814 float new_dec_factor_spatial =
815 state_dec_factor_spatial_ * spatial_width_fact * spatial_height_fact;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000816 float new_dec_factor_temp = state_dec_factor_temporal_ * temporal_fact;
817
818 // No spatial sampling if current frame size is too small, or if the
819 // amount of spatial down-sampling is above maximum spatial down-action.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000820 if ((width_ * height_) <= kMinImageSize ||
821 new_dec_factor_spatial > kMaxSpatialDown) {
822 action_.spatial = kNoChangeSpatial;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000823 new_dec_factor_spatial = state_dec_factor_spatial_;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000824 }
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000825 // No frame rate reduction if average frame rate is below some point, or if
826 // the amount of temporal down-sampling is above maximum temporal down-action.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000827 if (avg_incoming_framerate_ <= kMinFrameRate ||
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000828 new_dec_factor_temp > kMaxTempDown) {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000829 action_.temporal = kNoChangeTemporal;
marpan@webrtc.orgc5b392e2012-06-29 21:44:55 +0000830 new_dec_factor_temp = state_dec_factor_temporal_;
831 }
832 // Check if the total (spatial-temporal) down-action is above maximum allowed,
833 // if so, disallow the current selected down-action.
834 if (new_dec_factor_spatial * new_dec_factor_temp > kMaxTotalDown) {
835 if (action_.spatial != kNoChangeSpatial) {
836 action_.spatial = kNoChangeSpatial;
837 } else if (action_.temporal != kNoChangeTemporal) {
838 action_.temporal = kNoChangeTemporal;
839 } else {
840 // We only allow for one action (spatial or temporal) at a given time, so
841 // either spatial or temporal action is selected when this function is
842 // called. If the selected action is disallowed from one of the above
843 // 2 prior conditions (on spatial & temporal max down-action), then this
844 // condition "total down-action > |kMaxTotalDown|" would not be entered.
845 assert(false);
846 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000847 }
848}
849
850void VCMQmResolution::PickSpatialOrTemporal() {
851 // Pick the one that has had the most down-sampling thus far.
852 if (state_dec_factor_spatial_ > state_dec_factor_temporal_) {
853 action_.spatial = down_action_history_[0].spatial;
854 action_.temporal = kNoChangeTemporal;
855 } else {
856 action_.spatial = kNoChangeSpatial;
857 action_.temporal = down_action_history_[0].temporal;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000858 }
859}
860
marpan@webrtc.orge22d81c2012-03-20 18:21:53 +0000861// TODO(marpan): Update when we allow for directional spatial down-sampling.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000862void VCMQmResolution::SelectSpatialDirectionMode(float transition_rate) {
863 // Default is 4/3x4/3
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000864 // For bit rates well below transitional rate, we select 2x2.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000865 if (avg_target_rate_ < transition_rate * kRateRedSpatial2X2) {
866 qm_->spatial_width_fact = 2.0f;
867 qm_->spatial_height_fact = 2.0f;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000868 }
869 // Otherwise check prediction errors and aspect ratio.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000870 float spatial_err = 0.0f;
871 float spatial_err_h = 0.0f;
872 float spatial_err_v = 0.0f;
873 if (content_metrics_) {
874 spatial_err = content_metrics_->spatial_pred_err;
875 spatial_err_h = content_metrics_->spatial_pred_err_h;
876 spatial_err_v = content_metrics_->spatial_pred_err_v;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000877 }
878
879 // Favor 1x2 if aspect_ratio is 16:9.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000880 if (aspect_ratio_ >= 16.0f / 9.0f) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000881 // Check if 1x2 has lowest prediction error.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000882 if (spatial_err_h < spatial_err && spatial_err_h < spatial_err_v) {
883 qm_->spatial_width_fact = 2.0f;
884 qm_->spatial_height_fact = 1.0f;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000885 }
886 }
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000887 // Check for 4/3x4/3 selection: favor 2x2 over 1x2 and 2x1.
888 if (spatial_err < spatial_err_h * (1.0f + kSpatialErr2x2VsHoriz) &&
889 spatial_err < spatial_err_v * (1.0f + kSpatialErr2X2VsVert)) {
890 qm_->spatial_width_fact = 4.0f / 3.0f;
891 qm_->spatial_height_fact = 4.0f / 3.0f;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000892 }
893 // Check for 2x1 selection.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000894 if (spatial_err_v < spatial_err_h * (1.0f - kSpatialErrVertVsHoriz) &&
895 spatial_err_v < spatial_err * (1.0f - kSpatialErr2X2VsVert)) {
896 qm_->spatial_width_fact = 1.0f;
897 qm_->spatial_height_fact = 2.0f;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000898 }
899}
marpan@google.com86548c62011-07-12 17:12:57 +0000900
901// ROBUSTNESS CLASS
902
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000903VCMQmRobustness::VCMQmRobustness() {
904 Reset();
marpan@google.com86548c62011-07-12 17:12:57 +0000905}
niklase@google.com470e71d2011-07-07 08:21:25 +0000906
philipel9d3ab612015-12-21 04:12:39 -0800907VCMQmRobustness::~VCMQmRobustness() {}
niklase@google.com470e71d2011-07-07 08:21:25 +0000908
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000909void VCMQmRobustness::Reset() {
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000910 prev_total_rate_ = 0.0f;
911 prev_rtt_time_ = 0;
912 prev_packet_loss_ = 0;
913 prev_code_rate_delta_ = 0;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000914 ResetQM();
niklase@google.com470e71d2011-07-07 08:21:25 +0000915}
916
marpan@google.com86548c62011-07-12 17:12:57 +0000917// Adjust the FEC rate based on the content and the network state
918// (packet loss rate, total rate/bandwidth, round trip time).
919// Note that packetLoss here is the filtered loss value.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000920float VCMQmRobustness::AdjustFecFactor(uint8_t code_rate_delta,
921 float total_rate,
922 float framerate,
pkasting@chromium.org16825b12015-01-12 21:51:21 +0000923 int64_t rtt_time,
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000924 uint8_t packet_loss) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000925 // Default: no adjustment
philipel9d3ab612015-12-21 04:12:39 -0800926 float adjust_fec = 1.0f;
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000927 if (content_metrics_ == NULL) {
928 return adjust_fec;
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000929 }
930 // Compute class state of the content.
931 ComputeMotionNFD();
932 ComputeSpatial();
marpan@webrtc.orgbd5648f2012-02-17 23:16:58 +0000933
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000934 // TODO(marpan): Set FEC adjustment factor.
935
936 // Keep track of previous values of network state:
937 // adjustment may be also based on pattern of changes in network state.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000938 prev_total_rate_ = total_rate;
939 prev_rtt_time_ = rtt_time;
940 prev_packet_loss_ = packet_loss;
941 prev_code_rate_delta_ = code_rate_delta;
942 return adjust_fec;
niklase@google.com470e71d2011-07-07 08:21:25 +0000943}
944
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000945// Set the UEP (unequal-protection across packets) on/off for the FEC.
marpan@webrtc.orgaccf6072012-03-07 17:16:10 +0000946bool VCMQmRobustness::SetUepProtection(uint8_t code_rate_delta,
947 float total_rate,
948 uint8_t packet_loss,
949 bool frame_type) {
marpan@webrtc.org9d76b4e2012-02-28 23:39:31 +0000950 // Default.
951 return false;
niklase@google.com470e71d2011-07-07 08:21:25 +0000952}
philipel9d3ab612015-12-21 04:12:39 -0800953} // namespace webrtc