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