Home | History | Annotate | Download | only in source
      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 
     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 
     39 VCMQmMethod::~VCMQmMethod() {
     40 }
     41 
     42 void VCMQmMethod::ResetQM() {
     43   aspect_ratio_ = 1.0f;
     44   motion_.Reset();
     45   spatial_.Reset();
     46   content_class_ = 0;
     47 }
     48 
     49 uint8_t VCMQmMethod::ComputeContentClass() {
     50   ComputeMotionNFD();
     51   ComputeSpatial();
     52   return content_class_ = 3 * motion_.level + spatial_.level;
     53 }
     54 
     55 void VCMQmMethod::UpdateContent(const VideoContentMetrics*  contentMetrics) {
     56   content_metrics_ = contentMetrics;
     57 }
     58 
     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 
     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 
     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 
    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 
    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 
    153 VCMQmResolution::VCMQmResolution()
    154     :  qm_(new VCMResolutionScale()) {
    155   Reset();
    156 }
    157 
    158 VCMQmResolution::~VCMQmResolution() {
    159   delete qm_;
    160 }
    161 
    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 
    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 
    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 
    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.
    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 
    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.
    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.
    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.
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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).
    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 
    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.
    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 
    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 
    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 
    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 
    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.
    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 
    908 VCMQmRobustness::VCMQmRobustness() {
    909   Reset();
    910 }
    911 
    912 VCMQmRobustness::~VCMQmRobustness() {
    913 }
    914 
    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.
    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.
    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