| Index: remoting/protocol/webrtc_frame_scheduler.cc
|
| diff --git a/remoting/protocol/webrtc_frame_scheduler.cc b/remoting/protocol/webrtc_frame_scheduler.cc
|
| index a9f25cb1dc1c4cdc6ba888d386f0078149cb3d73..b303cd57ba6775191578aff390de415d6fbfd404 100644
|
| --- a/remoting/protocol/webrtc_frame_scheduler.cc
|
| +++ b/remoting/protocol/webrtc_frame_scheduler.cc
|
| @@ -4,6 +4,7 @@
|
|
|
| #include "remoting/protocol/webrtc_frame_scheduler.h"
|
|
|
| +#include <algorithm>
|
| #include <memory>
|
|
|
| #include "base/logging.h"
|
| @@ -13,6 +14,19 @@
|
| namespace remoting {
|
| namespace protocol {
|
|
|
| +namespace {
|
| +
|
| +// Amount of time in microseconds after which the accumulated average is halved.
|
| +const int kAccFrameDurationHalfLifeUs = 200000;
|
| +
|
| +// Starting value on the expected duration of a frame.
|
| +const int kStartingFrameDurationUs = 100000;
|
| +
|
| +// Target quantizer at which stop the encoding top-off.
|
| +const int kTargetQuantizerForTopOff = 30;
|
| +
|
| +} // namespace
|
| +
|
| // The frame scheduler currently uses a simple polling technique
|
| // at 30 FPS to capture, encode and send frames over webrtc transport.
|
| // An improved solution will use target bitrate feedback to pace out
|
| @@ -22,7 +36,9 @@ WebRtcFrameScheduler::WebRtcFrameScheduler(
|
| std::unique_ptr<webrtc::DesktopCapturer> capturer,
|
| WebrtcTransport* webrtc_transport,
|
| std::unique_ptr<VideoEncoder> encoder)
|
| - : encode_task_runner_(encode_task_runner),
|
| + : acc_frame_duration_(
|
| + base::TimeDelta::FromMicroseconds(kAccFrameDurationHalfLifeUs)),
|
| + encode_task_runner_(encode_task_runner),
|
| capturer_(std::move(capturer)),
|
| webrtc_transport_(webrtc_transport),
|
| encoder_(std::move(encoder)),
|
| @@ -45,6 +61,10 @@ void WebRtcFrameScheduler::Start() {
|
| webrtc_transport_->video_encoder_factory()->SetKeyFrameRequestCallback(
|
| base::Bind(&WebRtcFrameScheduler::SetKeyFrameRequest,
|
| base::Unretained(this)));
|
| + // Register for target bitrate notifications.
|
| + webrtc_transport_->video_encoder_factory()->SetTargetBitrateCallback(
|
| + base::Bind(&WebRtcFrameScheduler::SetTargetBitrate,
|
| + base::Unretained(this)));
|
| capture_timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(1) / 30, this,
|
| &WebRtcFrameScheduler::CaptureNextFrame);
|
| }
|
| @@ -53,6 +73,8 @@ void WebRtcFrameScheduler::Stop() {
|
| // Clear PLI request callback.
|
| webrtc_transport_->video_encoder_factory()->SetKeyFrameRequestCallback(
|
| base::Closure());
|
| + webrtc_transport_->video_encoder_factory()->SetTargetBitrateCallback(
|
| + TargetBitrateCallback());
|
| // Cancel any pending encode.
|
| task_tracker_.TryCancelAll();
|
| capture_timer_->Stop();
|
| @@ -77,6 +99,12 @@ void WebRtcFrameScheduler::SetKeyFrameRequest() {
|
| key_frame_request_ = true;
|
| }
|
|
|
| +void WebRtcFrameScheduler::SetTargetBitrate(uint32_t target_bitrate_kbps) {
|
| + VLOG(1) << "Set Target bitrate " << target_bitrate_kbps;
|
| + base::AutoLock lock(lock_);
|
| + target_bitrate_kbps_ = target_bitrate_kbps;
|
| +}
|
| +
|
| bool WebRtcFrameScheduler::ClearAndGetKeyFrameRequest() {
|
| base::AutoLock lock(lock_);
|
| bool key_frame_request = key_frame_request_;
|
| @@ -92,8 +120,9 @@ webrtc::SharedMemory* WebRtcFrameScheduler::CreateSharedMemory(size_t size) {
|
| void WebRtcFrameScheduler::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| + base::TimeTicks captured_ticks = base::TimeTicks::Now();
|
| VLOG(1) << "Capture overhead "
|
| - << (base::TimeTicks::Now() - last_capture_ticks_).InMilliseconds();
|
| + << (captured_ticks - last_capture_started_ticks_).InMilliseconds();
|
| capture_pending_ = false;
|
|
|
| std::unique_ptr<webrtc::DesktopFrame> owned_frame(frame);
|
| @@ -103,9 +132,20 @@ void WebRtcFrameScheduler::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
|
| VLOG(1) << "Dropping captured frame since encoder is still busy";
|
| return;
|
| }
|
| - if (!frame || frame->updated_region().is_empty())
|
| +
|
| + // If unchanged and does not need top-off, return.
|
| + if (!frame || (frame->updated_region().is_empty() &&
|
| + last_quantizer_ <= kTargetQuantizerForTopOff))
|
| return;
|
|
|
| + if ((last_capture_completed_ticks_ - base::TimeTicks()).is_zero()) {
|
| + acc_frame_duration_.Reset(kStartingFrameDurationUs, captured_ticks);
|
| + } else {
|
| + base::TimeDelta duration = captured_ticks - last_capture_completed_ticks_;
|
| + acc_frame_duration_.Update(duration.InMicroseconds(), captured_ticks);
|
| + }
|
| + last_capture_completed_ticks_ = captured_ticks;
|
| +
|
| webrtc::DesktopVector dpi =
|
| frame->dpi().is_zero() ? webrtc::DesktopVector(kDefaultDpi, kDefaultDpi)
|
| : frame->dpi();
|
| @@ -120,7 +160,8 @@ void WebRtcFrameScheduler::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
|
| task_tracker_.PostTaskAndReplyWithResult(
|
| encode_task_runner_.get(), FROM_HERE,
|
| base::Bind(&WebRtcFrameScheduler::EncodeFrame, encoder_.get(),
|
| - base::Passed(std::move(owned_frame)),
|
| + base::Passed(std::move(owned_frame)), target_bitrate_kbps_,
|
| + static_cast<int64_t>(acc_frame_duration_.current()),
|
| ClearAndGetKeyFrameRequest()),
|
| base::Bind(&WebRtcFrameScheduler::OnFrameEncoded,
|
| weak_factory_.GetWeakPtr()));
|
| @@ -135,8 +176,9 @@ void WebRtcFrameScheduler::CaptureNextFrame() {
|
| }
|
| capture_pending_ = true;
|
| VLOG(1) << "Capture next frame after "
|
| - << (base::TimeTicks::Now() - last_capture_ticks_).InMilliseconds();
|
| - last_capture_ticks_ = base::TimeTicks::Now();
|
| + << (base::TimeTicks::Now() - last_capture_started_ticks_)
|
| + .InMilliseconds();
|
| + last_capture_started_ticks_ = base::TimeTicks::Now();
|
| capturer_->Capture(webrtc::DesktopRegion());
|
| }
|
|
|
| @@ -144,19 +186,23 @@ void WebRtcFrameScheduler::CaptureNextFrame() {
|
| std::unique_ptr<VideoPacket> WebRtcFrameScheduler::EncodeFrame(
|
| VideoEncoder* encoder,
|
| std::unique_ptr<webrtc::DesktopFrame> frame,
|
| + uint32_t target_bitrate_kbps,
|
| + int64_t frame_duration_us,
|
| bool key_frame_request) {
|
| uint32_t flags = 0;
|
| if (key_frame_request)
|
| flags |= VideoEncoder::REQUEST_KEY_FRAME;
|
|
|
| base::TimeTicks current = base::TimeTicks::Now();
|
| + encoder->UpdateTargetBitrate(target_bitrate_kbps);
|
| std::unique_ptr<VideoPacket> packet = encoder->Encode(*frame, flags);
|
| if (!packet)
|
| return nullptr;
|
|
|
| VLOG(1) << "Encode duration "
|
| << (base::TimeTicks::Now() - current).InMilliseconds()
|
| - << " payload size " << packet->data().size();
|
| + << " payload size " << packet->data().size() << " quantizer "
|
| + << packet->quantizer();
|
| return packet;
|
| }
|
|
|
| @@ -165,16 +211,28 @@ void WebRtcFrameScheduler::OnFrameEncoded(std::unique_ptr<VideoPacket> packet) {
|
| encode_pending_ = false;
|
| if (!packet)
|
| return;
|
| + last_quantizer_ = packet->quantizer();
|
| int64_t capture_timestamp_ms =
|
| (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds();
|
| base::TimeTicks current = base::TimeTicks::Now();
|
| + uint32_t encoded_bits = packet->data().size() * 8.0;
|
| + VLOG(1) << "frame duration " << acc_frame_duration_.current()
|
| + << " encoded bitrate (kbps) "
|
| + << (encoded_bits * 1000 / acc_frame_duration_.current());
|
| // TODO(isheriff): Investigate why first frame fails to send at times.
|
| // This gets resolved through a PLI request.
|
| webrtc_transport_->video_encoder_factory()->SendEncodedFrame(
|
| capture_timestamp_ms, std::move(packet));
|
|
|
| + // Simplistic adaptation of frame polling in the range 5 FPS to 30 FPS.
|
| + uint32_t next_sched_ms = std::max(
|
| + 33, std::min(static_cast<int>(encoded_bits / target_bitrate_kbps_), 200));
|
| VLOG(1) << "Send duration "
|
| - << (base::TimeTicks::Now() - current).InMilliseconds();
|
| + << (base::TimeTicks::Now() - current).InMilliseconds()
|
| + << "next sched " << next_sched_ms;
|
| + capture_timer_->Start(FROM_HERE,
|
| + base::TimeDelta::FromMilliseconds(next_sched_ms), this,
|
| + &WebRtcFrameScheduler::CaptureNextFrame);
|
| }
|
|
|
| } // namespace protocol
|
|
|