1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/video_scheduler.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/message_loop/message_loop_proxy.h"
14#include "base/stl_util.h"
15#include "base/sys_info.h"
16#include "base/time/time.h"
17#include "remoting/proto/control.pb.h"
18#include "remoting/proto/internal.pb.h"
19#include "remoting/proto/video.pb.h"
20#include "remoting/protocol/cursor_shape_stub.h"
21#include "remoting/protocol/message_decoder.h"
22#include "remoting/protocol/util.h"
23#include "remoting/protocol/video_stub.h"
24#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
25#include "third_party/webrtc/modules/desktop_capture/mouse_cursor_shape.h"
26#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
27
28namespace remoting {
29
30// Maximum number of frames that can be processed simultaneously.
31// TODO(hclam): Move this value to CaptureScheduler.
32static const int kMaxPendingFrames = 2;
33
34VideoScheduler::VideoScheduler(
35    scoped_refptr<base::SingleThreadTaskRunner> capture_task_runner,
36    scoped_refptr<base::SingleThreadTaskRunner> encode_task_runner,
37    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
38    scoped_ptr<webrtc::ScreenCapturer> capturer,
39    scoped_ptr<VideoEncoder> encoder,
40    protocol::CursorShapeStub* cursor_stub,
41    protocol::VideoStub* video_stub)
42    : capture_task_runner_(capture_task_runner),
43      encode_task_runner_(encode_task_runner),
44      network_task_runner_(network_task_runner),
45      capturer_(capturer.Pass()),
46      encoder_(encoder.Pass()),
47      cursor_stub_(cursor_stub),
48      video_stub_(video_stub),
49      pending_frames_(0),
50      capture_pending_(false),
51      did_skip_frame_(false),
52      is_paused_(false),
53      sequence_number_(0) {
54  DCHECK(network_task_runner_->BelongsToCurrentThread());
55  DCHECK(capturer_);
56  DCHECK(encoder_);
57  DCHECK(cursor_stub_);
58  DCHECK(video_stub_);
59}
60
61// Public methods --------------------------------------------------------------
62
63webrtc::SharedMemory* VideoScheduler::CreateSharedMemory(size_t size) {
64  return NULL;
65}
66
67void VideoScheduler::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
68  DCHECK(capture_task_runner_->BelongsToCurrentThread());
69
70  capture_pending_ = false;
71
72  scoped_ptr<webrtc::DesktopFrame> owned_frame(frame);
73
74  if (frame) {
75    scheduler_.RecordCaptureTime(
76        base::TimeDelta::FromMilliseconds(frame->capture_time_ms()));
77  }
78
79  encode_task_runner_->PostTask(
80      FROM_HERE, base::Bind(&VideoScheduler::EncodeFrame, this,
81                            base::Passed(&owned_frame), sequence_number_));
82
83  // If a frame was skipped, try to capture it again.
84  if (did_skip_frame_) {
85    capture_task_runner_->PostTask(
86        FROM_HERE, base::Bind(&VideoScheduler::CaptureNextFrame, this));
87  }
88}
89
90void VideoScheduler::OnCursorShapeChanged(
91    webrtc::MouseCursorShape* cursor_shape) {
92  DCHECK(capture_task_runner_->BelongsToCurrentThread());
93
94  scoped_ptr<webrtc::MouseCursorShape> owned_cursor(cursor_shape);
95
96  // Do nothing if the scheduler is being stopped.
97  if (!capturer_)
98    return;
99
100  scoped_ptr<protocol::CursorShapeInfo> cursor_proto(
101      new protocol::CursorShapeInfo());
102  cursor_proto->set_width(cursor_shape->size.width());
103  cursor_proto->set_height(cursor_shape->size.height());
104  cursor_proto->set_hotspot_x(cursor_shape->hotspot.x());
105  cursor_proto->set_hotspot_y(cursor_shape->hotspot.y());
106  cursor_proto->set_data(cursor_shape->data);
107
108  network_task_runner_->PostTask(
109      FROM_HERE, base::Bind(&VideoScheduler::SendCursorShape, this,
110                            base::Passed(&cursor_proto)));
111}
112
113void VideoScheduler::Start() {
114  DCHECK(network_task_runner_->BelongsToCurrentThread());
115
116  capture_task_runner_->PostTask(
117      FROM_HERE, base::Bind(&VideoScheduler::StartOnCaptureThread, this));
118}
119
120void VideoScheduler::Stop() {
121  DCHECK(network_task_runner_->BelongsToCurrentThread());
122
123  // Clear stubs to prevent further updates reaching the client.
124  cursor_stub_ = NULL;
125  video_stub_ = NULL;
126
127  capture_task_runner_->PostTask(FROM_HERE,
128      base::Bind(&VideoScheduler::StopOnCaptureThread, this));
129}
130
131void VideoScheduler::Pause(bool pause) {
132  if (!capture_task_runner_->BelongsToCurrentThread()) {
133    DCHECK(network_task_runner_->BelongsToCurrentThread());
134    capture_task_runner_->PostTask(
135        FROM_HERE, base::Bind(&VideoScheduler::Pause, this, pause));
136    return;
137  }
138
139  if (is_paused_ != pause) {
140    is_paused_ = pause;
141
142    // Restart captures if we're resuming and there are none scheduled.
143    if (!is_paused_ && capture_timer_ && !capture_timer_->IsRunning())
144      CaptureNextFrame();
145  }
146}
147
148void VideoScheduler::UpdateSequenceNumber(int64 sequence_number) {
149  if (!capture_task_runner_->BelongsToCurrentThread()) {
150    DCHECK(network_task_runner_->BelongsToCurrentThread());
151    capture_task_runner_->PostTask(
152        FROM_HERE, base::Bind(&VideoScheduler::UpdateSequenceNumber,
153                              this, sequence_number));
154    return;
155  }
156
157  sequence_number_ = sequence_number;
158}
159
160// Private methods -----------------------------------------------------------
161
162VideoScheduler::~VideoScheduler() {
163}
164
165// Capturer thread -------------------------------------------------------------
166
167void VideoScheduler::StartOnCaptureThread() {
168  DCHECK(capture_task_runner_->BelongsToCurrentThread());
169  DCHECK(!capture_timer_);
170
171  // Start the capturer and let it notify us if cursor shape changes.
172  capturer_->SetMouseShapeObserver(this);
173  capturer_->Start(this);
174
175  capture_timer_.reset(new base::OneShotTimer<VideoScheduler>());
176
177  // Capture first frame immedately.
178  CaptureNextFrame();
179}
180
181void VideoScheduler::StopOnCaptureThread() {
182  DCHECK(capture_task_runner_->BelongsToCurrentThread());
183
184  // This doesn't deleted already captured frames, so encoder can keep using the
185  // frames that were captured previously.
186  capturer_.reset();
187
188  // |capture_timer_| must be destroyed on the thread on which it is used.
189  capture_timer_.reset();
190}
191
192void VideoScheduler::ScheduleNextCapture() {
193  DCHECK(capture_task_runner_->BelongsToCurrentThread());
194
195  capture_timer_->Start(FROM_HERE,
196                        scheduler_.NextCaptureDelay(),
197                        this,
198                        &VideoScheduler::CaptureNextFrame);
199}
200
201void VideoScheduler::CaptureNextFrame() {
202  DCHECK(capture_task_runner_->BelongsToCurrentThread());
203
204  // If we are stopping (|capturer_| is NULL), or paused, then don't capture.
205  if (!capturer_ || is_paused_)
206    return;
207
208  // Make sure we have at most two outstanding recordings. We can simply return
209  // if we can't make a capture now, the next capture will be started by the
210  // end of an encode operation.
211  if (pending_frames_ >= kMaxPendingFrames || capture_pending_) {
212    did_skip_frame_ = true;
213    return;
214  }
215
216  did_skip_frame_ = false;
217
218  // At this point we are going to perform one capture so save the current time.
219  pending_frames_++;
220  DCHECK_LE(pending_frames_, kMaxPendingFrames);
221
222  // Before doing a capture schedule for the next one.
223  ScheduleNextCapture();
224
225  capture_pending_ = true;
226
227  // And finally perform one capture.
228  capturer_->Capture(webrtc::DesktopRegion());
229}
230
231void VideoScheduler::FrameCaptureCompleted() {
232  DCHECK(capture_task_runner_->BelongsToCurrentThread());
233
234  // Decrement the pending capture count.
235  pending_frames_--;
236  DCHECK_GE(pending_frames_, 0);
237
238  // If we've skipped a frame capture because too we had too many captures
239  // pending then schedule one now.
240  if (did_skip_frame_)
241    CaptureNextFrame();
242}
243
244// Network thread --------------------------------------------------------------
245
246void VideoScheduler::SendVideoPacket(scoped_ptr<VideoPacket> packet) {
247  DCHECK(network_task_runner_->BelongsToCurrentThread());
248
249  if (!video_stub_)
250    return;
251
252  base::Closure callback;
253  if ((packet->flags() & VideoPacket::LAST_PARTITION) != 0)
254    callback = base::Bind(&VideoScheduler::VideoFrameSentCallback, this);
255
256  video_stub_->ProcessVideoPacket(packet.Pass(), callback);
257}
258
259void VideoScheduler::VideoFrameSentCallback() {
260  DCHECK(network_task_runner_->BelongsToCurrentThread());
261
262  if (!video_stub_)
263    return;
264
265  capture_task_runner_->PostTask(
266      FROM_HERE, base::Bind(&VideoScheduler::FrameCaptureCompleted, this));
267}
268
269void VideoScheduler::SendCursorShape(
270    scoped_ptr<protocol::CursorShapeInfo> cursor_shape) {
271  DCHECK(network_task_runner_->BelongsToCurrentThread());
272
273  if (!cursor_stub_)
274    return;
275
276  cursor_stub_->SetCursorShape(*cursor_shape);
277}
278
279// Encoder thread --------------------------------------------------------------
280
281void VideoScheduler::EncodeFrame(
282    scoped_ptr<webrtc::DesktopFrame> frame,
283    int64 sequence_number) {
284  DCHECK(encode_task_runner_->BelongsToCurrentThread());
285
286  // If there is nothing to encode then send an empty keep-alive packet.
287  if (!frame || frame->updated_region().is_empty()) {
288    scoped_ptr<VideoPacket> packet(new VideoPacket());
289    packet->set_flags(VideoPacket::LAST_PARTITION);
290    packet->set_client_sequence_number(sequence_number);
291    network_task_runner_->PostTask(
292        FROM_HERE, base::Bind(&VideoScheduler::SendVideoPacket, this,
293                              base::Passed(&packet)));
294    capture_task_runner_->DeleteSoon(FROM_HERE, frame.release());
295    return;
296  }
297
298  encoder_->Encode(
299      frame.get(), base::Bind(&VideoScheduler::EncodedDataAvailableCallback,
300                              this, sequence_number));
301  capture_task_runner_->DeleteSoon(FROM_HERE, frame.release());
302}
303
304void VideoScheduler::EncodedDataAvailableCallback(
305    int64 sequence_number,
306    scoped_ptr<VideoPacket> packet) {
307  DCHECK(encode_task_runner_->BelongsToCurrentThread());
308
309  packet->set_client_sequence_number(sequence_number);
310
311  bool last = (packet->flags() & VideoPacket::LAST_PACKET) != 0;
312  if (last) {
313    scheduler_.RecordEncodeTime(
314        base::TimeDelta::FromMilliseconds(packet->encode_time_ms()));
315  }
316
317  network_task_runner_->PostTask(
318      FROM_HERE, base::Bind(&VideoScheduler::SendVideoPacket, this,
319                            base::Passed(&packet)));
320}
321
322}  // namespace remoting
323