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 "base/bind.h"
8#include "base/message_loop/message_loop.h"
9#include "base/run_loop.h"
10#include "base/single_thread_task_runner.h"
11#include "remoting/base/auto_thread.h"
12#include "remoting/base/auto_thread_task_runner.h"
13#include "remoting/codec/video_encoder.h"
14#include "remoting/codec/video_encoder_verbatim.h"
15#include "remoting/host/fake_desktop_capturer.h"
16#include "remoting/host/fake_mouse_cursor_monitor.h"
17#include "remoting/host/host_mock_objects.h"
18#include "remoting/proto/control.pb.h"
19#include "remoting/proto/video.pb.h"
20#include "remoting/protocol/protocol_mock_objects.h"
21#include "testing/gmock/include/gmock/gmock.h"
22#include "testing/gtest/include/gtest/gtest.h"
23#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
24#include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h"
25#include "third_party/webrtc/modules/desktop_capture/screen_capturer_mock_objects.h"
26
27using ::remoting::protocol::MockClientStub;
28using ::remoting::protocol::MockVideoStub;
29
30using ::testing::_;
31using ::testing::AtLeast;
32using ::testing::AnyNumber;
33using ::testing::DeleteArg;
34using ::testing::DoAll;
35using ::testing::Expectation;
36using ::testing::InSequence;
37using ::testing::InvokeWithoutArgs;
38using ::testing::Return;
39using ::testing::ReturnRef;
40using ::testing::SaveArg;
41
42namespace remoting {
43
44namespace {
45
46ACTION(FinishEncode) {
47  scoped_ptr<VideoPacket> packet(new VideoPacket());
48  return packet.release();
49}
50
51ACTION(FinishSend) {
52  arg1.Run();
53}
54
55}  // namespace
56
57static const int kWidth = 640;
58static const int kHeight = 480;
59static const int kCursorWidth = 64;
60static const int kCursorHeight = 32;
61static const int kHotspotX = 11;
62static const int kHotspotY = 12;
63
64class MockVideoEncoder : public VideoEncoder {
65 public:
66  MockVideoEncoder() {}
67  virtual ~MockVideoEncoder() {}
68
69  scoped_ptr<VideoPacket> Encode(
70      const webrtc::DesktopFrame& frame) {
71    return scoped_ptr<VideoPacket>(EncodePtr(frame));
72  }
73  MOCK_METHOD1(EncodePtr, VideoPacket*(const webrtc::DesktopFrame& frame));
74
75 private:
76  DISALLOW_COPY_AND_ASSIGN(MockVideoEncoder);
77};
78
79class ThreadCheckVideoEncoder : public VideoEncoderVerbatim {
80 public:
81  ThreadCheckVideoEncoder(
82      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
83      : task_runner_(task_runner) {
84  }
85  virtual ~ThreadCheckVideoEncoder() {
86    EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
87  }
88
89 private:
90  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
91
92  DISALLOW_COPY_AND_ASSIGN(ThreadCheckVideoEncoder);
93};
94
95class ThreadCheckDesktopCapturer : public FakeDesktopCapturer {
96 public:
97  ThreadCheckDesktopCapturer(
98      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
99      : task_runner_(task_runner) {
100  }
101  virtual ~ThreadCheckDesktopCapturer() {
102    EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
103  }
104
105 private:
106  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
107
108  DISALLOW_COPY_AND_ASSIGN(ThreadCheckDesktopCapturer);
109};
110
111class ThreadCheckMouseCursorMonitor : public FakeMouseCursorMonitor {
112 public:
113  ThreadCheckMouseCursorMonitor(
114      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
115      : task_runner_(task_runner) {
116  }
117  virtual ~ThreadCheckMouseCursorMonitor() {
118    EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
119  }
120
121 private:
122  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
123
124  DISALLOW_COPY_AND_ASSIGN(ThreadCheckMouseCursorMonitor);
125};
126
127class VideoSchedulerTest : public testing::Test {
128 public:
129  VideoSchedulerTest();
130
131  virtual void SetUp() OVERRIDE;
132  virtual void TearDown() OVERRIDE;
133
134  void StartVideoScheduler(
135      scoped_ptr<webrtc::DesktopCapturer> capturer,
136      scoped_ptr<VideoEncoder> encoder,
137      scoped_ptr<webrtc::MouseCursorMonitor> mouse_monitor);
138  void StopVideoScheduler();
139
140  // webrtc::DesktopCapturer mocks.
141  void OnCapturerStart(webrtc::DesktopCapturer::Callback* callback);
142  void OnCaptureFrame(const webrtc::DesktopRegion& region);
143
144  // webrtc::MouseCursorMonitor mocks.
145  void OnMouseCursorMonitorInit(
146      webrtc::MouseCursorMonitor::Callback* callback,
147      webrtc::MouseCursorMonitor::Mode mode);
148  void OnCaptureMouse();
149  void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape);
150
151 protected:
152  base::MessageLoop message_loop_;
153  base::RunLoop run_loop_;
154  scoped_refptr<AutoThreadTaskRunner> capture_task_runner_;
155  scoped_refptr<AutoThreadTaskRunner> encode_task_runner_;
156  scoped_refptr<AutoThreadTaskRunner> main_task_runner_;
157  scoped_refptr<VideoScheduler> scheduler_;
158
159  MockClientStub client_stub_;
160  MockVideoStub video_stub_;
161
162  // Points to the callback passed to webrtc::DesktopCapturer::Start().
163  webrtc::DesktopCapturer::Callback* capturer_callback_;
164
165  // Points to the callback passed to webrtc::MouseCursor::Init().
166  webrtc::MouseCursorMonitor::Callback* mouse_monitor_callback_;
167
168 private:
169  DISALLOW_COPY_AND_ASSIGN(VideoSchedulerTest);
170};
171
172VideoSchedulerTest::VideoSchedulerTest()
173    : capturer_callback_(NULL),
174      mouse_monitor_callback_(NULL) {
175}
176
177void VideoSchedulerTest::SetUp() {
178  main_task_runner_ = new AutoThreadTaskRunner(
179      message_loop_.message_loop_proxy(), run_loop_.QuitClosure());
180  capture_task_runner_ = main_task_runner_;
181  encode_task_runner_ = main_task_runner_;
182}
183
184void VideoSchedulerTest::TearDown() {
185  // Release the task runners, so that the test can quit.
186  capture_task_runner_ = NULL;
187  encode_task_runner_ = NULL;
188  main_task_runner_ = NULL;
189
190  // Run the MessageLoop until everything has torn down.
191  run_loop_.Run();
192}
193
194void VideoSchedulerTest::StartVideoScheduler(
195    scoped_ptr<webrtc::DesktopCapturer> capturer,
196    scoped_ptr<VideoEncoder> encoder,
197    scoped_ptr<webrtc::MouseCursorMonitor> mouse_monitor) {
198  scheduler_ = new VideoScheduler(
199      capture_task_runner_,
200      encode_task_runner_,
201      main_task_runner_,
202      capturer.Pass(),
203      mouse_monitor.Pass(),
204      encoder.Pass(),
205      &client_stub_,
206      &video_stub_);
207  scheduler_->Start();
208}
209
210void VideoSchedulerTest::StopVideoScheduler() {
211  scheduler_->Stop();
212  scheduler_ = NULL;
213}
214
215void VideoSchedulerTest::OnCapturerStart(
216    webrtc::DesktopCapturer::Callback* callback) {
217  EXPECT_FALSE(capturer_callback_);
218  EXPECT_TRUE(callback);
219
220  capturer_callback_ = callback;
221}
222
223void VideoSchedulerTest::OnCaptureFrame(const webrtc::DesktopRegion& region) {
224  scoped_ptr<webrtc::DesktopFrame> frame(
225      new webrtc::BasicDesktopFrame(webrtc::DesktopSize(kWidth, kHeight)));
226  frame->mutable_updated_region()->SetRect(
227      webrtc::DesktopRect::MakeXYWH(0, 0, 10, 10));
228  capturer_callback_->OnCaptureCompleted(frame.release());
229}
230
231void VideoSchedulerTest::OnCaptureMouse() {
232  EXPECT_TRUE(mouse_monitor_callback_);
233
234  scoped_ptr<webrtc::MouseCursor> mouse_cursor(
235      new webrtc::MouseCursor(
236          new webrtc::BasicDesktopFrame(
237              webrtc::DesktopSize(kCursorWidth, kCursorHeight)),
238          webrtc::DesktopVector(kHotspotX, kHotspotY)));
239
240  mouse_monitor_callback_->OnMouseCursor(mouse_cursor.release());
241}
242
243void VideoSchedulerTest::OnMouseCursorMonitorInit(
244    webrtc::MouseCursorMonitor::Callback* callback,
245    webrtc::MouseCursorMonitor::Mode mode) {
246  EXPECT_FALSE(mouse_monitor_callback_);
247  EXPECT_TRUE(callback);
248
249  mouse_monitor_callback_ = callback;
250}
251
252void VideoSchedulerTest::SetCursorShape(
253    const protocol::CursorShapeInfo& cursor_shape) {
254  EXPECT_TRUE(cursor_shape.has_width());
255  EXPECT_EQ(kCursorWidth, cursor_shape.width());
256  EXPECT_TRUE(cursor_shape.has_height());
257  EXPECT_EQ(kCursorHeight, cursor_shape.height());
258  EXPECT_TRUE(cursor_shape.has_hotspot_x());
259  EXPECT_EQ(kHotspotX, cursor_shape.hotspot_x());
260  EXPECT_TRUE(cursor_shape.has_hotspot_y());
261  EXPECT_EQ(kHotspotY, cursor_shape.hotspot_y());
262  EXPECT_TRUE(cursor_shape.has_data());
263  EXPECT_EQ(kCursorWidth * kCursorHeight * webrtc::DesktopFrame::kBytesPerPixel,
264            static_cast<int>(cursor_shape.data().size()));
265}
266
267// This test mocks capturer, encoder and network layer to simulate one capture
268// cycle. When the first encoded packet is submitted to the network
269// VideoScheduler is instructed to come to a complete stop. We expect the stop
270// sequence to be executed successfully.
271TEST_F(VideoSchedulerTest, StartAndStop) {
272  scoped_ptr<webrtc::MockScreenCapturer> capturer(
273      new webrtc::MockScreenCapturer());
274  scoped_ptr<MockMouseCursorMonitor> cursor_monitor(
275      new MockMouseCursorMonitor());
276
277  {
278    InSequence s;
279
280    EXPECT_CALL(*cursor_monitor, Init(_, _))
281        .WillOnce(
282            Invoke(this, &VideoSchedulerTest::OnMouseCursorMonitorInit));
283
284    EXPECT_CALL(*cursor_monitor, Capture())
285        .WillRepeatedly(Invoke(this, &VideoSchedulerTest::OnCaptureMouse));
286  }
287
288  Expectation capturer_start =
289      EXPECT_CALL(*capturer, Start(_))
290          .WillOnce(Invoke(this, &VideoSchedulerTest::OnCapturerStart));
291
292  // First the capturer is called.
293  Expectation capturer_capture = EXPECT_CALL(*capturer, Capture(_))
294      .After(capturer_start)
295      .WillRepeatedly(Invoke(this, &VideoSchedulerTest::OnCaptureFrame));
296
297  scoped_ptr<MockVideoEncoder> encoder(new MockVideoEncoder());
298
299  // Expect the encoder be called.
300  EXPECT_CALL(*encoder, EncodePtr(_))
301      .WillRepeatedly(FinishEncode());
302
303  // By default delete the arguments when ProcessVideoPacket is received.
304  EXPECT_CALL(video_stub_, ProcessVideoPacketPtr(_, _))
305      .WillRepeatedly(FinishSend());
306
307  // When the first ProcessVideoPacket is received we stop the VideoScheduler.
308  EXPECT_CALL(video_stub_, ProcessVideoPacketPtr(_, _))
309      .WillOnce(DoAll(
310          FinishSend(),
311          InvokeWithoutArgs(this, &VideoSchedulerTest::StopVideoScheduler)))
312      .RetiresOnSaturation();
313
314  EXPECT_CALL(client_stub_, SetCursorShape(_))
315      .WillOnce(Invoke(this, &VideoSchedulerTest::SetCursorShape));
316
317  // Start video frame capture.
318  scoped_ptr<webrtc::MouseCursorMonitor> mouse_cursor_monitor(
319      new FakeMouseCursorMonitor());
320  StartVideoScheduler(capturer.PassAs<webrtc::DesktopCapturer>(),
321                      encoder.PassAs<VideoEncoder>(),
322                      cursor_monitor.PassAs<webrtc::MouseCursorMonitor>());
323
324  // Run until there are no more pending tasks from the VideoScheduler.
325  // Otherwise, a lingering frame capture might attempt to trigger a capturer
326  // expectation action and crash.
327  base::RunLoop().RunUntilIdle();
328}
329
330// Verify that the capturer, encoder and mouse monitor are torn down on the
331// correct threads.
332TEST_F(VideoSchedulerTest, DeleteOnThreads) {
333  capture_task_runner_ = AutoThread::Create("capture", main_task_runner_);
334  encode_task_runner_ = AutoThread::Create("encode", main_task_runner_);
335
336  scoped_ptr<webrtc::DesktopCapturer> capturer(
337      new ThreadCheckDesktopCapturer(capture_task_runner_));
338  scoped_ptr<VideoEncoder> encoder(
339      new ThreadCheckVideoEncoder(encode_task_runner_));
340  scoped_ptr<webrtc::MouseCursorMonitor> mouse_cursor_monitor(
341      new ThreadCheckMouseCursorMonitor(capture_task_runner_));
342
343  // Start and stop the scheduler, so it will tear down the screen capturer,
344  // video encoder and mouse monitor.
345  StartVideoScheduler(capturer.Pass(), encoder.Pass(),
346                      mouse_cursor_monitor.Pass());
347  StopVideoScheduler();
348}
349
350}  // namespace remoting
351