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