1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
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
11 #include "webrtc/modules/video_coding/main/source/qm_select.h"
12
13 #include <math.h>
14
15 #include "webrtc/modules/interface/module_common_types.h"
16 #include "webrtc/modules/video_coding/main/interface/video_coding_defines.h"
17 #include "webrtc/modules/video_coding/main/source/internal_defines.h"
18 #include "webrtc/modules/video_coding/main/source/qm_select_data.h"
19 #include "webrtc/system_wrappers/interface/trace.h"
20
21 namespace webrtc {
22
23 // QM-METHOD class
24
VCMQmMethod()25 VCMQmMethod::VCMQmMethod()
26 : content_metrics_(NULL),
27 width_(0),
28 height_(0),
29 user_frame_rate_(0.0f),
30 native_width_(0),
31 native_height_(0),
32 native_frame_rate_(0.0f),
33 image_type_(kVGA),
34 framerate_level_(kFrameRateHigh),
35 init_(false) {
36 ResetQM();
37 }
38
~VCMQmMethod()39 VCMQmMethod::~VCMQmMethod() {
40 }
41
ResetQM()42 void VCMQmMethod::ResetQM() {
43 aspect_ratio_ = 1.0f;
44 motion_.Reset();
45 spatial_.Reset();
46 content_class_ = 0;
47 }
48
ComputeContentClass()49 uint8_t VCMQmMethod::ComputeContentClass() {
50 ComputeMotionNFD();
51 ComputeSpatial();
52 return content_class_ = 3 * motion_.level + spatial_.level;
53 }
54
UpdateContent(const VideoContentMetrics * contentMetrics)55 void VCMQmMethod::UpdateContent(const VideoContentMetrics* contentMetrics) {
56 content_metrics_ = contentMetrics;
57 }
58
ComputeMotionNFD()59 void VCMQmMethod::ComputeMotionNFD() {
60 if (content_metrics_) {
61 motion_.value = content_metrics_->motion_magnitude;
62 }
63 // Determine motion level.
64 if (motion_.value < kLowMotionNfd) {
65 motion_.level = kLow;
66 } else if (motion_.value > kHighMotionNfd) {
67 motion_.level = kHigh;
68 } else {
69 motion_.level = kDefault;
70 }
71 }
72
ComputeSpatial()73 void VCMQmMethod::ComputeSpatial() {
74 float spatial_err = 0.0;
75 float spatial_err_h = 0.0;
76 float spatial_err_v = 0.0;
77 if (content_metrics_) {
78 spatial_err = content_metrics_->spatial_pred_err;
79 spatial_err_h = content_metrics_->spatial_pred_err_h;
80 spatial_err_v = content_metrics_->spatial_pred_err_v;
81 }
82 // Spatial measure: take average of 3 prediction errors.
83 spatial_.value = (spatial_err + spatial_err_h + spatial_err_v) / 3.0f;
84
85 // Reduce thresholds for large scenes/higher pixel correlation.
86 float scale2 = image_type_ > kVGA ? kScaleTexture : 1.0;
87
88 if (spatial_.value > scale2 * kHighTexture) {
89 spatial_.level = kHigh;
90 } else if (spatial_.value < scale2 * kLowTexture) {
91 spatial_.level = kLow;
92 } else {
93 spatial_.level = kDefault;
94 }
95 }
96
GetImageType(uint16_t width,uint16_t height)97 ImageType VCMQmMethod::GetImageType(uint16_t width,
98 uint16_t height) {
99 // Get the image type for the encoder frame size.
100 uint32_t image_size = width * height;
101 if (image_size == kSizeOfImageType[kQCIF]) {
102 return kQCIF;
103 } else if (image_size == kSizeOfImageType[kHCIF]) {
104 return kHCIF;
105 } else if (image_size == kSizeOfImageType[kQVGA]) {
106 return kQVGA;
107 } else if (image_size == kSizeOfImageType[kCIF]) {
108 return kCIF;
109 } else if (image_size == kSizeOfImageType[kHVGA]) {
110 return kHVGA;
111 } else if (image_size == kSizeOfImageType[kVGA]) {
112 return kVGA;
113 } else if (image_size == kSizeOfImageType[kQFULLHD]) {
114 return kQFULLHD;
115 } else if (image_size == kSizeOfImageType[kWHD]) {
116 return kWHD;
117 } else if (image_size == kSizeOfImageType[kFULLHD]) {
118 return kFULLHD;
119 } else {
120 // No exact match, find closet one.
121 return FindClosestImageType(width, height);
122 }
123 }
124
FindClosestImageType(uint16_t width,uint16_t height)125 ImageType VCMQmMethod::FindClosestImageType(uint16_t width, uint16_t height) {
126 float size = static_cast<float>(width * height);
127 float min = size;
128 int isel = 0;
129 for (int i = 0; i < kNumImageTypes; ++i) {
130 float dist = fabs(size - kSizeOfImageType[i]);
131 if (dist < min) {
132 min = dist;
133 isel = i;
134 }
135 }
136 return static_cast<ImageType>(isel);
137 }
138
FrameRateLevel(float avg_framerate)139 FrameRateLevelClass VCMQmMethod::FrameRateLevel(float avg_framerate) {
140 if (avg_framerate <= kLowFrameRate) {
141 return kFrameRateLow;
142 } else if (avg_framerate <= kMiddleFrameRate) {
143 return kFrameRateMiddle1;
144 } else if (avg_framerate <= kHighFrameRate) {
145 return kFrameRateMiddle2;
146 } else {
147 return kFrameRateHigh;
148 }
149 }
150
151 // RESOLUTION CLASS
152
VCMQmResolution()153 VCMQmResolution::VCMQmResolution()
154 : qm_(new VCMResolutionScale()) {
155 Reset();
156 }
157
~VCMQmResolution()158 VCMQmResolution::~VCMQmResolution() {
159 delete qm_;
160 }
161
ResetRates()162 void VCMQmResolution::ResetRates() {
163 sum_target_rate_ = 0.0f;
164 sum_incoming_framerate_ = 0.0f;
165 sum_rate_MM_ = 0.0f;
166 sum_rate_MM_sgn_ = 0.0f;
167 sum_packet_loss_ = 0.0f;
168 buffer_level_ = kInitBufferLevel * target_bitrate_;
169 frame_cnt_ = 0;
170 frame_cnt_delta_ = 0;
171 low_buffer_cnt_ = 0;
172 update_rate_cnt_ = 0;
173 }
174
ResetDownSamplingState()175 void VCMQmResolution::ResetDownSamplingState() {
176 state_dec_factor_spatial_ = 1.0;
177 state_dec_factor_temporal_ = 1.0;
178 for (int i = 0; i < kDownActionHistorySize; i++) {
179 down_action_history_[i].spatial = kNoChangeSpatial;
180 down_action_history_[i].temporal = kNoChangeTemporal;
181 }
182 }
183
Reset()184 void VCMQmResolution::Reset() {
185 target_bitrate_ = 0.0f;
186 incoming_framerate_ = 0.0f;
187 buffer_level_ = 0.0f;
188 per_frame_bandwidth_ = 0.0f;
189 avg_target_rate_ = 0.0f;
190 avg_incoming_framerate_ = 0.0f;
191 avg_ratio_buffer_low_ = 0.0f;
192 avg_rate_mismatch_ = 0.0f;
193 avg_rate_mismatch_sgn_ = 0.0f;
194 avg_packet_loss_ = 0.0f;
195 encoder_state_ = kStableEncoding;
196 num_layers_ = 1;
197 ResetRates();
198 ResetDownSamplingState();
199 ResetQM();
200 }
201
GetEncoderState()202 EncoderState VCMQmResolution::GetEncoderState() {
203 return encoder_state_;
204 }
205
206 // Initialize state after re-initializing the encoder,
207 // i.e., after SetEncodingData() in mediaOpt.
Initialize(float bitrate,float user_framerate,uint16_t width,uint16_t height,int num_layers)208 int VCMQmResolution::Initialize(float bitrate,
209 float user_framerate,
210 uint16_t width,
211 uint16_t height,
212 int num_layers) {
213 if (user_framerate == 0.0f || width == 0 || height == 0) {
214 return VCM_PARAMETER_ERROR;
215 }
216 Reset();
217 target_bitrate_ = bitrate;
218 incoming_framerate_ = user_framerate;
219 UpdateCodecParameters(user_framerate, width, height);
220 native_width_ = width;
221 native_height_ = height;
222 native_frame_rate_ = user_framerate;
223 num_layers_ = num_layers;
224 // Initial buffer level.
225 buffer_level_ = kInitBufferLevel * target_bitrate_;
226 // Per-frame bandwidth.
227 per_frame_bandwidth_ = target_bitrate_ / user_framerate;
228 init_ = true;
229 return VCM_OK;
230 }
231
UpdateCodecParameters(float frame_rate,uint16_t width,uint16_t height)232 void VCMQmResolution::UpdateCodecParameters(float frame_rate, uint16_t width,
233 uint16_t height) {
234 width_ = width;
235 height_ = height;
236 // |user_frame_rate| is the target frame rate for VPM frame dropper.
237 user_frame_rate_ = frame_rate;
238 image_type_ = GetImageType(width, height);
239 }
240
241 // Update rate data after every encoded frame.
UpdateEncodedSize(int encoded_size,FrameType encoded_frame_type)242 void VCMQmResolution::UpdateEncodedSize(int encoded_size,
243 FrameType encoded_frame_type) {
244 frame_cnt_++;
245 // Convert to Kbps.
246 float encoded_size_kbits = static_cast<float>((encoded_size * 8.0) / 1000.0);
247
248 // Update the buffer level:
249 // Note this is not the actual encoder buffer level.
250 // |buffer_level_| is reset to an initial value after SelectResolution is
251 // called, and does not account for frame dropping by encoder or VCM.
252 buffer_level_ += per_frame_bandwidth_ - encoded_size_kbits;
253
254 // Counter for occurrences of low buffer level:
255 // low/negative values means encoder is likely dropping frames.
256 if (buffer_level_ <= kPercBufferThr * kInitBufferLevel * target_bitrate_) {
257 low_buffer_cnt_++;
258 }
259 }
260
261 // Update various quantities after SetTargetRates in MediaOpt.
UpdateRates(float target_bitrate,float encoder_sent_rate,float incoming_framerate,uint8_t packet_loss)262 void VCMQmResolution::UpdateRates(float target_bitrate,
263 float encoder_sent_rate,
264 float incoming_framerate,
265 uint8_t packet_loss) {
266 // Sum the target bitrate: this is the encoder rate from previous update
267 // (~1sec), i.e, before the update for next ~1sec.
268 sum_target_rate_ += target_bitrate_;
269 update_rate_cnt_++;
270
271 // Sum the received (from RTCP reports) packet loss rates.
272 sum_packet_loss_ += static_cast<float>(packet_loss / 255.0);
273
274 // Sum the sequence rate mismatch:
275 // Mismatch here is based on the difference between the target rate
276 // used (in previous ~1sec) and the average actual encoding rate measured
277 // at previous ~1sec.
278 float diff = target_bitrate_ - encoder_sent_rate;
279 if (target_bitrate_ > 0.0)
280 sum_rate_MM_ += fabs(diff) / target_bitrate_;
281 int sgnDiff = diff > 0 ? 1 : (diff < 0 ? -1 : 0);
282 // To check for consistent under(+)/over_shooting(-) of target rate.
283 sum_rate_MM_sgn_ += sgnDiff;
284
285 // Update with the current new target and frame rate:
286 // these values are ones the encoder will use for the current/next ~1sec.
287 target_bitrate_ = target_bitrate;
288 incoming_framerate_ = incoming_framerate;
289 sum_incoming_framerate_ += incoming_framerate_;
290 // Update the per_frame_bandwidth:
291 // this is the per_frame_bw for the current/next ~1sec.
292 per_frame_bandwidth_ = 0.0f;
293 if (incoming_framerate_ > 0.0f) {
294 per_frame_bandwidth_ = target_bitrate_ / incoming_framerate_;
295 }
296 }
297
298 // Select the resolution factors: frame size and frame rate change (qm scales).
299 // Selection is for going down in resolution, or for going back up
300 // (if a previous down-sampling action was taken).
301
302 // In the current version the following constraints are imposed:
303 // 1) We only allow for one action, either down or up, at a given time.
304 // 2) The possible down-sampling actions are: spatial by 1/2x1/2, 3/4x3/4;
305 // temporal/frame rate reduction by 1/2 and 2/3.
306 // 3) The action for going back up is the reverse of last (spatial or temporal)
307 // down-sampling action. The list of down-sampling actions from the
308 // Initialize() state are kept in |down_action_history_|.
309 // 4) The total amount of down-sampling (spatial and/or temporal) from the
310 // Initialize() state (native resolution) is limited by various factors.
SelectResolution(VCMResolutionScale ** qm)311 int VCMQmResolution::SelectResolution(VCMResolutionScale** qm) {
312 if (!init_) {
313 return VCM_UNINITIALIZED;
314 }
315 if (content_metrics_ == NULL) {
316 Reset();
317 *qm = qm_;
318 return VCM_OK;
319 }
320
321 // Check conditions on down-sampling state.
322 assert(state_dec_factor_spatial_ >= 1.0f);
323 assert(state_dec_factor_temporal_ >= 1.0f);
324 assert(state_dec_factor_spatial_ <= kMaxSpatialDown);
325 assert(state_dec_factor_temporal_ <= kMaxTempDown);
326 assert(state_dec_factor_temporal_ * state_dec_factor_spatial_ <=
327 kMaxTotalDown);
328
329 // Compute content class for selection.
330 content_class_ = ComputeContentClass();
331 // Compute various rate quantities for selection.
332 ComputeRatesForSelection();
333
334 // Get the encoder state.
335 ComputeEncoderState();
336
337 // Default settings: no action.
338 SetDefaultAction();
339 *qm = qm_;
340
341 // Check for going back up in resolution, if we have had some down-sampling
342 // relative to native state in Initialize().
343 if (down_action_history_[0].spatial != kNoChangeSpatial ||
344 down_action_history_[0].temporal != kNoChangeTemporal) {
345 if (GoingUpResolution()) {
346 *qm = qm_;
347 return VCM_OK;
348 }
349 }
350
351 // Check for going down in resolution.
352 if (GoingDownResolution()) {
353 *qm = qm_;
354 return VCM_OK;
355 }
356 return VCM_OK;
357 }
358
SetDefaultAction()359 void VCMQmResolution::SetDefaultAction() {
360 qm_->codec_width = width_;
361 qm_->codec_height = height_;
362 qm_->frame_rate = user_frame_rate_;
363 qm_->change_resolution_spatial = false;
364 qm_->change_resolution_temporal = false;
365 qm_->spatial_width_fact = 1.0f;
366 qm_->spatial_height_fact = 1.0f;
367 qm_->temporal_fact = 1.0f;
368 action_.spatial = kNoChangeSpatial;
369 action_.temporal = kNoChangeTemporal;
370 }
371
ComputeRatesForSelection()372 void VCMQmResolution::ComputeRatesForSelection() {
373 avg_target_rate_ = 0.0f;
374 avg_incoming_framerate_ = 0.0f;
375 avg_ratio_buffer_low_ = 0.0f;
376 avg_rate_mismatch_ = 0.0f;
377 avg_rate_mismatch_sgn_ = 0.0f;
378 avg_packet_loss_ = 0.0f;
379 if (frame_cnt_ > 0) {
380 avg_ratio_buffer_low_ = static_cast<float>(low_buffer_cnt_) /
381 static_cast<float>(frame_cnt_);
382 }
383 if (update_rate_cnt_ > 0) {
384 avg_rate_mismatch_ = static_cast<float>(sum_rate_MM_) /
385 static_cast<float>(update_rate_cnt_);
386 avg_rate_mismatch_sgn_ = static_cast<float>(sum_rate_MM_sgn_) /
387 static_cast<float>(update_rate_cnt_);
388 avg_target_rate_ = static_cast<float>(sum_target_rate_) /
389 static_cast<float>(update_rate_cnt_);
390 avg_incoming_framerate_ = static_cast<float>(sum_incoming_framerate_) /
391 static_cast<float>(update_rate_cnt_);
392 avg_packet_loss_ = static_cast<float>(sum_packet_loss_) /
393 static_cast<float>(update_rate_cnt_);
394 }
395 // For selection we may want to weight some quantities more heavily
396 // with the current (i.e., next ~1sec) rate values.
397 avg_target_rate_ = kWeightRate * avg_target_rate_ +
398 (1.0 - kWeightRate) * target_bitrate_;
399 avg_incoming_framerate_ = kWeightRate * avg_incoming_framerate_ +
400 (1.0 - kWeightRate) * incoming_framerate_;
401 // Use base layer frame rate for temporal layers: this will favor spatial.
402 assert(num_layers_ > 0);
403 framerate_level_ = FrameRateLevel(
404 avg_incoming_framerate_ / static_cast<float>(1 << (num_layers_ - 1)));
405 }
406
ComputeEncoderState()407 void VCMQmResolution::ComputeEncoderState() {
408 // Default.
409 encoder_state_ = kStableEncoding;
410
411 // Assign stressed state if:
412 // 1) occurrences of low buffer levels is high, or
413 // 2) rate mis-match is high, and consistent over-shooting by encoder.
414 if ((avg_ratio_buffer_low_ > kMaxBufferLow) ||
415 ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
416 (avg_rate_mismatch_sgn_ < -kRateOverShoot))) {
417 encoder_state_ = kStressedEncoding;
418 }
419 // Assign easy state if:
420 // 1) rate mis-match is high, and
421 // 2) consistent under-shooting by encoder.
422 if ((avg_rate_mismatch_ > kMaxRateMisMatch) &&
423 (avg_rate_mismatch_sgn_ > kRateUnderShoot)) {
424 encoder_state_ = kEasyEncoding;
425 }
426 }
427
GoingUpResolution()428 bool VCMQmResolution::GoingUpResolution() {
429 // For going up, we check for undoing the previous down-sampling action.
430
431 float fac_width = kFactorWidthSpatial[down_action_history_[0].spatial];
432 float fac_height = kFactorHeightSpatial[down_action_history_[0].spatial];
433 float fac_temp = kFactorTemporal[down_action_history_[0].temporal];
434 // For going up spatially, we allow for going up by 3/4x3/4 at each stage.
435 // So if the last spatial action was 1/2x1/2 it would be undone in 2 stages.
436 // Modify the fac_width/height for this case.
437 if (down_action_history_[0].spatial == kOneQuarterSpatialUniform) {
438 fac_width = kFactorWidthSpatial[kOneQuarterSpatialUniform] /
439 kFactorWidthSpatial[kOneHalfSpatialUniform];
440 fac_height = kFactorHeightSpatial[kOneQuarterSpatialUniform] /
441 kFactorHeightSpatial[kOneHalfSpatialUniform];
442 }
443
444 // Check if we should go up both spatially and temporally.
445 if (down_action_history_[0].spatial != kNoChangeSpatial &&
446 down_action_history_[0].temporal != kNoChangeTemporal) {
447 if (ConditionForGoingUp(fac_width, fac_height, fac_temp,
448 kTransRateScaleUpSpatialTemp)) {
449 action_.spatial = down_action_history_[0].spatial;
450 action_.temporal = down_action_history_[0].temporal;
451 UpdateDownsamplingState(kUpResolution);
452 return true;
453 }
454 }
455 // Check if we should go up either spatially or temporally.
456 bool selected_up_spatial = false;
457 bool selected_up_temporal = false;
458 if (down_action_history_[0].spatial != kNoChangeSpatial) {
459 selected_up_spatial = ConditionForGoingUp(fac_width, fac_height, 1.0f,
460 kTransRateScaleUpSpatial);
461 }
462 if (down_action_history_[0].temporal != kNoChangeTemporal) {
463 selected_up_temporal = ConditionForGoingUp(1.0f, 1.0f, fac_temp,
464 kTransRateScaleUpTemp);
465 }
466 if (selected_up_spatial && !selected_up_temporal) {
467 action_.spatial = down_action_history_[0].spatial;
468 action_.temporal = kNoChangeTemporal;
469 UpdateDownsamplingState(kUpResolution);
470 return true;
471 } else if (!selected_up_spatial && selected_up_temporal) {
472 action_.spatial = kNoChangeSpatial;
473 action_.temporal = down_action_history_[0].temporal;
474 UpdateDownsamplingState(kUpResolution);
475 return true;
476 } else if (selected_up_spatial && selected_up_temporal) {
477 PickSpatialOrTemporal();
478 UpdateDownsamplingState(kUpResolution);
479 return true;
480 }
481 return false;
482 }
483
ConditionForGoingUp(float fac_width,float fac_height,float fac_temp,float scale_fac)484 bool VCMQmResolution::ConditionForGoingUp(float fac_width,
485 float fac_height,
486 float fac_temp,
487 float scale_fac) {
488 float estimated_transition_rate_up = GetTransitionRate(fac_width, fac_height,
489 fac_temp, scale_fac);
490 // Go back up if:
491 // 1) target rate is above threshold and current encoder state is stable, or
492 // 2) encoder state is easy (encoder is significantly under-shooting target).
493 if (((avg_target_rate_ > estimated_transition_rate_up) &&
494 (encoder_state_ == kStableEncoding)) ||
495 (encoder_state_ == kEasyEncoding)) {
496 return true;
497 } else {
498 return false;
499 }
500 }
501
GoingDownResolution()502 bool VCMQmResolution::GoingDownResolution() {
503 float estimated_transition_rate_down =
504 GetTransitionRate(1.0f, 1.0f, 1.0f, 1.0f);
505 float max_rate = kFrameRateFac[framerate_level_] * kMaxRateQm[image_type_];
506 // Resolution reduction if:
507 // (1) target rate is below transition rate, or
508 // (2) encoder is in stressed state and target rate below a max threshold.
509 if ((avg_target_rate_ < estimated_transition_rate_down ) ||
510 (encoder_state_ == kStressedEncoding && avg_target_rate_ < max_rate)) {
511 // Get the down-sampling action: based on content class, and how low
512 // average target rate is relative to transition rate.
513 uint8_t spatial_fact =
514 kSpatialAction[content_class_ +
515 9 * RateClass(estimated_transition_rate_down)];
516 uint8_t temp_fact =
517 kTemporalAction[content_class_ +
518 9 * RateClass(estimated_transition_rate_down)];
519
520 switch (spatial_fact) {
521 case 4: {
522 action_.spatial = kOneQuarterSpatialUniform;
523 break;
524 }
525 case 2: {
526 action_.spatial = kOneHalfSpatialUniform;
527 break;
528 }
529 case 1: {
530 action_.spatial = kNoChangeSpatial;
531 break;
532 }
533 default: {
534 assert(false);
535 }
536 }
537 switch (temp_fact) {
538 case 3: {
539 action_.temporal = kTwoThirdsTemporal;
540 break;
541 }
542 case 2: {
543 action_.temporal = kOneHalfTemporal;
544 break;
545 }
546 case 1: {
547 action_.temporal = kNoChangeTemporal;
548 break;
549 }
550 default: {
551 assert(false);
552 }
553 }
554 // Only allow for one action (spatial or temporal) at a given time.
555 assert(action_.temporal == kNoChangeTemporal ||
556 action_.spatial == kNoChangeSpatial);
557
558 // Adjust cases not captured in tables, mainly based on frame rate, and
559 // also check for odd frame sizes.
560 AdjustAction();
561
562 // Update down-sampling state.
563 if (action_.spatial != kNoChangeSpatial ||
564 action_.temporal != kNoChangeTemporal) {
565 UpdateDownsamplingState(kDownResolution);
566 return true;
567 }
568 }
569 return false;
570 }
571
GetTransitionRate(float fac_width,float fac_height,float fac_temp,float scale_fac)572 float VCMQmResolution::GetTransitionRate(float fac_width,
573 float fac_height,
574 float fac_temp,
575 float scale_fac) {
576 ImageType image_type = GetImageType(
577 static_cast<uint16_t>(fac_width * width_),
578 static_cast<uint16_t>(fac_height * height_));
579
580 FrameRateLevelClass framerate_level =
581 FrameRateLevel(fac_temp * avg_incoming_framerate_);
582 // If we are checking for going up temporally, and this is the last
583 // temporal action, then use native frame rate.
584 if (down_action_history_[1].temporal == kNoChangeTemporal &&
585 fac_temp > 1.0f) {
586 framerate_level = FrameRateLevel(native_frame_rate_);
587 }
588
589 // The maximum allowed rate below which down-sampling is allowed:
590 // Nominal values based on image format (frame size and frame rate).
591 float max_rate = kFrameRateFac[framerate_level] * kMaxRateQm[image_type];
592
593 uint8_t image_class = image_type > kVGA ? 1: 0;
594 uint8_t table_index = image_class * 9 + content_class_;
595 // Scale factor for down-sampling transition threshold:
596 // factor based on the content class and the image size.
597 float scaleTransRate = kScaleTransRateQm[table_index];
598 // Threshold bitrate for resolution action.
599 return static_cast<float> (scale_fac * scaleTransRate * max_rate);
600 }
601
UpdateDownsamplingState(UpDownAction up_down)602 void VCMQmResolution::UpdateDownsamplingState(UpDownAction up_down) {
603 if (up_down == kUpResolution) {
604 qm_->spatial_width_fact = 1.0f / kFactorWidthSpatial[action_.spatial];
605 qm_->spatial_height_fact = 1.0f / kFactorHeightSpatial[action_.spatial];
606 // If last spatial action was 1/2x1/2, we undo it in two steps, so the
607 // spatial scale factor in this first step is modified as (4.0/3.0 / 2.0).
608 if (action_.spatial == kOneQuarterSpatialUniform) {
609 qm_->spatial_width_fact =
610 1.0f * kFactorWidthSpatial[kOneHalfSpatialUniform] /
611 kFactorWidthSpatial[kOneQuarterSpatialUniform];
612 qm_->spatial_height_fact =
613 1.0f * kFactorHeightSpatial[kOneHalfSpatialUniform] /
614 kFactorHeightSpatial[kOneQuarterSpatialUniform];
615 }
616 qm_->temporal_fact = 1.0f / kFactorTemporal[action_.temporal];
617 RemoveLastDownAction();
618 } else if (up_down == kDownResolution) {
619 ConstrainAmountOfDownSampling();
620 ConvertSpatialFractionalToWhole();
621 qm_->spatial_width_fact = kFactorWidthSpatial[action_.spatial];
622 qm_->spatial_height_fact = kFactorHeightSpatial[action_.spatial];
623 qm_->temporal_fact = kFactorTemporal[action_.temporal];
624 InsertLatestDownAction();
625 } else {
626 // This function should only be called if either the Up or Down action
627 // has been selected.
628 assert(false);
629 }
630 UpdateCodecResolution();
631 state_dec_factor_spatial_ = state_dec_factor_spatial_ *
632 qm_->spatial_width_fact * qm_->spatial_height_fact;
633 state_dec_factor_temporal_ = state_dec_factor_temporal_ * qm_->temporal_fact;
634 }
635
UpdateCodecResolution()636 void VCMQmResolution::UpdateCodecResolution() {
637 if (action_.spatial != kNoChangeSpatial) {
638 qm_->change_resolution_spatial = true;
639 qm_->codec_width = static_cast<uint16_t>(width_ /
640 qm_->spatial_width_fact + 0.5f);
641 qm_->codec_height = static_cast<uint16_t>(height_ /
642 qm_->spatial_height_fact + 0.5f);
643 // Size should not exceed native sizes.
644 assert(qm_->codec_width <= native_width_);
645 assert(qm_->codec_height <= native_height_);
646 // New sizes should be multiple of 2, otherwise spatial should not have
647 // been selected.
648 assert(qm_->codec_width % 2 == 0);
649 assert(qm_->codec_height % 2 == 0);
650 }
651 if (action_.temporal != kNoChangeTemporal) {
652 qm_->change_resolution_temporal = true;
653 // Update the frame rate based on the average incoming frame rate.
654 qm_->frame_rate = avg_incoming_framerate_ / qm_->temporal_fact + 0.5f;
655 if (down_action_history_[0].temporal == 0) {
656 // When we undo the last temporal-down action, make sure we go back up
657 // to the native frame rate. Since the incoming frame rate may
658 // fluctuate over time, |avg_incoming_framerate_| scaled back up may
659 // be smaller than |native_frame rate_|.
660 qm_->frame_rate = native_frame_rate_;
661 }
662 }
663 }
664
RateClass(float transition_rate)665 uint8_t VCMQmResolution::RateClass(float transition_rate) {
666 return avg_target_rate_ < (kFacLowRate * transition_rate) ? 0:
667 (avg_target_rate_ >= transition_rate ? 2 : 1);
668 }
669
670 // TODO(marpan): Would be better to capture these frame rate adjustments by
671 // extending the table data (qm_select_data.h).
AdjustAction()672 void VCMQmResolution::AdjustAction() {
673 // If the spatial level is default state (neither low or high), motion level
674 // is not high, and spatial action was selected, switch to 2/3 frame rate
675 // reduction if the average incoming frame rate is high.
676 if (spatial_.level == kDefault && motion_.level != kHigh &&
677 action_.spatial != kNoChangeSpatial &&
678 framerate_level_ == kFrameRateHigh) {
679 action_.spatial = kNoChangeSpatial;
680 action_.temporal = kTwoThirdsTemporal;
681 }
682 // If both motion and spatial level are low, and temporal down action was
683 // selected, switch to spatial 3/4x3/4 if the frame rate is not above the
684 // lower middle level (|kFrameRateMiddle1|).
685 if (motion_.level == kLow && spatial_.level == kLow &&
686 framerate_level_ <= kFrameRateMiddle1 &&
687 action_.temporal != kNoChangeTemporal) {
688 action_.spatial = kOneHalfSpatialUniform;
689 action_.temporal = kNoChangeTemporal;
690 }
691 // If spatial action is selected, and there has been too much spatial
692 // reduction already (i.e., 1/4), then switch to temporal action if the
693 // average frame rate is not low.
694 if (action_.spatial != kNoChangeSpatial &&
695 down_action_history_[0].spatial == kOneQuarterSpatialUniform &&
696 framerate_level_ != kFrameRateLow) {
697 action_.spatial = kNoChangeSpatial;
698 action_.temporal = kTwoThirdsTemporal;
699 }
700 // Never use temporal action if number of temporal layers is above 2.
701 if (num_layers_ > 2) {
702 if (action_.temporal != kNoChangeTemporal) {
703 action_.spatial = kOneHalfSpatialUniform;
704 }
705 action_.temporal = kNoChangeTemporal;
706 }
707 // If spatial action was selected, we need to make sure the frame sizes
708 // are multiples of two. Otherwise switch to 2/3 temporal.
709 if (action_.spatial != kNoChangeSpatial &&
710 !EvenFrameSize()) {
711 action_.spatial = kNoChangeSpatial;
712 // Only one action (spatial or temporal) is allowed at a given time, so need
713 // to check whether temporal action is currently selected.
714 action_.temporal = kTwoThirdsTemporal;
715 }
716 }
717
ConvertSpatialFractionalToWhole()718 void VCMQmResolution::ConvertSpatialFractionalToWhole() {
719 // If 3/4 spatial is selected, check if there has been another 3/4,
720 // and if so, combine them into 1/2. 1/2 scaling is more efficient than 9/16.
721 // Note we define 3/4x3/4 spatial as kOneHalfSpatialUniform.
722 if (action_.spatial == kOneHalfSpatialUniform) {
723 bool found = false;
724 int isel = kDownActionHistorySize;
725 for (int i = 0; i < kDownActionHistorySize; ++i) {
726 if (down_action_history_[i].spatial == kOneHalfSpatialUniform) {
727 isel = i;
728 found = true;
729 break;
730 }
731 }
732 if (found) {
733 action_.spatial = kOneQuarterSpatialUniform;
734 state_dec_factor_spatial_ = state_dec_factor_spatial_ /
735 (kFactorWidthSpatial[kOneHalfSpatialUniform] *
736 kFactorHeightSpatial[kOneHalfSpatialUniform]);
737 // Check if switching to 1/2x1/2 (=1/4) spatial is allowed.
738 ConstrainAmountOfDownSampling();
739 if (action_.spatial == kNoChangeSpatial) {
740 // Not allowed. Go back to 3/4x3/4 spatial.
741 action_.spatial = kOneHalfSpatialUniform;
742 state_dec_factor_spatial_ = state_dec_factor_spatial_ *
743 kFactorWidthSpatial[kOneHalfSpatialUniform] *
744 kFactorHeightSpatial[kOneHalfSpatialUniform];
745 } else {
746 // Switching is allowed. Remove 3/4x3/4 from the history, and update
747 // the frame size.
748 for (int i = isel; i < kDownActionHistorySize - 1; ++i) {
749 down_action_history_[i].spatial =
750 down_action_history_[i + 1].spatial;
751 }
752 width_ = width_ * kFactorWidthSpatial[kOneHalfSpatialUniform];
753 height_ = height_ * kFactorHeightSpatial[kOneHalfSpatialUniform];
754 }
755 }
756 }
757 }
758
759 // Returns false if the new frame sizes, under the current spatial action,
760 // are not multiples of two.
EvenFrameSize()761 bool VCMQmResolution::EvenFrameSize() {
762 if (action_.spatial == kOneHalfSpatialUniform) {
763 if ((width_ * 3 / 4) % 2 != 0 || (height_ * 3 / 4) % 2 != 0) {
764 return false;
765 }
766 } else if (action_.spatial == kOneQuarterSpatialUniform) {
767 if ((width_ * 1 / 2) % 2 != 0 || (height_ * 1 / 2) % 2 != 0) {
768 return false;
769 }
770 }
771 return true;
772 }
773
InsertLatestDownAction()774 void VCMQmResolution::InsertLatestDownAction() {
775 if (action_.spatial != kNoChangeSpatial) {
776 for (int i = kDownActionHistorySize - 1; i > 0; --i) {
777 down_action_history_[i].spatial = down_action_history_[i - 1].spatial;
778 }
779 down_action_history_[0].spatial = action_.spatial;
780 }
781 if (action_.temporal != kNoChangeTemporal) {
782 for (int i = kDownActionHistorySize - 1; i > 0; --i) {
783 down_action_history_[i].temporal = down_action_history_[i - 1].temporal;
784 }
785 down_action_history_[0].temporal = action_.temporal;
786 }
787 }
788
RemoveLastDownAction()789 void VCMQmResolution::RemoveLastDownAction() {
790 if (action_.spatial != kNoChangeSpatial) {
791 // If the last spatial action was 1/2x1/2 we replace it with 3/4x3/4.
792 if (action_.spatial == kOneQuarterSpatialUniform) {
793 down_action_history_[0].spatial = kOneHalfSpatialUniform;
794 } else {
795 for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
796 down_action_history_[i].spatial = down_action_history_[i + 1].spatial;
797 }
798 down_action_history_[kDownActionHistorySize - 1].spatial =
799 kNoChangeSpatial;
800 }
801 }
802 if (action_.temporal != kNoChangeTemporal) {
803 for (int i = 0; i < kDownActionHistorySize - 1; ++i) {
804 down_action_history_[i].temporal = down_action_history_[i + 1].temporal;
805 }
806 down_action_history_[kDownActionHistorySize - 1].temporal =
807 kNoChangeTemporal;
808 }
809 }
810
ConstrainAmountOfDownSampling()811 void VCMQmResolution::ConstrainAmountOfDownSampling() {
812 // Sanity checks on down-sampling selection:
813 // override the settings for too small image size and/or frame rate.
814 // Also check the limit on current down-sampling states.
815
816 float spatial_width_fact = kFactorWidthSpatial[action_.spatial];
817 float spatial_height_fact = kFactorHeightSpatial[action_.spatial];
818 float temporal_fact = kFactorTemporal[action_.temporal];
819 float new_dec_factor_spatial = state_dec_factor_spatial_ *
820 spatial_width_fact * spatial_height_fact;
821 float new_dec_factor_temp = state_dec_factor_temporal_ * temporal_fact;
822
823 // No spatial sampling if current frame size is too small, or if the
824 // amount of spatial down-sampling is above maximum spatial down-action.
825 if ((width_ * height_) <= kMinImageSize ||
826 new_dec_factor_spatial > kMaxSpatialDown) {
827 action_.spatial = kNoChangeSpatial;
828 new_dec_factor_spatial = state_dec_factor_spatial_;
829 }
830 // No frame rate reduction if average frame rate is below some point, or if
831 // the amount of temporal down-sampling is above maximum temporal down-action.
832 if (avg_incoming_framerate_ <= kMinFrameRate ||
833 new_dec_factor_temp > kMaxTempDown) {
834 action_.temporal = kNoChangeTemporal;
835 new_dec_factor_temp = state_dec_factor_temporal_;
836 }
837 // Check if the total (spatial-temporal) down-action is above maximum allowed,
838 // if so, disallow the current selected down-action.
839 if (new_dec_factor_spatial * new_dec_factor_temp > kMaxTotalDown) {
840 if (action_.spatial != kNoChangeSpatial) {
841 action_.spatial = kNoChangeSpatial;
842 } else if (action_.temporal != kNoChangeTemporal) {
843 action_.temporal = kNoChangeTemporal;
844 } else {
845 // We only allow for one action (spatial or temporal) at a given time, so
846 // either spatial or temporal action is selected when this function is
847 // called. If the selected action is disallowed from one of the above
848 // 2 prior conditions (on spatial & temporal max down-action), then this
849 // condition "total down-action > |kMaxTotalDown|" would not be entered.
850 assert(false);
851 }
852 }
853 }
854
PickSpatialOrTemporal()855 void VCMQmResolution::PickSpatialOrTemporal() {
856 // Pick the one that has had the most down-sampling thus far.
857 if (state_dec_factor_spatial_ > state_dec_factor_temporal_) {
858 action_.spatial = down_action_history_[0].spatial;
859 action_.temporal = kNoChangeTemporal;
860 } else {
861 action_.spatial = kNoChangeSpatial;
862 action_.temporal = down_action_history_[0].temporal;
863 }
864 }
865
866 // TODO(marpan): Update when we allow for directional spatial down-sampling.
SelectSpatialDirectionMode(float transition_rate)867 void VCMQmResolution::SelectSpatialDirectionMode(float transition_rate) {
868 // Default is 4/3x4/3
869 // For bit rates well below transitional rate, we select 2x2.
870 if (avg_target_rate_ < transition_rate * kRateRedSpatial2X2) {
871 qm_->spatial_width_fact = 2.0f;
872 qm_->spatial_height_fact = 2.0f;
873 }
874 // Otherwise check prediction errors and aspect ratio.
875 float spatial_err = 0.0f;
876 float spatial_err_h = 0.0f;
877 float spatial_err_v = 0.0f;
878 if (content_metrics_) {
879 spatial_err = content_metrics_->spatial_pred_err;
880 spatial_err_h = content_metrics_->spatial_pred_err_h;
881 spatial_err_v = content_metrics_->spatial_pred_err_v;
882 }
883
884 // Favor 1x2 if aspect_ratio is 16:9.
885 if (aspect_ratio_ >= 16.0f / 9.0f) {
886 // Check if 1x2 has lowest prediction error.
887 if (spatial_err_h < spatial_err && spatial_err_h < spatial_err_v) {
888 qm_->spatial_width_fact = 2.0f;
889 qm_->spatial_height_fact = 1.0f;
890 }
891 }
892 // Check for 4/3x4/3 selection: favor 2x2 over 1x2 and 2x1.
893 if (spatial_err < spatial_err_h * (1.0f + kSpatialErr2x2VsHoriz) &&
894 spatial_err < spatial_err_v * (1.0f + kSpatialErr2X2VsVert)) {
895 qm_->spatial_width_fact = 4.0f / 3.0f;
896 qm_->spatial_height_fact = 4.0f / 3.0f;
897 }
898 // Check for 2x1 selection.
899 if (spatial_err_v < spatial_err_h * (1.0f - kSpatialErrVertVsHoriz) &&
900 spatial_err_v < spatial_err * (1.0f - kSpatialErr2X2VsVert)) {
901 qm_->spatial_width_fact = 1.0f;
902 qm_->spatial_height_fact = 2.0f;
903 }
904 }
905
906 // ROBUSTNESS CLASS
907
VCMQmRobustness()908 VCMQmRobustness::VCMQmRobustness() {
909 Reset();
910 }
911
~VCMQmRobustness()912 VCMQmRobustness::~VCMQmRobustness() {
913 }
914
Reset()915 void VCMQmRobustness::Reset() {
916 prev_total_rate_ = 0.0f;
917 prev_rtt_time_ = 0;
918 prev_packet_loss_ = 0;
919 prev_code_rate_delta_ = 0;
920 ResetQM();
921 }
922
923 // Adjust the FEC rate based on the content and the network state
924 // (packet loss rate, total rate/bandwidth, round trip time).
925 // Note that packetLoss here is the filtered loss value.
AdjustFecFactor(uint8_t code_rate_delta,float total_rate,float framerate,uint32_t rtt_time,uint8_t packet_loss)926 float VCMQmRobustness::AdjustFecFactor(uint8_t code_rate_delta,
927 float total_rate,
928 float framerate,
929 uint32_t rtt_time,
930 uint8_t packet_loss) {
931 // Default: no adjustment
932 float adjust_fec = 1.0f;
933 if (content_metrics_ == NULL) {
934 return adjust_fec;
935 }
936 // Compute class state of the content.
937 ComputeMotionNFD();
938 ComputeSpatial();
939
940 // TODO(marpan): Set FEC adjustment factor.
941
942 // Keep track of previous values of network state:
943 // adjustment may be also based on pattern of changes in network state.
944 prev_total_rate_ = total_rate;
945 prev_rtt_time_ = rtt_time;
946 prev_packet_loss_ = packet_loss;
947 prev_code_rate_delta_ = code_rate_delta;
948 return adjust_fec;
949 }
950
951 // Set the UEP (unequal-protection across packets) on/off for the FEC.
SetUepProtection(uint8_t code_rate_delta,float total_rate,uint8_t packet_loss,bool frame_type)952 bool VCMQmRobustness::SetUepProtection(uint8_t code_rate_delta,
953 float total_rate,
954 uint8_t packet_loss,
955 bool frame_type) {
956 // Default.
957 return false;
958 }
959 } // namespace
960