video_capture_host_unittest.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 <map>
6#include <string>
7
8#include "base/bind.h"
9#include "base/file_util.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop.h"
12#include "base/process_util.h"
13#include "base/stl_util.h"
14#include "base/stringprintf.h"
15#include "content/browser/browser_thread_impl.h"
16#include "content/browser/renderer_host/media/media_stream_manager.h"
17#include "content/browser/renderer_host/media/video_capture_host.h"
18#include "content/browser/renderer_host/media/video_capture_manager.h"
19#include "content/common/media/video_capture_messages.h"
20#include "content/public/test/mock_resource_context.h"
21#include "media/audio/audio_manager.h"
22#include "media/video/capture/video_capture_types.h"
23#include "net/url_request/url_request_context.h"
24#include "testing/gmock/include/gmock/gmock.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27using ::testing::_;
28using ::testing::AtLeast;
29using ::testing::AnyNumber;
30using ::testing::DoAll;
31using ::testing::InSequence;
32using ::testing::Mock;
33using ::testing::Return;
34
35namespace content {
36
37// Id used to identify the capture session between renderer and
38// video_capture_host.
39static const int kDeviceId = 1;
40// Id of a video capture device
41static const media::VideoCaptureSessionId kTestFakeDeviceId =
42    VideoCaptureManager::kStartOpenSessionId;
43
44// Define to enable test where video is dumped to file.
45// #define DUMP_VIDEO
46
47// Define to use a real video capture device.
48// #define TEST_REAL_CAPTURE_DEVICE
49
50// Simple class used for dumping video to a file. This can be used for
51// verifying the output.
52class DumpVideo {
53 public:
54  DumpVideo() : expected_size_(0) {}
55  void StartDump(int width, int height) {
56    base::FilePath file_name = base::FilePath(base::StringPrintf(
57        FILE_PATH_LITERAL("dump_w%d_h%d.yuv"), width, height));
58    file_.reset(file_util::OpenFile(file_name, "wb"));
59    expected_size_ = width * height * 3 / 2;
60  }
61  void NewVideoFrame(const void* buffer) {
62    if (file_.get() != NULL) {
63      fwrite(buffer, expected_size_, 1, file_.get());
64    }
65  }
66
67 private:
68  file_util::ScopedFILE file_;
69  int expected_size_;
70};
71
72class MockVideoCaptureHost : public VideoCaptureHost {
73 public:
74  MockVideoCaptureHost(MediaStreamManager* manager)
75      : VideoCaptureHost(),
76        return_buffers_(false),
77        dump_video_(false),
78        manager_(manager) {}
79
80  // A list of mock methods.
81  MOCK_METHOD4(OnNewBufferCreated,
82               void(int device_id, base::SharedMemoryHandle handle,
83                    int length, int buffer_id));
84  MOCK_METHOD3(OnBufferFilled,
85               void(int device_id, int buffer_id, base::Time timestamp));
86  MOCK_METHOD2(OnStateChanged, void(int device_id, VideoCaptureState state));
87  MOCK_METHOD1(OnDeviceInfo, void(int device_id));
88
89  // Use class DumpVideo to write I420 video to file.
90  void SetDumpVideo(bool enable) {
91    dump_video_ = enable;
92  }
93
94  void SetReturnReceviedDibs(bool enable) {
95    return_buffers_ = enable;
96  }
97
98  // Return Dibs we currently have received.
99  void ReturnReceivedDibs(int device_id)  {
100    int handle = GetReceivedDib();
101    while (handle) {
102      this->OnReceiveEmptyBuffer(device_id, handle);
103      handle = GetReceivedDib();
104    }
105  }
106
107  int GetReceivedDib() {
108    if (filled_dib_.empty())
109      return 0;
110    std::map<int, base::SharedMemory*>::iterator it = filled_dib_.begin();
111    int h = it->first;
112    delete it->second;
113    filled_dib_.erase(it);
114
115    return h;
116  }
117
118 private:
119  virtual ~MockVideoCaptureHost() {
120    STLDeleteContainerPairSecondPointers(filled_dib_.begin(),
121                                         filled_dib_.end());
122  }
123
124  // This method is used to dispatch IPC messages to the renderer. We intercept
125  // these messages here and dispatch to our mock methods to verify the
126  // conversation between this object and the renderer.
127  virtual bool Send(IPC::Message* message) OVERRIDE {
128    CHECK(message);
129
130    // In this method we dispatch the messages to the according handlers as if
131    // we are the renderer.
132    bool handled = true;
133    IPC_BEGIN_MESSAGE_MAP(MockVideoCaptureHost, *message)
134      IPC_MESSAGE_HANDLER(VideoCaptureMsg_NewBuffer, OnNewBufferCreatedDispatch)
135      IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilledDispatch)
136      IPC_MESSAGE_HANDLER(VideoCaptureMsg_StateChanged, OnStateChangedDispatch)
137      IPC_MESSAGE_HANDLER(VideoCaptureMsg_DeviceInfo, OnDeviceInfoDispatch)
138      IPC_MESSAGE_UNHANDLED(handled = false)
139    IPC_END_MESSAGE_MAP()
140    EXPECT_TRUE(handled);
141
142    delete message;
143    return true;
144  }
145
146  virtual VideoCaptureManager* GetVideoCaptureManager() OVERRIDE {
147    return manager_->video_capture_manager();
148  }
149
150  // These handler methods do minimal things and delegate to the mock methods.
151  void OnNewBufferCreatedDispatch(int device_id,
152                                  base::SharedMemoryHandle handle,
153                                  int length, int buffer_id) {
154    OnNewBufferCreated(device_id, handle, length, buffer_id);
155    base::SharedMemory* dib = new base::SharedMemory(handle, false);
156    dib->Map(length);
157    filled_dib_[buffer_id] = dib;
158  }
159
160  void OnBufferFilledDispatch(int device_id, int buffer_id,
161                              base::Time timestamp) {
162    if (dump_video_) {
163      base::SharedMemory* dib = filled_dib_[buffer_id];
164      ASSERT_TRUE(dib != NULL);
165      dumper_.NewVideoFrame(dib->memory());
166    }
167
168    OnBufferFilled(device_id, buffer_id, timestamp);
169    if (return_buffers_) {
170      VideoCaptureHost::OnReceiveEmptyBuffer(device_id, buffer_id);
171    }
172  }
173
174  void OnStateChangedDispatch(int device_id, VideoCaptureState state) {
175    OnStateChanged(device_id, state);
176  }
177
178  void OnDeviceInfoDispatch(int device_id,
179                            media::VideoCaptureParams params) {
180    if (dump_video_) {
181      dumper_.StartDump(params.width, params.height);
182    }
183    OnDeviceInfo(device_id);
184  }
185
186  std::map<int, base::SharedMemory*> filled_dib_;
187  bool return_buffers_;
188  bool dump_video_;
189  DumpVideo dumper_;
190  MediaStreamManager* manager_;
191};
192
193ACTION_P(ExitMessageLoop, message_loop) {
194  message_loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
195}
196
197class VideoCaptureHostTest : public testing::Test {
198 public:
199  VideoCaptureHostTest() {}
200
201 protected:
202  virtual void SetUp() OVERRIDE {
203    // Create a message loop so VideoCaptureHostTest can use it.
204    message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO));
205
206    // MediaStreamManager must be created on the IO thread.
207    io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
208                                           message_loop_.get()));
209
210    // Create our own MediaStreamManager.
211    audio_manager_.reset(media::AudioManager::Create());
212    media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
213#ifndef TEST_REAL_CAPTURE_DEVICE
214    media_stream_manager_->UseFakeDevice();
215#endif
216
217    host_ = new MockVideoCaptureHost(media_stream_manager_.get());
218
219    // Simulate IPC channel connected.
220    host_->OnChannelConnected(base::GetCurrentProcId());
221  }
222
223  virtual void TearDown() OVERRIDE {
224    // Verifies and removes the expectations on host_ and
225    // returns true iff successful.
226    Mock::VerifyAndClearExpectations(host_);
227
228    EXPECT_CALL(*host_, OnStateChanged(kDeviceId,
229                                       VIDEO_CAPTURE_STATE_STOPPED))
230        .Times(AnyNumber());
231
232    // Simulate closing the IPC channel.
233    host_->OnChannelClosing();
234
235    // Release the reference to the mock object. The object will be destructed
236    // on message_loop_.
237    host_ = NULL;
238
239    // We need to continue running message_loop_ to complete all destructions.
240    message_loop_->RunUntilIdle();
241
242    // Delete the IO message loop.  This will cause the MediaStreamManager to be
243    // notified so it will stop its device thread and device managers.
244    message_loop_.reset();
245  }
246
247  void StartCapture() {
248    InSequence s;
249    // 1. First - get info about the new resolution
250    EXPECT_CALL(*host_, OnDeviceInfo(kDeviceId));
251
252    // 2. Change state to started
253    EXPECT_CALL(*host_, OnStateChanged(kDeviceId,
254                                       VIDEO_CAPTURE_STATE_STARTED));
255
256    // 3. Newly created buffers will arrive.
257    EXPECT_CALL(*host_, OnNewBufferCreated(kDeviceId, _, _, _))
258        .Times(AnyNumber())
259        .WillRepeatedly(Return());
260
261    // 4. First filled buffer will arrive.
262    EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _))
263        .Times(AnyNumber())
264        .WillOnce(ExitMessageLoop(message_loop_.get()));
265
266    media::VideoCaptureParams params;
267    params.width = 352;
268    params.height = 288;
269    params.frame_per_second = 30;
270    params.session_id = kTestFakeDeviceId;
271    host_->OnStartCapture(kDeviceId, params);
272    message_loop_->Run();
273  }
274
275#ifdef DUMP_VIDEO
276  void CaptureAndDumpVideo(int width, int heigt, int frame_rate) {
277    InSequence s;
278    // 1. First - get info about the new resolution
279    EXPECT_CALL(*host_, OnDeviceInfo(kDeviceId));
280
281    // 2. Change state to started
282    EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STARTED));
283
284    // 3. First filled buffer will arrive.
285    EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _))
286        .Times(AnyNumber())
287        .WillOnce(ExitMessageLoop(message_loop_.get()));
288
289    media::VideoCaptureParams params;
290    params.width = width;
291    params.height = heigt;
292    params.frame_per_second = frame_rate;
293    params.session_id = kTestFakeDeviceId;
294    host_->SetDumpVideo(true);
295    host_->OnStartCapture(kDeviceId, params);
296    message_loop_->Run();
297  }
298#endif
299
300  void StopCapture() {
301    EXPECT_CALL(*host_, OnStateChanged(kDeviceId,
302                                       VIDEO_CAPTURE_STATE_STOPPED))
303        .WillOnce(ExitMessageLoop(message_loop_.get()));
304
305    host_->OnStopCapture(kDeviceId);
306    host_->SetReturnReceviedDibs(true);
307    host_->ReturnReceivedDibs(kDeviceId);
308
309    message_loop_->Run();
310
311    host_->SetReturnReceviedDibs(false);
312    // Expect the VideoCaptureDevice has been stopped
313    EXPECT_EQ(0u, host_->entries_.size());
314  }
315
316  void NotifyPacketReady() {
317    EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _))
318        .Times(AnyNumber())
319        .WillOnce(ExitMessageLoop(message_loop_.get()))
320        .RetiresOnSaturation();
321    message_loop_->Run();
322  }
323
324  void ReturnReceivedPackets() {
325    host_->ReturnReceivedDibs(kDeviceId);
326  }
327
328  void SimulateError() {
329    // Expect a change state to error state  sent through IPC.
330    EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ERROR))
331        .Times(1);
332    VideoCaptureControllerID id(kDeviceId);
333    host_->OnError(id);
334    // Wait for the error callback.
335    message_loop_->RunUntilIdle();
336  }
337
338  scoped_refptr<MockVideoCaptureHost> host_;
339
340 private:
341  scoped_ptr<MessageLoop> message_loop_;
342  scoped_ptr<BrowserThreadImpl> io_thread_;
343  scoped_ptr<media::AudioManager> audio_manager_;
344  scoped_ptr<MediaStreamManager> media_stream_manager_;
345
346  DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest);
347};
348
349TEST_F(VideoCaptureHostTest, StartCapture) {
350  StartCapture();
351}
352
353TEST_F(VideoCaptureHostTest, StartCapturePlayStop) {
354  StartCapture();
355  NotifyPacketReady();
356  NotifyPacketReady();
357  ReturnReceivedPackets();
358  StopCapture();
359}
360
361TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) {
362  StartCapture();
363  SimulateError();
364  StopCapture();
365}
366
367TEST_F(VideoCaptureHostTest, StartCaptureError) {
368  EXPECT_CALL(*host_, OnStateChanged(kDeviceId,
369                                     VIDEO_CAPTURE_STATE_STOPPED))
370      .Times(0);
371  StartCapture();
372  NotifyPacketReady();
373  SimulateError();
374  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
375}
376
377#ifdef DUMP_VIDEO
378TEST_F(VideoCaptureHostTest, CaptureAndDumpVideoVga) {
379  CaptureAndDumpVideo(640, 480, 30);
380}
381TEST_F(VideoCaptureHostTest, CaptureAndDump720P) {
382  CaptureAndDumpVideo(1280, 720, 30);
383}
384#endif
385
386}  // namespace content
387