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