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