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/video/capture/video_capture_types.h" 24 #include "net/url_request/url_request_context.h" 25 #include "testing/gmock/include/gmock/gmock.h" 26 #include "testing/gtest/include/gtest/gtest.h" 27 28 using ::testing::_; 29 using ::testing::AtLeast; 30 using ::testing::AnyNumber; 31 using ::testing::DoAll; 32 using ::testing::InSequence; 33 using ::testing::Mock; 34 using ::testing::Return; 35 36 namespace content { 37 38 // Id used to identify the capture session between renderer and 39 // video_capture_host. 40 static const int kDeviceId = 1; 41 // Id of a video capture device 42 static const media::VideoCaptureSessionId kTestFakeDeviceId = 43 VideoCaptureManager::kStartOpenSessionId; 44 45 // Define to enable test where video is dumped to file. 46 // #define DUMP_VIDEO 47 48 // Define to use a real video capture device. 49 // #define TEST_REAL_CAPTURE_DEVICE 50 51 // Simple class used for dumping video to a file. This can be used for 52 // verifying the output. 53 class DumpVideo { 54 public: 55 DumpVideo() : expected_size_(0) {} 56 void StartDump(int width, int height) { 57 base::FilePath file_name = base::FilePath(base::StringPrintf( 58 FILE_PATH_LITERAL("dump_w%d_h%d.yuv"), width, height)); 59 file_.reset(file_util::OpenFile(file_name, "wb")); 60 expected_size_ = width * height * 3 / 2; 61 } 62 void NewVideoFrame(const void* buffer) { 63 if (file_.get() != NULL) { 64 fwrite(buffer, expected_size_, 1, file_.get()); 65 } 66 } 67 68 private: 69 file_util::ScopedFILE file_; 70 int expected_size_; 71 }; 72 73 class MockVideoCaptureHost : public VideoCaptureHost { 74 public: 75 MockVideoCaptureHost(MediaStreamManager* manager) 76 : VideoCaptureHost(manager), 77 return_buffers_(false), 78 dump_video_(false) {} 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 // These handler methods do minimal things and delegate to the mock methods. 147 void OnNewBufferCreatedDispatch(int device_id, 148 base::SharedMemoryHandle handle, 149 int length, int buffer_id) { 150 OnNewBufferCreated(device_id, handle, length, buffer_id); 151 base::SharedMemory* dib = new base::SharedMemory(handle, false); 152 dib->Map(length); 153 filled_dib_[buffer_id] = dib; 154 } 155 156 void OnBufferFilledDispatch(int device_id, int buffer_id, 157 base::Time timestamp) { 158 if (dump_video_) { 159 base::SharedMemory* dib = filled_dib_[buffer_id]; 160 ASSERT_TRUE(dib != NULL); 161 dumper_.NewVideoFrame(dib->memory()); 162 } 163 164 OnBufferFilled(device_id, buffer_id, timestamp); 165 if (return_buffers_) { 166 VideoCaptureHost::OnReceiveEmptyBuffer(device_id, buffer_id); 167 } 168 } 169 170 void OnStateChangedDispatch(int device_id, VideoCaptureState state) { 171 OnStateChanged(device_id, state); 172 } 173 174 void OnDeviceInfoDispatch(int device_id, 175 media::VideoCaptureParams params) { 176 if (dump_video_) { 177 dumper_.StartDump(params.width, params.height); 178 } 179 OnDeviceInfo(device_id); 180 } 181 182 std::map<int, base::SharedMemory*> filled_dib_; 183 bool return_buffers_; 184 bool dump_video_; 185 DumpVideo dumper_; 186 }; 187 188 ACTION_P2(ExitMessageLoop, message_loop, quit_closure) { 189 message_loop->PostTask(FROM_HERE, quit_closure); 190 } 191 192 class VideoCaptureHostTest : public testing::Test { 193 public: 194 VideoCaptureHostTest() 195 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), 196 message_loop_(base::MessageLoopProxy::current()) { 197 // Create our own MediaStreamManager. 198 audio_manager_.reset(media::AudioManager::Create()); 199 media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); 200 #ifndef TEST_REAL_CAPTURE_DEVICE 201 media_stream_manager_->UseFakeDevice(); 202 #endif 203 204 host_ = new MockVideoCaptureHost(media_stream_manager_.get()); 205 206 // Simulate IPC channel connected. 207 host_->OnChannelConnected(base::GetCurrentProcId()); 208 } 209 210 virtual ~VideoCaptureHostTest() { 211 // Verifies and removes the expectations on host_ and 212 // returns true iff successful. 213 Mock::VerifyAndClearExpectations(host_.get()); 214 215 EXPECT_CALL(*host_.get(), 216 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)) 217 .Times(AnyNumber()); 218 219 // Simulate closing the IPC channel. 220 host_->OnChannelClosing(); 221 222 // Release the reference to the mock object. The object will be destructed 223 // on the current message loop. 224 host_ = NULL; 225 226 media_stream_manager_->WillDestroyCurrentMessageLoop(); 227 } 228 229 protected: 230 void StartCapture() { 231 InSequence s; 232 // 1. First - get info about the new resolution 233 EXPECT_CALL(*host_.get(), OnDeviceInfo(kDeviceId)); 234 235 // 2. Change state to started 236 EXPECT_CALL(*host_.get(), 237 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STARTED)); 238 239 // 3. Newly created buffers will arrive. 240 EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _)) 241 .Times(AnyNumber()).WillRepeatedly(Return()); 242 243 // 4. First filled buffer will arrive. 244 base::RunLoop run_loop; 245 EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId, _, _)) 246 .Times(AnyNumber()).WillOnce(ExitMessageLoop( 247 message_loop_, run_loop.QuitClosure())); 248 249 media::VideoCaptureParams params; 250 params.width = 352; 251 params.height = 288; 252 params.frame_per_second = 30; 253 params.session_id = kTestFakeDeviceId; 254 host_->OnStartCapture(kDeviceId, params); 255 run_loop.Run(); 256 } 257 258 #ifdef DUMP_VIDEO 259 void CaptureAndDumpVideo(int width, int heigt, int frame_rate) { 260 InSequence s; 261 // 1. First - get info about the new resolution 262 EXPECT_CALL(*host_, OnDeviceInfo(kDeviceId)); 263 264 // 2. Change state to started 265 EXPECT_CALL(*host_, OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STARTED)); 266 267 // 3. First filled buffer will arrive. 268 base::RunLoop run_loop; 269 EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _)) 270 .Times(AnyNumber()) 271 .WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure())); 272 273 media::VideoCaptureParams params; 274 params.width = width; 275 params.height = heigt; 276 params.frame_per_second = frame_rate; 277 params.session_id = kTestFakeDeviceId; 278 host_->SetDumpVideo(true); 279 host_->OnStartCapture(kDeviceId, params); 280 run_loop.Run(); 281 } 282 #endif 283 284 void StopCapture() { 285 base::RunLoop run_loop; 286 EXPECT_CALL(*host_.get(), 287 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)) 288 .WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure())); 289 290 host_->OnStopCapture(kDeviceId); 291 host_->SetReturnReceviedDibs(true); 292 host_->ReturnReceivedDibs(kDeviceId); 293 294 run_loop.Run(); 295 296 host_->SetReturnReceviedDibs(false); 297 // Expect the VideoCaptureDevice has been stopped 298 EXPECT_EQ(0u, host_->entries_.size()); 299 } 300 301 void NotifyPacketReady() { 302 base::RunLoop run_loop; 303 EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId, _, _)) 304 .Times(AnyNumber()).WillOnce(ExitMessageLoop( 305 message_loop_, run_loop.QuitClosure())) 306 .RetiresOnSaturation(); 307 run_loop.Run(); 308 } 309 310 void ReturnReceivedPackets() { 311 host_->ReturnReceivedDibs(kDeviceId); 312 } 313 314 void SimulateError() { 315 // Expect a change state to error state sent through IPC. 316 EXPECT_CALL(*host_.get(), 317 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ERROR)).Times(1); 318 VideoCaptureControllerID id(kDeviceId); 319 host_->OnError(id); 320 // Wait for the error callback. 321 base::RunLoop().RunUntilIdle(); 322 } 323 324 scoped_refptr<MockVideoCaptureHost> host_; 325 326 private: 327 scoped_ptr<media::AudioManager> audio_manager_; 328 scoped_ptr<MediaStreamManager> media_stream_manager_; 329 content::TestBrowserThreadBundle thread_bundle_; 330 scoped_refptr<base::MessageLoopProxy> message_loop_; 331 332 DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest); 333 }; 334 335 TEST_F(VideoCaptureHostTest, StartCapture) { 336 StartCapture(); 337 } 338 339 TEST_F(VideoCaptureHostTest, StartCapturePlayStop) { 340 StartCapture(); 341 NotifyPacketReady(); 342 NotifyPacketReady(); 343 ReturnReceivedPackets(); 344 StopCapture(); 345 } 346 347 TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) { 348 StartCapture(); 349 SimulateError(); 350 StopCapture(); 351 } 352 353 TEST_F(VideoCaptureHostTest, StartCaptureError) { 354 EXPECT_CALL(*host_.get(), 355 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)).Times(0); 356 StartCapture(); 357 NotifyPacketReady(); 358 SimulateError(); 359 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); 360 } 361 362 #ifdef DUMP_VIDEO 363 TEST_F(VideoCaptureHostTest, CaptureAndDumpVideoVga) { 364 CaptureAndDumpVideo(640, 480, 30); 365 } 366 TEST_F(VideoCaptureHostTest, CaptureAndDump720P) { 367 CaptureAndDumpVideo(1280, 720, 30); 368 } 369 #endif 370 371 } // namespace content 372