| // 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/logging.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 |