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/media_optimization.h" 12 13 #include "webrtc/base/logging.h" 14 #include "webrtc/modules/video_coding/content_metrics_processing.h" 15 #include "webrtc/modules/video_coding/qm_select.h" 16 #include "webrtc/modules/video_coding/utility/frame_dropper.h" 17 #include "webrtc/system_wrappers/include/clock.h" 18 19 namespace webrtc { 20 namespace media_optimization { 21 namespace { 22 void UpdateProtectionCallback( 23 VCMProtectionMethod* selected_method, 24 uint32_t* video_rate_bps, 25 uint32_t* nack_overhead_rate_bps, 26 uint32_t* fec_overhead_rate_bps, 27 VCMProtectionCallback* video_protection_callback) { 28 FecProtectionParams delta_fec_params; 29 FecProtectionParams key_fec_params; 30 // Get the FEC code rate for Key frames (set to 0 when NA). 31 key_fec_params.fec_rate = selected_method->RequiredProtectionFactorK(); 32 33 // Get the FEC code rate for Delta frames (set to 0 when NA). 34 delta_fec_params.fec_rate = selected_method->RequiredProtectionFactorD(); 35 36 // Get the FEC-UEP protection status for Key frames: UEP on/off. 37 key_fec_params.use_uep_protection = selected_method->RequiredUepProtectionK(); 38 39 // Get the FEC-UEP protection status for Delta frames: UEP on/off. 40 delta_fec_params.use_uep_protection = 41 selected_method->RequiredUepProtectionD(); 42 43 // The RTP module currently requires the same |max_fec_frames| for both 44 // key and delta frames. 45 delta_fec_params.max_fec_frames = selected_method->MaxFramesFec(); 46 key_fec_params.max_fec_frames = selected_method->MaxFramesFec(); 47 48 // Set the FEC packet mask type. |kFecMaskBursty| is more effective for 49 // consecutive losses and little/no packet re-ordering. As we currently 50 // do not have feedback data on the degree of correlated losses and packet 51 // re-ordering, we keep default setting to |kFecMaskRandom| for now. 52 delta_fec_params.fec_mask_type = kFecMaskRandom; 53 key_fec_params.fec_mask_type = kFecMaskRandom; 54 55 // TODO(Marco): Pass FEC protection values per layer. 56 video_protection_callback->ProtectionRequest( 57 &delta_fec_params, &key_fec_params, video_rate_bps, 58 nack_overhead_rate_bps, fec_overhead_rate_bps); 59 } 60 } // namespace 61 62 struct MediaOptimization::EncodedFrameSample { 63 EncodedFrameSample(size_t size_bytes, 64 uint32_t timestamp, 65 int64_t time_complete_ms) 66 : size_bytes(size_bytes), 67 timestamp(timestamp), 68 time_complete_ms(time_complete_ms) {} 69 70 size_t size_bytes; 71 uint32_t timestamp; 72 int64_t time_complete_ms; 73 }; 74 75 MediaOptimization::MediaOptimization(Clock* clock) 76 : crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), 77 clock_(clock), 78 max_bit_rate_(0), 79 send_codec_type_(kVideoCodecUnknown), 80 codec_width_(0), 81 codec_height_(0), 82 user_frame_rate_(0), 83 frame_dropper_(new FrameDropper), 84 loss_prot_logic_( 85 new VCMLossProtectionLogic(clock_->TimeInMilliseconds())), 86 fraction_lost_(0), 87 send_statistics_zero_encode_(0), 88 max_payload_size_(1460), 89 video_target_bitrate_(0), 90 incoming_frame_rate_(0), 91 enable_qm_(false), 92 encoded_frame_samples_(), 93 avg_sent_bit_rate_bps_(0), 94 avg_sent_framerate_(0), 95 key_frame_cnt_(0), 96 delta_frame_cnt_(0), 97 content_(new VCMContentMetricsProcessing()), 98 qm_resolution_(new VCMQmResolution()), 99 last_qm_update_time_(0), 100 last_change_time_(0), 101 num_layers_(0), 102 suspension_enabled_(false), 103 video_suspended_(false), 104 suspension_threshold_bps_(0), 105 suspension_window_bps_(0) { 106 memset(send_statistics_, 0, sizeof(send_statistics_)); 107 memset(incoming_frame_times_, -1, sizeof(incoming_frame_times_)); 108 } 109 110 MediaOptimization::~MediaOptimization(void) { 111 loss_prot_logic_->Release(); 112 } 113 114 void MediaOptimization::Reset() { 115 CriticalSectionScoped lock(crit_sect_.get()); 116 SetEncodingDataInternal(kVideoCodecUnknown, 0, 0, 0, 0, 0, 0, 117 max_payload_size_); 118 memset(incoming_frame_times_, -1, sizeof(incoming_frame_times_)); 119 incoming_frame_rate_ = 0.0; 120 frame_dropper_->Reset(); 121 loss_prot_logic_->Reset(clock_->TimeInMilliseconds()); 122 frame_dropper_->SetRates(0, 0); 123 content_->Reset(); 124 qm_resolution_->Reset(); 125 loss_prot_logic_->UpdateFrameRate(incoming_frame_rate_); 126 loss_prot_logic_->Reset(clock_->TimeInMilliseconds()); 127 send_statistics_zero_encode_ = 0; 128 video_target_bitrate_ = 0; 129 codec_width_ = 0; 130 codec_height_ = 0; 131 user_frame_rate_ = 0; 132 key_frame_cnt_ = 0; 133 delta_frame_cnt_ = 0; 134 last_qm_update_time_ = 0; 135 last_change_time_ = 0; 136 encoded_frame_samples_.clear(); 137 avg_sent_bit_rate_bps_ = 0; 138 num_layers_ = 1; 139 } 140 141 void MediaOptimization::SetEncodingData(VideoCodecType send_codec_type, 142 int32_t max_bit_rate, 143 uint32_t target_bitrate, 144 uint16_t width, 145 uint16_t height, 146 uint32_t frame_rate, 147 int num_layers, 148 int32_t mtu) { 149 CriticalSectionScoped lock(crit_sect_.get()); 150 SetEncodingDataInternal(send_codec_type, max_bit_rate, frame_rate, 151 target_bitrate, width, height, num_layers, mtu); 152 } 153 154 void MediaOptimization::SetEncodingDataInternal(VideoCodecType send_codec_type, 155 int32_t max_bit_rate, 156 uint32_t frame_rate, 157 uint32_t target_bitrate, 158 uint16_t width, 159 uint16_t height, 160 int num_layers, 161 int32_t mtu) { 162 // Everything codec specific should be reset here since this means the codec 163 // has changed. If native dimension values have changed, then either user 164 // initiated change, or QM initiated change. Will be able to determine only 165 // after the processing of the first frame. 166 last_change_time_ = clock_->TimeInMilliseconds(); 167 content_->Reset(); 168 content_->UpdateFrameRate(frame_rate); 169 170 max_bit_rate_ = max_bit_rate; 171 send_codec_type_ = send_codec_type; 172 video_target_bitrate_ = target_bitrate; 173 float target_bitrate_kbps = static_cast<float>(target_bitrate) / 1000.0f; 174 loss_prot_logic_->UpdateBitRate(target_bitrate_kbps); 175 loss_prot_logic_->UpdateFrameRate(static_cast<float>(frame_rate)); 176 loss_prot_logic_->UpdateFrameSize(width, height); 177 loss_prot_logic_->UpdateNumLayers(num_layers); 178 frame_dropper_->Reset(); 179 frame_dropper_->SetRates(target_bitrate_kbps, static_cast<float>(frame_rate)); 180 user_frame_rate_ = static_cast<float>(frame_rate); 181 codec_width_ = width; 182 codec_height_ = height; 183 num_layers_ = (num_layers <= 1) ? 1 : num_layers; // Can also be zero. 184 max_payload_size_ = mtu; 185 qm_resolution_->Initialize(target_bitrate_kbps, user_frame_rate_, 186 codec_width_, codec_height_, num_layers_); 187 } 188 189 uint32_t MediaOptimization::SetTargetRates( 190 uint32_t target_bitrate, 191 uint8_t fraction_lost, 192 int64_t round_trip_time_ms, 193 VCMProtectionCallback* protection_callback, 194 VCMQMSettingsCallback* qmsettings_callback) { 195 CriticalSectionScoped lock(crit_sect_.get()); 196 VCMProtectionMethod* selected_method = loss_prot_logic_->SelectedMethod(); 197 float target_bitrate_kbps = static_cast<float>(target_bitrate) / 1000.0f; 198 loss_prot_logic_->UpdateBitRate(target_bitrate_kbps); 199 loss_prot_logic_->UpdateRtt(round_trip_time_ms); 200 201 // Get frame rate for encoder: this is the actual/sent frame rate. 202 float actual_frame_rate = SentFrameRateInternal(); 203 204 // Sanity check. 205 if (actual_frame_rate < 1.0) { 206 actual_frame_rate = 1.0; 207 } 208 209 // Update frame rate for the loss protection logic class: frame rate should 210 // be the actual/sent rate. 211 loss_prot_logic_->UpdateFrameRate(actual_frame_rate); 212 213 fraction_lost_ = fraction_lost; 214 215 // Returns the filtered packet loss, used for the protection setting. 216 // The filtered loss may be the received loss (no filter), or some 217 // filtered value (average or max window filter). 218 // Use max window filter for now. 219 FilterPacketLossMode filter_mode = kMaxFilter; 220 uint8_t packet_loss_enc = loss_prot_logic_->FilteredLoss( 221 clock_->TimeInMilliseconds(), filter_mode, fraction_lost); 222 223 // For now use the filtered loss for computing the robustness settings. 224 loss_prot_logic_->UpdateFilteredLossPr(packet_loss_enc); 225 226 // Rate cost of the protection methods. 227 float protection_overhead_rate = 0.0f; 228 229 // Update protection settings, when applicable. 230 float sent_video_rate_kbps = 0.0f; 231 if (loss_prot_logic_->SelectedType() != kNone) { 232 // Update protection method with content metrics. 233 selected_method->UpdateContentMetrics(content_->ShortTermAvgData()); 234 235 // Update method will compute the robustness settings for the given 236 // protection method and the overhead cost 237 // the protection method is set by the user via SetVideoProtection. 238 loss_prot_logic_->UpdateMethod(); 239 240 // Update protection callback with protection settings. 241 uint32_t sent_video_rate_bps = 0; 242 uint32_t sent_nack_rate_bps = 0; 243 uint32_t sent_fec_rate_bps = 0; 244 // Get the bit cost of protection method, based on the amount of 245 // overhead data actually transmitted (including headers) the last 246 // second. 247 if (protection_callback) { 248 UpdateProtectionCallback(selected_method, &sent_video_rate_bps, 249 &sent_nack_rate_bps, &sent_fec_rate_bps, 250 protection_callback); 251 } 252 uint32_t sent_total_rate_bps = 253 sent_video_rate_bps + sent_nack_rate_bps + sent_fec_rate_bps; 254 // Estimate the overhead costs of the next second as staying the same 255 // wrt the source bitrate. 256 if (sent_total_rate_bps > 0) { 257 protection_overhead_rate = 258 static_cast<float>(sent_nack_rate_bps + sent_fec_rate_bps) / 259 sent_total_rate_bps; 260 } 261 // Cap the overhead estimate to 50%. 262 if (protection_overhead_rate > 0.5) 263 protection_overhead_rate = 0.5; 264 265 // Get the effective packet loss for encoder ER when applicable. Should be 266 // passed to encoder via fraction_lost. 267 packet_loss_enc = selected_method->RequiredPacketLossER(); 268 sent_video_rate_kbps = static_cast<float>(sent_video_rate_bps) / 1000.0f; 269 } 270 271 // Source coding rate: total rate - protection overhead. 272 video_target_bitrate_ = target_bitrate * (1.0 - protection_overhead_rate); 273 274 // Cap target video bitrate to codec maximum. 275 if (max_bit_rate_ > 0 && video_target_bitrate_ > max_bit_rate_) { 276 video_target_bitrate_ = max_bit_rate_; 277 } 278 279 // Update encoding rates following protection settings. 280 float target_video_bitrate_kbps = 281 static_cast<float>(video_target_bitrate_) / 1000.0f; 282 frame_dropper_->SetRates(target_video_bitrate_kbps, incoming_frame_rate_); 283 284 if (enable_qm_ && qmsettings_callback) { 285 // Update QM with rates. 286 qm_resolution_->UpdateRates(target_video_bitrate_kbps, sent_video_rate_kbps, 287 incoming_frame_rate_, fraction_lost_); 288 // Check for QM selection. 289 bool select_qm = CheckStatusForQMchange(); 290 if (select_qm) { 291 SelectQuality(qmsettings_callback); 292 } 293 // Reset the short-term averaged content data. 294 content_->ResetShortTermAvgData(); 295 } 296 297 CheckSuspendConditions(); 298 299 return video_target_bitrate_; 300 } 301 302 void MediaOptimization::SetProtectionMethod(VCMProtectionMethodEnum method) { 303 CriticalSectionScoped lock(crit_sect_.get()); 304 loss_prot_logic_->SetMethod(method); 305 } 306 307 uint32_t MediaOptimization::InputFrameRate() { 308 CriticalSectionScoped lock(crit_sect_.get()); 309 return InputFrameRateInternal(); 310 } 311 312 uint32_t MediaOptimization::InputFrameRateInternal() { 313 ProcessIncomingFrameRate(clock_->TimeInMilliseconds()); 314 return uint32_t(incoming_frame_rate_ + 0.5f); 315 } 316 317 uint32_t MediaOptimization::SentFrameRate() { 318 CriticalSectionScoped lock(crit_sect_.get()); 319 return SentFrameRateInternal(); 320 } 321 322 uint32_t MediaOptimization::SentFrameRateInternal() { 323 PurgeOldFrameSamples(clock_->TimeInMilliseconds()); 324 UpdateSentFramerate(); 325 return avg_sent_framerate_; 326 } 327 328 uint32_t MediaOptimization::SentBitRate() { 329 CriticalSectionScoped lock(crit_sect_.get()); 330 const int64_t now_ms = clock_->TimeInMilliseconds(); 331 PurgeOldFrameSamples(now_ms); 332 UpdateSentBitrate(now_ms); 333 return avg_sent_bit_rate_bps_; 334 } 335 336 int32_t MediaOptimization::UpdateWithEncodedData( 337 const EncodedImage& encoded_image) { 338 size_t encoded_length = encoded_image._length; 339 uint32_t timestamp = encoded_image._timeStamp; 340 CriticalSectionScoped lock(crit_sect_.get()); 341 const int64_t now_ms = clock_->TimeInMilliseconds(); 342 PurgeOldFrameSamples(now_ms); 343 if (encoded_frame_samples_.size() > 0 && 344 encoded_frame_samples_.back().timestamp == timestamp) { 345 // Frames having the same timestamp are generated from the same input 346 // frame. We don't want to double count them, but only increment the 347 // size_bytes. 348 encoded_frame_samples_.back().size_bytes += encoded_length; 349 encoded_frame_samples_.back().time_complete_ms = now_ms; 350 } else { 351 encoded_frame_samples_.push_back( 352 EncodedFrameSample(encoded_length, timestamp, now_ms)); 353 } 354 UpdateSentBitrate(now_ms); 355 UpdateSentFramerate(); 356 if (encoded_length > 0) { 357 const bool delta_frame = encoded_image._frameType != kVideoFrameKey; 358 359 frame_dropper_->Fill(encoded_length, delta_frame); 360 if (max_payload_size_ > 0 && encoded_length > 0) { 361 const float min_packets_per_frame = 362 encoded_length / static_cast<float>(max_payload_size_); 363 if (delta_frame) { 364 loss_prot_logic_->UpdatePacketsPerFrame(min_packets_per_frame, 365 clock_->TimeInMilliseconds()); 366 } else { 367 loss_prot_logic_->UpdatePacketsPerFrameKey( 368 min_packets_per_frame, clock_->TimeInMilliseconds()); 369 } 370 371 if (enable_qm_) { 372 // Update quality select with encoded length. 373 qm_resolution_->UpdateEncodedSize(encoded_length); 374 } 375 } 376 if (!delta_frame && encoded_length > 0) { 377 loss_prot_logic_->UpdateKeyFrameSize(static_cast<float>(encoded_length)); 378 } 379 380 // Updating counters. 381 if (delta_frame) { 382 delta_frame_cnt_++; 383 } else { 384 key_frame_cnt_++; 385 } 386 } 387 388 return VCM_OK; 389 } 390 391 void MediaOptimization::EnableQM(bool enable) { 392 CriticalSectionScoped lock(crit_sect_.get()); 393 enable_qm_ = enable; 394 } 395 396 void MediaOptimization::EnableFrameDropper(bool enable) { 397 CriticalSectionScoped lock(crit_sect_.get()); 398 frame_dropper_->Enable(enable); 399 } 400 401 void MediaOptimization::SuspendBelowMinBitrate(int threshold_bps, 402 int window_bps) { 403 CriticalSectionScoped lock(crit_sect_.get()); 404 assert(threshold_bps > 0 && window_bps >= 0); 405 suspension_threshold_bps_ = threshold_bps; 406 suspension_window_bps_ = window_bps; 407 suspension_enabled_ = true; 408 video_suspended_ = false; 409 } 410 411 bool MediaOptimization::IsVideoSuspended() const { 412 CriticalSectionScoped lock(crit_sect_.get()); 413 return video_suspended_; 414 } 415 416 bool MediaOptimization::DropFrame() { 417 CriticalSectionScoped lock(crit_sect_.get()); 418 UpdateIncomingFrameRate(); 419 // Leak appropriate number of bytes. 420 frame_dropper_->Leak((uint32_t)(InputFrameRateInternal() + 0.5f)); 421 if (video_suspended_) { 422 return true; // Drop all frames when muted. 423 } 424 return frame_dropper_->DropFrame(); 425 } 426 427 void MediaOptimization::UpdateContentData( 428 const VideoContentMetrics* content_metrics) { 429 CriticalSectionScoped lock(crit_sect_.get()); 430 // Updating content metrics. 431 if (content_metrics == NULL) { 432 // Disable QM if metrics are NULL. 433 enable_qm_ = false; 434 qm_resolution_->Reset(); 435 } else { 436 content_->UpdateContentData(content_metrics); 437 } 438 } 439 440 void MediaOptimization::UpdateIncomingFrameRate() { 441 int64_t now = clock_->TimeInMilliseconds(); 442 if (incoming_frame_times_[0] == 0) { 443 // No shifting if this is the first time. 444 } else { 445 // Shift all times one step. 446 for (int32_t i = (kFrameCountHistorySize - 2); i >= 0; i--) { 447 incoming_frame_times_[i + 1] = incoming_frame_times_[i]; 448 } 449 } 450 incoming_frame_times_[0] = now; 451 ProcessIncomingFrameRate(now); 452 } 453 454 int32_t MediaOptimization::SelectQuality( 455 VCMQMSettingsCallback* video_qmsettings_callback) { 456 // Reset quantities for QM select. 457 qm_resolution_->ResetQM(); 458 459 // Update QM will long-term averaged content metrics. 460 qm_resolution_->UpdateContent(content_->LongTermAvgData()); 461 462 // Select quality mode. 463 VCMResolutionScale* qm = NULL; 464 int32_t ret = qm_resolution_->SelectResolution(&qm); 465 if (ret < 0) { 466 return ret; 467 } 468 469 // Check for updates to spatial/temporal modes. 470 QMUpdate(qm, video_qmsettings_callback); 471 472 // Reset all the rate and related frame counters quantities. 473 qm_resolution_->ResetRates(); 474 475 // Reset counters. 476 last_qm_update_time_ = clock_->TimeInMilliseconds(); 477 478 // Reset content metrics. 479 content_->Reset(); 480 481 return VCM_OK; 482 } 483 484 void MediaOptimization::PurgeOldFrameSamples(int64_t now_ms) { 485 while (!encoded_frame_samples_.empty()) { 486 if (now_ms - encoded_frame_samples_.front().time_complete_ms > 487 kBitrateAverageWinMs) { 488 encoded_frame_samples_.pop_front(); 489 } else { 490 break; 491 } 492 } 493 } 494 495 void MediaOptimization::UpdateSentBitrate(int64_t now_ms) { 496 if (encoded_frame_samples_.empty()) { 497 avg_sent_bit_rate_bps_ = 0; 498 return; 499 } 500 size_t framesize_sum = 0; 501 for (FrameSampleList::iterator it = encoded_frame_samples_.begin(); 502 it != encoded_frame_samples_.end(); ++it) { 503 framesize_sum += it->size_bytes; 504 } 505 float denom = static_cast<float>( 506 now_ms - encoded_frame_samples_.front().time_complete_ms); 507 if (denom >= 1.0f) { 508 avg_sent_bit_rate_bps_ = 509 static_cast<uint32_t>(framesize_sum * 8.0f * 1000.0f / denom + 0.5f); 510 } else { 511 avg_sent_bit_rate_bps_ = framesize_sum * 8; 512 } 513 } 514 515 void MediaOptimization::UpdateSentFramerate() { 516 if (encoded_frame_samples_.size() <= 1) { 517 avg_sent_framerate_ = encoded_frame_samples_.size(); 518 return; 519 } 520 int denom = encoded_frame_samples_.back().timestamp - 521 encoded_frame_samples_.front().timestamp; 522 if (denom > 0) { 523 avg_sent_framerate_ = 524 (90000 * (encoded_frame_samples_.size() - 1) + denom / 2) / denom; 525 } else { 526 avg_sent_framerate_ = encoded_frame_samples_.size(); 527 } 528 } 529 530 bool MediaOptimization::QMUpdate( 531 VCMResolutionScale* qm, 532 VCMQMSettingsCallback* video_qmsettings_callback) { 533 // Check for no change. 534 if (!qm->change_resolution_spatial && !qm->change_resolution_temporal) { 535 return false; 536 } 537 538 // Check for change in frame rate. 539 if (qm->change_resolution_temporal) { 540 incoming_frame_rate_ = qm->frame_rate; 541 // Reset frame rate estimate. 542 memset(incoming_frame_times_, -1, sizeof(incoming_frame_times_)); 543 } 544 545 // Check for change in frame size. 546 if (qm->change_resolution_spatial) { 547 codec_width_ = qm->codec_width; 548 codec_height_ = qm->codec_height; 549 } 550 551 LOG(LS_INFO) << "Media optimizer requests the video resolution to be changed " 552 "to " 553 << qm->codec_width << "x" << qm->codec_height << "@" 554 << qm->frame_rate; 555 556 // Update VPM with new target frame rate and frame size. 557 // Note: use |qm->frame_rate| instead of |_incoming_frame_rate| for updating 558 // target frame rate in VPM frame dropper. The quantity |_incoming_frame_rate| 559 // will vary/fluctuate, and since we don't want to change the state of the 560 // VPM frame dropper, unless a temporal action was selected, we use the 561 // quantity |qm->frame_rate| for updating. 562 video_qmsettings_callback->SetVideoQMSettings(qm->frame_rate, codec_width_, 563 codec_height_); 564 content_->UpdateFrameRate(qm->frame_rate); 565 qm_resolution_->UpdateCodecParameters(qm->frame_rate, codec_width_, 566 codec_height_); 567 return true; 568 } 569 570 // Check timing constraints and look for significant change in: 571 // (1) scene content, 572 // (2) target bit rate. 573 bool MediaOptimization::CheckStatusForQMchange() { 574 bool status = true; 575 576 // Check that we do not call QMSelect too often, and that we waited some time 577 // (to sample the metrics) from the event last_change_time 578 // last_change_time is the time where user changed the size/rate/frame rate 579 // (via SetEncodingData). 580 int64_t now = clock_->TimeInMilliseconds(); 581 if ((now - last_qm_update_time_) < kQmMinIntervalMs || 582 (now - last_change_time_) < kQmMinIntervalMs) { 583 status = false; 584 } 585 586 return status; 587 } 588 589 // Allowing VCM to keep track of incoming frame rate. 590 void MediaOptimization::ProcessIncomingFrameRate(int64_t now) { 591 int32_t num = 0; 592 int32_t nr_of_frames = 0; 593 for (num = 1; num < (kFrameCountHistorySize - 1); ++num) { 594 if (incoming_frame_times_[num] <= 0 || 595 // don't use data older than 2 s 596 now - incoming_frame_times_[num] > kFrameHistoryWinMs) { 597 break; 598 } else { 599 nr_of_frames++; 600 } 601 } 602 if (num > 1) { 603 const int64_t diff = 604 incoming_frame_times_[0] - incoming_frame_times_[num - 1]; 605 incoming_frame_rate_ = 0.0; // No frame rate estimate available. 606 if (diff > 0) { 607 incoming_frame_rate_ = nr_of_frames * 1000.0f / static_cast<float>(diff); 608 } 609 } 610 } 611 612 void MediaOptimization::CheckSuspendConditions() { 613 // Check conditions for SuspendBelowMinBitrate. |video_target_bitrate_| is in 614 // bps. 615 if (suspension_enabled_) { 616 if (!video_suspended_) { 617 // Check if we just went below the threshold. 618 if (video_target_bitrate_ < suspension_threshold_bps_) { 619 video_suspended_ = true; 620 } 621 } else { 622 // Video is already suspended. Check if we just went over the threshold 623 // with a margin. 624 if (video_target_bitrate_ > 625 suspension_threshold_bps_ + suspension_window_bps_) { 626 video_suspended_ = false; 627 } 628 } 629 } 630 } 631 632 } // namespace media_optimization 633 } // namespace webrtc 634