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..1d3a43e02f8d72e1d943b12495354ac4cc3ec67b 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) { |
+ LOG(ERROR) << "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()); |
- VLOG(1) << "Capture overhead " |
- << (base::TimeTicks::Now() - last_capture_ticks_).InMilliseconds(); |
+ base::TimeTicks captured_ticks = base::TimeTicks::Now(); |
+ int64_t capture_timestamp_ms = |
+ (captured_ticks - base::TimeTicks()).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,8 +160,9 @@ 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)), |
- ClearAndGetKeyFrameRequest()), |
+ base::Passed(std::move(owned_frame)), target_bitrate_kbps_, |
+ static_cast<int64_t>(acc_frame_duration_.current()), |
+ ClearAndGetKeyFrameRequest(), capture_timestamp_ms), |
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,25 @@ void WebRtcFrameScheduler::CaptureNextFrame() { |
std::unique_ptr<VideoPacket> WebRtcFrameScheduler::EncodeFrame( |
VideoEncoder* encoder, |
std::unique_ptr<webrtc::DesktopFrame> frame, |
- bool key_frame_request) { |
+ uint32_t target_bitrate_kbps, |
+ int64_t frame_duration_us, |
+ bool key_frame_request, |
+ int64_t capture_time_ms) { |
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; |
+ packet->set_capture_time_ms(capture_time_ms); |
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 +213,29 @@ void WebRtcFrameScheduler::OnFrameEncoded(std::unique_ptr<VideoPacket> packet) { |
encode_pending_ = false; |
if (!packet) |
return; |
- int64_t capture_timestamp_ms = |
- (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds(); |
+ last_quantizer_ = packet->quantizer(); |
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()); |
+ |
+ // 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)); |
// 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)); |
- |
- VLOG(1) << "Send duration " |
- << (base::TimeTicks::Now() - current).InMilliseconds(); |
+ if (webrtc_transport_->video_encoder_factory()->SendEncodedFrame( |
+ std::move(packet)) >= 0) { |
+ VLOG(1) << " Send duration " |
+ << (base::TimeTicks::Now() - current).InMilliseconds() |
+ << "next sched " << next_sched_ms; |
+ } else { |
+ LOG(ERROR) << "SendEncodedFrame() failed"; |
+ } |
+ capture_timer_->Start(FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(next_sched_ms), this, |
+ &WebRtcFrameScheduler::CaptureNextFrame); |
} |
} // namespace protocol |