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/command_line.h" 10 #include "base/file_util.h" 11 #include "base/files/scoped_file.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/run_loop.h" 15 #include "base/stl_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "content/browser/browser_thread_impl.h" 18 #include "content/browser/renderer_host/media/media_stream_manager.h" 19 #include "content/browser/renderer_host/media/media_stream_requester.h" 20 #include "content/browser/renderer_host/media/media_stream_ui_proxy.h" 21 #include "content/browser/renderer_host/media/video_capture_host.h" 22 #include "content/browser/renderer_host/media/video_capture_manager.h" 23 #include "content/common/media/video_capture_messages.h" 24 #include "content/public/common/content_switches.h" 25 #include "content/public/test/mock_resource_context.h" 26 #include "content/public/test/test_browser_context.h" 27 #include "content/public/test/test_browser_thread_bundle.h" 28 #include "content/test/test_content_browser_client.h" 29 #include "media/audio/audio_manager.h" 30 #include "media/base/media_switches.h" 31 #include "media/base/video_frame.h" 32 #include "media/video/capture/video_capture_types.h" 33 #include "net/url_request/url_request_context.h" 34 #include "testing/gmock/include/gmock/gmock.h" 35 #include "testing/gtest/include/gtest/gtest.h" 36 37 using ::testing::_; 38 using ::testing::AtLeast; 39 using ::testing::AnyNumber; 40 using ::testing::DoAll; 41 using ::testing::InSequence; 42 using ::testing::Mock; 43 using ::testing::Return; 44 using ::testing::SaveArg; 45 using ::testing::StrictMock; 46 47 namespace content { 48 49 // Id used to identify the capture session between renderer and 50 // video_capture_host. This is an arbitrary value. 51 static const int kDeviceId = 555; 52 53 // Define to enable test where video is dumped to file. 54 // #define DUMP_VIDEO 55 56 // Define to use a real video capture device. 57 // #define TEST_REAL_CAPTURE_DEVICE 58 59 // Simple class used for dumping video to a file. This can be used for 60 // verifying the output. 61 class DumpVideo { 62 public: 63 DumpVideo() : expected_size_(0) {} 64 void StartDump(int width, int height) { 65 base::FilePath file_name = base::FilePath(base::StringPrintf( 66 FILE_PATH_LITERAL("dump_w%d_h%d.yuv"), width, height)); 67 file_.reset(base::OpenFile(file_name, "wb")); 68 expected_size_ = media::VideoFrame::AllocationSize( 69 media::VideoFrame::I420, gfx::Size(width, height)); 70 } 71 void NewVideoFrame(const void* buffer) { 72 if (file_.get() != NULL) { 73 ASSERT_EQ(1U, fwrite(buffer, expected_size_, 1, file_.get())); 74 } 75 } 76 77 private: 78 base::ScopedFILE file_; 79 int expected_size_; 80 }; 81 82 class MockMediaStreamRequester : public MediaStreamRequester { 83 public: 84 MockMediaStreamRequester() {} 85 virtual ~MockMediaStreamRequester() {} 86 87 // MediaStreamRequester implementation. 88 MOCK_METHOD5(StreamGenerated, 89 void(int render_view_id, 90 int page_request_id, 91 const std::string& label, 92 const StreamDeviceInfoArray& audio_devices, 93 const StreamDeviceInfoArray& video_devices)); 94 MOCK_METHOD3(StreamGenerationFailed, 95 void(int render_view_id, 96 int page_request_id, 97 content::MediaStreamRequestResult result)); 98 MOCK_METHOD3(DeviceStopped, void(int render_view_id, 99 const std::string& label, 100 const StreamDeviceInfo& device)); 101 MOCK_METHOD4(DevicesEnumerated, void(int render_view_id, 102 int page_request_id, 103 const std::string& label, 104 const StreamDeviceInfoArray& devices)); 105 MOCK_METHOD4(DeviceOpened, void(int render_view_id, 106 int page_request_id, 107 const std::string& label, 108 const StreamDeviceInfo& device_info)); 109 110 private: 111 DISALLOW_COPY_AND_ASSIGN(MockMediaStreamRequester); 112 }; 113 114 class MockVideoCaptureHost : public VideoCaptureHost { 115 public: 116 MockVideoCaptureHost(MediaStreamManager* manager) 117 : VideoCaptureHost(manager), 118 return_buffers_(false), 119 dump_video_(false) {} 120 121 // A list of mock methods. 122 MOCK_METHOD4(OnNewBufferCreated, 123 void(int device_id, 124 base::SharedMemoryHandle handle, 125 int length, 126 int buffer_id)); 127 MOCK_METHOD2(OnBufferFreed, 128 void(int device_id, int buffer_id)); 129 MOCK_METHOD4(OnBufferFilled, 130 void(int device_id, 131 int buffer_id, 132 const media::VideoCaptureFormat& format, 133 base::TimeTicks timestamp)); 134 MOCK_METHOD5(OnMailboxBufferFilled, 135 void(int device_id, 136 int buffer_id, 137 const gpu::MailboxHolder& mailbox_holder, 138 const media::VideoCaptureFormat& format, 139 base::TimeTicks timestamp)); 140 MOCK_METHOD2(OnStateChanged, void(int device_id, VideoCaptureState state)); 141 142 // Use class DumpVideo to write I420 video to file. 143 void SetDumpVideo(bool enable) { 144 dump_video_ = enable; 145 } 146 147 void SetReturnReceivedDibs(bool enable) { 148 return_buffers_ = enable; 149 } 150 151 // Return Dibs we currently have received. 152 void ReturnReceivedDibs(int device_id) { 153 int handle = GetReceivedDib(); 154 while (handle) { 155 this->OnReceiveEmptyBuffer(device_id, handle, std::vector<uint32>()); 156 handle = GetReceivedDib(); 157 } 158 } 159 160 int GetReceivedDib() { 161 if (filled_dib_.empty()) 162 return 0; 163 std::map<int, base::SharedMemory*>::iterator it = filled_dib_.begin(); 164 int h = it->first; 165 delete it->second; 166 filled_dib_.erase(it); 167 168 return h; 169 } 170 171 private: 172 virtual ~MockVideoCaptureHost() { 173 STLDeleteContainerPairSecondPointers(filled_dib_.begin(), 174 filled_dib_.end()); 175 } 176 177 // This method is used to dispatch IPC messages to the renderer. We intercept 178 // these messages here and dispatch to our mock methods to verify the 179 // conversation between this object and the renderer. 180 virtual bool Send(IPC::Message* message) OVERRIDE { 181 CHECK(message); 182 183 // In this method we dispatch the messages to the according handlers as if 184 // we are the renderer. 185 bool handled = true; 186 IPC_BEGIN_MESSAGE_MAP(MockVideoCaptureHost, *message) 187 IPC_MESSAGE_HANDLER(VideoCaptureMsg_NewBuffer, OnNewBufferCreatedDispatch) 188 IPC_MESSAGE_HANDLER(VideoCaptureMsg_FreeBuffer, OnBufferFreedDispatch) 189 IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilledDispatch) 190 IPC_MESSAGE_HANDLER(VideoCaptureMsg_MailboxBufferReady, 191 OnMailboxBufferFilledDispatch) 192 IPC_MESSAGE_HANDLER(VideoCaptureMsg_StateChanged, OnStateChangedDispatch) 193 IPC_MESSAGE_UNHANDLED(handled = false) 194 IPC_END_MESSAGE_MAP() 195 EXPECT_TRUE(handled); 196 197 delete message; 198 return true; 199 } 200 201 // These handler methods do minimal things and delegate to the mock methods. 202 void OnNewBufferCreatedDispatch(int device_id, 203 base::SharedMemoryHandle handle, 204 uint32 length, 205 int buffer_id) { 206 OnNewBufferCreated(device_id, handle, length, buffer_id); 207 base::SharedMemory* dib = new base::SharedMemory(handle, false); 208 dib->Map(length); 209 filled_dib_[buffer_id] = dib; 210 } 211 212 void OnBufferFreedDispatch(int device_id, int buffer_id) { 213 OnBufferFreed(device_id, buffer_id); 214 215 std::map<int, base::SharedMemory*>::iterator it = 216 filled_dib_.find(buffer_id); 217 ASSERT_TRUE(it != filled_dib_.end()); 218 delete it->second; 219 filled_dib_.erase(it); 220 } 221 222 void OnBufferFilledDispatch(int device_id, 223 int buffer_id, 224 const media::VideoCaptureFormat& frame_format, 225 base::TimeTicks timestamp) { 226 base::SharedMemory* dib = filled_dib_[buffer_id]; 227 ASSERT_TRUE(dib != NULL); 228 if (dump_video_) { 229 if (!format_.IsValid()) { 230 dumper_.StartDump(frame_format.frame_size.width(), 231 frame_format.frame_size.height()); 232 format_ = frame_format; 233 } 234 ASSERT_EQ(format_.frame_size.width(), frame_format.frame_size.width()) 235 << "Dump format does not handle variable resolution."; 236 ASSERT_EQ(format_.frame_size.height(), frame_format.frame_size.height()) 237 << "Dump format does not handle variable resolution."; 238 dumper_.NewVideoFrame(dib->memory()); 239 } 240 241 OnBufferFilled(device_id, buffer_id, frame_format, timestamp); 242 if (return_buffers_) { 243 VideoCaptureHost::OnReceiveEmptyBuffer( 244 device_id, buffer_id, std::vector<uint32>()); 245 } 246 } 247 248 void OnMailboxBufferFilledDispatch(int device_id, 249 int buffer_id, 250 const gpu::MailboxHolder& mailbox_holder, 251 const media::VideoCaptureFormat& format, 252 base::TimeTicks timestamp) { 253 OnMailboxBufferFilled( 254 device_id, buffer_id, mailbox_holder, format, timestamp); 255 if (return_buffers_) { 256 VideoCaptureHost::OnReceiveEmptyBuffer( 257 device_id, buffer_id, std::vector<uint32>()); 258 } 259 } 260 261 void OnStateChangedDispatch(int device_id, VideoCaptureState state) { 262 OnStateChanged(device_id, state); 263 } 264 265 std::map<int, base::SharedMemory*> filled_dib_; 266 bool return_buffers_; 267 bool dump_video_; 268 media::VideoCaptureFormat format_; 269 DumpVideo dumper_; 270 }; 271 272 ACTION_P2(ExitMessageLoop, message_loop, quit_closure) { 273 message_loop->PostTask(FROM_HERE, quit_closure); 274 } 275 276 // This is an integration test of VideoCaptureHost in conjunction with 277 // MediaStreamManager, VideoCaptureManager, VideoCaptureController, and 278 // VideoCaptureDevice. 279 class VideoCaptureHostTest : public testing::Test { 280 public: 281 VideoCaptureHostTest() 282 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), 283 message_loop_(base::MessageLoopProxy::current()), 284 opened_session_id_(kInvalidMediaCaptureSessionId) {} 285 286 virtual void SetUp() OVERRIDE { 287 SetBrowserClientForTesting(&browser_client_); 288 // Create our own MediaStreamManager. 289 audio_manager_.reset(media::AudioManager::CreateForTesting()); 290 #ifndef TEST_REAL_CAPTURE_DEVICE 291 base::CommandLine::ForCurrentProcess()->AppendSwitch( 292 switches::kUseFakeDeviceForMediaStream); 293 #endif 294 media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); 295 media_stream_manager_->UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy>()); 296 297 // Create a Host and connect it to a simulated IPC channel. 298 host_ = new MockVideoCaptureHost(media_stream_manager_.get()); 299 host_->OnChannelConnected(base::GetCurrentProcId()); 300 301 OpenSession(); 302 } 303 304 virtual void TearDown() OVERRIDE { 305 // Verifies and removes the expectations on host_ and 306 // returns true iff successful. 307 Mock::VerifyAndClearExpectations(host_.get()); 308 EXPECT_EQ(0u, host_->entries_.size()); 309 310 CloseSession(); 311 312 // Simulate closing the IPC sender. 313 host_->OnChannelClosing(); 314 315 // Release the reference to the mock object. The object will be destructed 316 // on the current message loop. 317 host_ = NULL; 318 } 319 320 void OpenSession() { 321 const int render_process_id = 1; 322 const int render_view_id = 1; 323 const int page_request_id = 1; 324 const GURL security_origin("http://test.com"); 325 326 ASSERT_TRUE(opened_device_label_.empty()); 327 328 // Enumerate video devices. 329 StreamDeviceInfoArray devices; 330 { 331 base::RunLoop run_loop; 332 std::string label = media_stream_manager_->EnumerateDevices( 333 &stream_requester_, 334 render_process_id, 335 render_view_id, 336 browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(), 337 page_request_id, 338 MEDIA_DEVICE_VIDEO_CAPTURE, 339 security_origin, 340 true); 341 EXPECT_CALL(stream_requester_, DevicesEnumerated(render_view_id, 342 page_request_id, 343 label, 344 _)) 345 .Times(1).WillOnce( 346 DoAll(ExitMessageLoop(message_loop_, run_loop.QuitClosure()), 347 SaveArg<3>(&devices))); 348 run_loop.Run(); 349 Mock::VerifyAndClearExpectations(&stream_requester_); 350 media_stream_manager_->CancelRequest(label); 351 } 352 ASSERT_FALSE(devices.empty()); 353 ASSERT_EQ(StreamDeviceInfo::kNoId, devices[0].session_id); 354 355 // Open the first device. 356 { 357 base::RunLoop run_loop; 358 StreamDeviceInfo opened_device; 359 media_stream_manager_->OpenDevice( 360 &stream_requester_, 361 render_process_id, 362 render_view_id, 363 browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(), 364 page_request_id, 365 devices[0].device.id, 366 MEDIA_DEVICE_VIDEO_CAPTURE, 367 security_origin); 368 EXPECT_CALL(stream_requester_, DeviceOpened(render_view_id, 369 page_request_id, 370 _, 371 _)) 372 .Times(1).WillOnce( 373 DoAll(ExitMessageLoop(message_loop_, run_loop.QuitClosure()), 374 SaveArg<2>(&opened_device_label_), 375 SaveArg<3>(&opened_device))); 376 run_loop.Run(); 377 Mock::VerifyAndClearExpectations(&stream_requester_); 378 ASSERT_NE(StreamDeviceInfo::kNoId, opened_device.session_id); 379 opened_session_id_ = opened_device.session_id; 380 } 381 } 382 383 void CloseSession() { 384 if (opened_device_label_.empty()) 385 return; 386 media_stream_manager_->CancelRequest(opened_device_label_); 387 opened_device_label_.clear(); 388 opened_session_id_ = kInvalidMediaCaptureSessionId; 389 } 390 391 protected: 392 void StartCapture() { 393 EXPECT_CALL(*host_, OnNewBufferCreated(kDeviceId, _, _, _)) 394 .Times(AnyNumber()).WillRepeatedly(Return()); 395 396 base::RunLoop run_loop; 397 EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _, _)) 398 .Times(AnyNumber()).WillOnce(ExitMessageLoop( 399 message_loop_, run_loop.QuitClosure())); 400 401 media::VideoCaptureParams params; 402 params.requested_format = media::VideoCaptureFormat( 403 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); 404 host_->OnStartCapture(kDeviceId, opened_session_id_, params); 405 run_loop.Run(); 406 } 407 408 void StartStopCapture() { 409 // Quickly start and then stop capture, without giving much chance for 410 // asynchronous start operations to complete. 411 InSequence s; 412 base::RunLoop run_loop; 413 EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)); 414 media::VideoCaptureParams params; 415 params.requested_format = media::VideoCaptureFormat( 416 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); 417 host_->OnStartCapture(kDeviceId, opened_session_id_, params); 418 host_->OnStopCapture(kDeviceId); 419 run_loop.RunUntilIdle(); 420 } 421 422 #ifdef DUMP_VIDEO 423 void CaptureAndDumpVideo(int width, int height, int frame_rate) { 424 InSequence s; 425 EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _)) 426 .Times(AnyNumber()).WillRepeatedly(Return()); 427 428 base::RunLoop run_loop; 429 EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _, _)) 430 .Times(AnyNumber()) 431 .WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure())); 432 433 media::VideoCaptureParams params; 434 params.requested_format = 435 media::VideoCaptureFormat(gfx::Size(width, height), frame_rate); 436 host_->SetDumpVideo(true); 437 host_->OnStartCapture(kDeviceId, opened_session_id_, params); 438 run_loop.Run(); 439 } 440 #endif 441 442 void StopCapture() { 443 base::RunLoop run_loop; 444 EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)) 445 .WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure())); 446 447 host_->OnStopCapture(kDeviceId); 448 host_->SetReturnReceivedDibs(true); 449 host_->ReturnReceivedDibs(kDeviceId); 450 451 run_loop.Run(); 452 453 host_->SetReturnReceivedDibs(false); 454 // Expect the VideoCaptureDevice has been stopped 455 EXPECT_EQ(0u, host_->entries_.size()); 456 } 457 458 void NotifyPacketReady() { 459 base::RunLoop run_loop; 460 EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _, _)) 461 .Times(AnyNumber()).WillOnce(ExitMessageLoop( 462 message_loop_, run_loop.QuitClosure())) 463 .RetiresOnSaturation(); 464 run_loop.Run(); 465 } 466 467 void ReturnReceivedPackets() { 468 host_->ReturnReceivedDibs(kDeviceId); 469 } 470 471 void SimulateError() { 472 // Expect a change state to error state sent through IPC. 473 EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ERROR)) 474 .Times(1); 475 VideoCaptureControllerID id(kDeviceId); 476 host_->OnError(id); 477 // Wait for the error callback. 478 base::RunLoop().RunUntilIdle(); 479 } 480 481 scoped_refptr<MockVideoCaptureHost> host_; 482 483 private: 484 StrictMock<MockMediaStreamRequester> stream_requester_; 485 scoped_ptr<media::AudioManager> audio_manager_; 486 scoped_ptr<MediaStreamManager> media_stream_manager_; 487 content::TestBrowserThreadBundle thread_bundle_; 488 content::TestBrowserContext browser_context_; 489 content::TestContentBrowserClient browser_client_; 490 scoped_refptr<base::MessageLoopProxy> message_loop_; 491 int opened_session_id_; 492 std::string opened_device_label_; 493 494 DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest); 495 }; 496 497 TEST_F(VideoCaptureHostTest, CloseSessionWithoutStopping) { 498 StartCapture(); 499 500 // When the session is closed via the stream without stopping capture, the 501 // ENDED event is sent. 502 EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ENDED)) 503 .Times(1); 504 CloseSession(); 505 base::RunLoop().RunUntilIdle(); 506 } 507 508 TEST_F(VideoCaptureHostTest, StopWhileStartPending) { 509 StartStopCapture(); 510 } 511 512 TEST_F(VideoCaptureHostTest, StartCapturePlayStop) { 513 StartCapture(); 514 NotifyPacketReady(); 515 NotifyPacketReady(); 516 ReturnReceivedPackets(); 517 StopCapture(); 518 } 519 520 TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) { 521 StartCapture(); 522 SimulateError(); 523 StopCapture(); 524 } 525 526 TEST_F(VideoCaptureHostTest, StartCaptureError) { 527 EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)) 528 .Times(0); 529 StartCapture(); 530 NotifyPacketReady(); 531 SimulateError(); 532 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); 533 } 534 535 #ifdef DUMP_VIDEO 536 TEST_F(VideoCaptureHostTest, CaptureAndDumpVideoVga) { 537 CaptureAndDumpVideo(640, 480, 30); 538 } 539 TEST_F(VideoCaptureHostTest, CaptureAndDump720P) { 540 CaptureAndDumpVideo(1280, 720, 30); 541 } 542 #endif 543 544 } // namespace content 545