|  | // Copyright 2017 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "remoting/codec/frame_processing_time_estimator.h" | 
|  |  | 
|  | #include <algorithm> | 
|  |  | 
|  | #include "base/check.h" | 
|  | #include "remoting/base/constants.h" | 
|  |  | 
|  | namespace remoting { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // We tracks the frame information in last 6 seconds. | 
|  | static constexpr int kWindowSizeInSeconds = 6; | 
|  |  | 
|  | // A key-frame is assumed to be generated roughly every 3 seconds, though the | 
|  | // accurate frequency is dependent on host/client software versions, the encoder | 
|  | // being used, and the quality of the network. | 
|  | static constexpr int kKeyFrameWindowSize = kWindowSizeInSeconds / 3; | 
|  |  | 
|  | // The count of delta frames we are tracking. | 
|  | static constexpr int kDeltaFrameWindowSize = | 
|  | kTargetFrameRate * kWindowSizeInSeconds - kKeyFrameWindowSize; | 
|  |  | 
|  | // The count of bandwidth estimates we are tracking. | 
|  | static constexpr int kBandwidthEstimateWindowSize = | 
|  | kTargetFrameRate * kWindowSizeInSeconds; | 
|  |  | 
|  | // The size of the circular_deque<TimeTicks> we are using to track the time | 
|  | // interval between frames. | 
|  | static constexpr int kFrameFinishTicksCount = kBandwidthEstimateWindowSize; | 
|  |  | 
|  | base::TimeDelta CalculateEstimatedTransitTime(int size, int kbps) { | 
|  | return base::TimeDelta::FromMicroseconds(size * 1000 * 8 / kbps); | 
|  | } | 
|  |  | 
|  | // Uses the |time| to estimate the frame rate, and round the result in ceiling. | 
|  | // May return values over |kTargetFrameRate|. | 
|  | int CalculateEstimatedFrameRate(base::TimeDelta time) { | 
|  | if (time.is_zero()) { | 
|  | return kTargetFrameRate; | 
|  | } else { | 
|  | int64_t us = time.InMicroseconds(); | 
|  | return (base::Time::kMicrosecondsPerSecond + us - 1) / us; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | FrameProcessingTimeEstimator::FrameProcessingTimeEstimator() | 
|  | : delta_frame_processing_us_(kDeltaFrameWindowSize), | 
|  | delta_frame_size_(kDeltaFrameWindowSize), | 
|  | key_frame_processing_us_(kKeyFrameWindowSize), | 
|  | key_frame_size_(kKeyFrameWindowSize), | 
|  | frame_finish_ticks_(), | 
|  | bandwidth_kbps_(kBandwidthEstimateWindowSize) { | 
|  | frame_finish_ticks_.reserve(kFrameFinishTicksCount); | 
|  | } | 
|  |  | 
|  | FrameProcessingTimeEstimator::~FrameProcessingTimeEstimator() = default; | 
|  |  | 
|  | void FrameProcessingTimeEstimator::StartFrame() { | 
|  | start_time_ = Now(); | 
|  | } | 
|  |  | 
|  | void FrameProcessingTimeEstimator::FinishFrame( | 
|  | const WebrtcVideoEncoder::EncodedFrame& frame) { | 
|  | DCHECK(!start_time_.is_null()); | 
|  | base::TimeTicks now = Now(); | 
|  | if (frame_finish_ticks_.size() == kFrameFinishTicksCount) { | 
|  | frame_finish_ticks_.pop_front(); | 
|  | } | 
|  | frame_finish_ticks_.push_back(now); | 
|  | DCHECK(frame_finish_ticks_.size() <= kFrameFinishTicksCount); | 
|  | if (frame.key_frame) { | 
|  | key_frame_processing_us_.Record((now - start_time_).InMicroseconds()); | 
|  | key_frame_size_.Record(frame.data.length()); | 
|  | key_frame_count_++; | 
|  | } else { | 
|  | delta_frame_processing_us_.Record((now - start_time_).InMicroseconds()); | 
|  | delta_frame_size_.Record(frame.data.length()); | 
|  | delta_frame_count_++; | 
|  | } | 
|  | start_time_ = base::TimeTicks(); | 
|  | } | 
|  |  | 
|  | void FrameProcessingTimeEstimator::SetBandwidthKbps(int bandwidth_kbps) { | 
|  | if (bandwidth_kbps >= 0) { | 
|  | bandwidth_kbps_.Record(bandwidth_kbps); | 
|  | } | 
|  | } | 
|  |  | 
|  | base::TimeDelta FrameProcessingTimeEstimator::EstimatedProcessingTime( | 
|  | bool key_frame) const { | 
|  | // Avoid returning 0 if there are no records for delta-frames. | 
|  | if ((key_frame && !key_frame_processing_us_.IsEmpty()) || | 
|  | delta_frame_processing_us_.IsEmpty()) { | 
|  | return base::TimeDelta::FromMicroseconds( | 
|  | key_frame_processing_us_.Average()); | 
|  | } | 
|  | return base::TimeDelta::FromMicroseconds( | 
|  | delta_frame_processing_us_.Average()); | 
|  | } | 
|  |  | 
|  | base::TimeDelta FrameProcessingTimeEstimator::EstimatedTransitTime( | 
|  | bool key_frame) const { | 
|  | if (bandwidth_kbps_.IsEmpty()) { | 
|  | // To avoid unnecessary complexity in WebrtcFrameSchedulerSimple, we return | 
|  | // a fairly large value (1 minute) here. So WebrtcFrameSchedulerSimple does | 
|  | // not need to handle the overflow issue caused by returning | 
|  | // TimeDelta::Max(). | 
|  | return base::TimeDelta::FromMinutes(1); | 
|  | } | 
|  | // Avoid returning 0 if there are no records for delta-frames. | 
|  | if ((key_frame && !key_frame_size_.IsEmpty()) || | 
|  | delta_frame_size_.IsEmpty()) { | 
|  | return CalculateEstimatedTransitTime( | 
|  | key_frame_size_.Average(), AverageBandwidthKbps()); | 
|  | } | 
|  | return CalculateEstimatedTransitTime( | 
|  | delta_frame_size_.Average(), AverageBandwidthKbps()); | 
|  | } | 
|  |  | 
|  | int FrameProcessingTimeEstimator::AverageBandwidthKbps() const { | 
|  | return bandwidth_kbps_.Average(); | 
|  | } | 
|  |  | 
|  | int FrameProcessingTimeEstimator::EstimatedFrameSize() const { | 
|  | if (delta_frame_count_ + key_frame_count_ == 0) { | 
|  | return 0; | 
|  | } | 
|  | double key_frame_rate = key_frame_count_; | 
|  | key_frame_rate /= (delta_frame_count_ + key_frame_count_); | 
|  | return key_frame_rate * key_frame_size_.Average() + | 
|  | (1 - key_frame_rate) * delta_frame_size_.Average(); | 
|  | } | 
|  |  | 
|  | base::TimeDelta FrameProcessingTimeEstimator::EstimatedProcessingTime() const { | 
|  | if (delta_frame_count_ + key_frame_count_ == 0) { | 
|  | return base::TimeDelta(); | 
|  | } | 
|  | double key_frame_rate = key_frame_count_; | 
|  | key_frame_rate /= (delta_frame_count_ + key_frame_count_); | 
|  | return base::TimeDelta::FromMicroseconds( | 
|  | key_frame_rate * key_frame_processing_us_.Average() + | 
|  | (1 - key_frame_rate) * delta_frame_processing_us_.Average()); | 
|  | } | 
|  |  | 
|  | base::TimeDelta FrameProcessingTimeEstimator::EstimatedTransitTime() const { | 
|  | if (bandwidth_kbps_.IsEmpty()) { | 
|  | return base::TimeDelta::FromMinutes(1); | 
|  | } | 
|  | return CalculateEstimatedTransitTime( | 
|  | EstimatedFrameSize(), AverageBandwidthKbps()); | 
|  | } | 
|  |  | 
|  | base::TimeDelta FrameProcessingTimeEstimator:: | 
|  | RecentAverageFrameInterval() const { | 
|  | if (frame_finish_ticks_.size() < 2) { | 
|  | return base::TimeDelta(); | 
|  | } | 
|  |  | 
|  | return (frame_finish_ticks_.back() - frame_finish_ticks_.front()) / | 
|  | (frame_finish_ticks_.size() - 1); | 
|  | } | 
|  |  | 
|  | int FrameProcessingTimeEstimator::RecentFrameRate() const { | 
|  | return std::min(kTargetFrameRate, | 
|  | CalculateEstimatedFrameRate(RecentAverageFrameInterval())); | 
|  | } | 
|  |  | 
|  | int FrameProcessingTimeEstimator::PredictedFrameRate() const { | 
|  | return std::min({ | 
|  | kTargetFrameRate, | 
|  | CalculateEstimatedFrameRate(EstimatedProcessingTime()), | 
|  | CalculateEstimatedFrameRate(EstimatedTransitTime()) | 
|  | }); | 
|  | } | 
|  |  | 
|  | int FrameProcessingTimeEstimator::EstimatedFrameRate() const { | 
|  | return std::min(RecentFrameRate(), PredictedFrameRate()); | 
|  | } | 
|  |  | 
|  | base::TimeTicks FrameProcessingTimeEstimator::Now() const { | 
|  | return base::TimeTicks::Now(); | 
|  | } | 
|  |  | 
|  | }  // namespace remoting |