• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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