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