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