blob: 59a0519289f0e028ed7d7c6de00d03dd3d69a36a [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/protocol/webrtc_frame_scheduler_constant_rate.h"
#include <algorithm>
#include "base/logging.h"
#include "base/system/sys_info.h"
#include "base/time/time.h"
namespace remoting::protocol {
namespace {
base::TimeDelta GetPostTaskAdjustment() {
int proccessor_count = base::SysInfo::NumberOfProcessors();
if (proccessor_count < 16) {
// Don't change the scheduler timing on these machines as we don't want to
// overload the machine.
return base::Milliseconds(0);
}
// We've observed the encoding rate in the client as being a couple of frames
// lower than the target. By adjusting the capture rate by ~2ms, the host will
// generate frames at, or slightly above, the target frame rate. If a value of
// 1ms is used, then the host will generate frames at, or slightly below, the
// target. They higher of the two was chosen for better performance on high-
// CPU machines.
return base::Milliseconds(2);
}
} // namespace
WebrtcFrameSchedulerConstantRate::WebrtcFrameSchedulerConstantRate()
: post_task_adjustment_(GetPostTaskAdjustment()) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
WebrtcFrameSchedulerConstantRate::~WebrtcFrameSchedulerConstantRate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void WebrtcFrameSchedulerConstantRate::Start(
const base::RepeatingClosure& capture_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
capture_callback_ = capture_callback;
}
void WebrtcFrameSchedulerConstantRate::Pause(bool pause) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
paused_ = pause;
if (paused_) {
capture_timer_.Stop();
} else {
ScheduleNextFrame();
}
}
void WebrtcFrameSchedulerConstantRate::OnFrameCaptured(
const webrtc::DesktopFrame* frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_pending_);
frame_pending_ = false;
ScheduleNextFrame();
}
void WebrtcFrameSchedulerConstantRate::SetMaxFramerateFps(int max_framerate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GE(max_framerate, 0);
max_framerate_fps_ = std::min(max_framerate, 1000);
if (max_framerate_fps_ > 0) {
// Calculate the interval used to schedule each frame capture and account
// for the time used when posting tasks (~1-2ms per frame). We also set a
// floor at 1ms as some of our dependencies have problems when frames are
// delivered with the same timestamp.
capture_interval_ =
std::max(base::Hertz(max_framerate_fps_) - post_task_adjustment_,
base::Milliseconds(1));
} else {
capture_interval_ = {};
}
ScheduleNextFrame();
}
void WebrtcFrameSchedulerConstantRate::BoostCaptureRate(
base::TimeDelta capture_interval,
base::TimeDelta duration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Clamp |boost_capture_interval_| as the capture pipeline starts acting weird
// when we try to capture at sub-millisecond intervals.
boost_capture_interval_ = std::max(capture_interval, base::Milliseconds(1));
boost_window_ = base::TimeTicks::Now() + duration;
ScheduleNextFrame();
}
void WebrtcFrameSchedulerConstantRate::ScheduleNextFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::TimeTicks now = base::TimeTicks::Now();
if (paused_) {
VLOG(0) << "Not scheduling capture because stream is paused.";
return;
}
if (!capture_callback_) {
VLOG(0) << "Not scheduling capture because callback is not provided.";
return;
}
if (frame_pending_) {
// This might be logged every time the capture takes more time than the
// polling period. To avoid spamming, only log if the capture has been
// pending for an unreasonable length of time.
DCHECK(!last_capture_started_time_.is_null());
if (now - last_capture_started_time_ > base::Seconds(1)) {
// Log this as an error, because a capture should never be pending for
// this length of time.
LOG(ERROR) << "Not scheduling capture because a capture is pending.";
}
return;
}
if (max_framerate_fps_ == 0) {
VLOG(0) << "Not scheduling capture because framerate is set to 0.";
return;
}
base::TimeDelta delay;
if (!last_capture_started_time_.is_null()) {
auto capture_interval = capture_interval_;
// Use the boosted capture interval if we are within |boost_window_|.
if (!boost_window_.is_null()) {
if (boost_window_ > now) {
capture_interval = boost_capture_interval_;
} else {
boost_window_ = {};
}
}
base::TimeTicks target_capture_time =
last_capture_started_time_ + capture_interval;
// Captures should be scheduled at least 1ms apart, otherwise WebRTC's video
// stream logic will complain about non-increasing frame timestamps, which
// can affect some unittests.
delay = std::max(target_capture_time - now, base::Milliseconds(1));
}
capture_timer_.Start(FROM_HERE, delay, this,
&WebrtcFrameSchedulerConstantRate::CaptureNextFrame);
}
void WebrtcFrameSchedulerConstantRate::CaptureNextFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!frame_pending_);
last_capture_started_time_ = base::TimeTicks::Now();
frame_pending_ = true;
capture_callback_.Run();
}
void WebrtcFrameSchedulerConstantRate::SetPostTaskAdjustmentForTest(
base::TimeDelta post_task_adjustment) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
post_task_adjustment_ = post_task_adjustment;
}
} // namespace remoting::protocol