| Index: content/browser/renderer_host/video_capture_host_unittest.cc
|
| ===================================================================
|
| --- content/browser/renderer_host/video_capture_host_unittest.cc (revision 0)
|
| +++ content/browser/renderer_host/video_capture_host_unittest.cc (revision 0)
|
| @@ -0,0 +1,363 @@
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include <list>
|
| +#include <string>
|
| +
|
| +#include "base/file_util.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/message_loop.h"
|
| +#include "base/process_util.h"
|
| +#include "base/stringprintf.h"
|
| +#include "content/browser/browser_thread.h"
|
| +#include "content/browser/renderer_host/video_capture_host.h"
|
| +#include "content/browser/media_stream/video_capture_manager.h"
|
| +#include "content/common/video_capture_messages.h"
|
| +#include "media/video/capture/video_capture_types.h"
|
| +
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using ::testing::_;
|
| +using ::testing::AnyNumber;
|
| +using ::testing::DoAll;
|
| +using ::testing::InSequence;
|
| +using ::testing::Return;
|
| +
|
| +// IPC::Msg.routing_id.
|
| +static const int32 kRouteId = 200;
|
| +// Id used to identify the capture session between renderer and
|
| +// video_capture_host.
|
| +static const int kDeviceId = 1;
|
| +// Id of a video capture device
|
| +static const media::VideoCaptureSessionId kTestFakeDeviceId =
|
| + media_stream::VideoCaptureManager::kStartOpenSessionId;
|
| +
|
| +// Define to enable test where video is dumped to file.
|
| +//#define DUMP_VIDEO
|
| +
|
| +// Define to use a real video capture device.
|
| +//#define TEST_REAL_CAPTURE_DEVICE
|
| +
|
| +// Simple class used for dumping video to a file. This can be used for
|
| +// verifying the output.
|
| +class DumpVideo {
|
| + public:
|
| + DumpVideo() : expected_size_(0) {}
|
| + void StartDump(int widht, int height) {
|
| + // Create an FilePath that works on all platforms. There should
|
| + // be a better way.
|
| + FilePath file_name =
|
| + FilePath::FromWStringHack(StringPrintf(L"dump_w%d_h%d.yuv", widht,
|
| + height));
|
| + file_.reset(file_util::OpenFile(file_name, "wb"));
|
| + expected_size_ = widht * height * 3 / 2;
|
| + }
|
| + void NewVideoFrame(const void* buffer) {
|
| + if (file_.get() != NULL) {
|
| + fwrite(buffer, expected_size_, 1, file_.get());
|
| + }
|
| + }
|
| +
|
| + private:
|
| + file_util::ScopedFILE file_;
|
| + int expected_size_;
|
| +};
|
| +
|
| +class MockVideoCaptureHost : public VideoCaptureHost {
|
| + public:
|
| + MockVideoCaptureHost() : return_buffers_(false), dump_video_(false) {}
|
| + virtual ~MockVideoCaptureHost() {}
|
| +
|
| + // A list of mock methods.
|
| + MOCK_METHOD3(OnBufferFilled,
|
| + void(int routing_id, int device_id, TransportDIB::Handle));
|
| + MOCK_METHOD3(OnStateChanged,
|
| + void(int routing_id, int device_id,
|
| + media::VideoCapture::State state));
|
| + MOCK_METHOD2(OnDeviceInfo, void(int routing_id, int device_id));
|
| +
|
| + // Use class DumpVideo to write I420 video to file.
|
| + void SetDumpVideo(bool enable) {
|
| + dump_video_ = enable;
|
| + }
|
| + void SetReturnReceviedDibs(bool enable) {
|
| + return_buffers_ = enable;
|
| + }
|
| +
|
| + // Return Dibs we currently have received.
|
| + void ReturnReceivedDibs(int device_id) {
|
| + TransportDIB::Handle handle = GetReceivedDib();
|
| + while (handle != 0) {
|
| + IPC::Message msg;
|
| + msg.set_routing_id(kRouteId);
|
| + this->OnReceiveEmptyBuffer(msg, device_id, handle);
|
| + handle = GetReceivedDib();
|
| + }
|
| + }
|
| + TransportDIB::Handle GetReceivedDib() {
|
| + if (filled_dib_.empty())
|
| + return 0;
|
| + TransportDIB::Handle h = filled_dib_.front();
|
| + filled_dib_.pop_front();
|
| + return h;
|
| + }
|
| +
|
| + private:
|
| + // This method is used to dispatch IPC messages to the renderer. We intercept
|
| + // these messages here and dispatch to our mock methods to verify the
|
| + // conversation between this object and the renderer.
|
| + virtual bool Send(IPC::Message* message) {
|
| + CHECK(message);
|
| +
|
| + // In this method we dispatch the messages to the according handlers as if
|
| + // we are the renderer.
|
| + bool handled = true;
|
| + IPC_BEGIN_MESSAGE_MAP(MockVideoCaptureHost, *message)
|
| + IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilled)
|
| + IPC_MESSAGE_HANDLER(VideoCaptureMsg_StateChanged, OnStateChanged)
|
| + IPC_MESSAGE_HANDLER(VideoCaptureMsg_DeviceInfo, OnDeviceInfo)
|
| + IPC_MESSAGE_UNHANDLED(handled = false)
|
| + IPC_END_MESSAGE_MAP()
|
| + EXPECT_TRUE(handled);
|
| +
|
| + delete message;
|
| + return true;
|
| + }
|
| +
|
| + // These handler methods do minimal things and delegate to the mock methods.
|
| + void OnBufferFilled(const IPC::Message& msg, int device_id,
|
| + TransportDIB::Handle dib_handle) {
|
| + if (dump_video_) {
|
| + TransportDIB* dib = TransportDIB::Map(dib_handle);
|
| + ASSERT_TRUE(dib != NULL);
|
| + dumper_.NewVideoFrame(dib->memory());
|
| + }
|
| +
|
| + OnBufferFilled(msg.routing_id(), device_id, dib_handle);
|
| + if (return_buffers_) {
|
| + VideoCaptureHost::OnReceiveEmptyBuffer(msg, device_id, dib_handle);
|
| + } else {
|
| + filled_dib_.push_back(dib_handle);
|
| + }
|
| + }
|
| +
|
| + void OnStateChanged(const IPC::Message& msg, int device_id,
|
| + media::VideoCapture::State state) {
|
| + OnStateChanged(msg.routing_id(), device_id, state);
|
| + }
|
| +
|
| + void OnDeviceInfo(const IPC::Message& msg, int device_id,
|
| + media::VideoCaptureParams params) {
|
| + if (dump_video_) {
|
| + dumper_.StartDump(params.width, params.height);
|
| + }
|
| + OnDeviceInfo(msg.routing_id(), device_id);
|
| + }
|
| +
|
| + std::list<TransportDIB::Handle> filled_dib_;
|
| + bool return_buffers_;
|
| + bool dump_video_;
|
| + DumpVideo dumper_;
|
| +};
|
| +
|
| +ACTION_P(ExitMessageLoop, message_loop) {
|
| + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask());
|
| +}
|
| +
|
| +class VideoCaptureHostTest : public testing::Test {
|
| + public:
|
| + VideoCaptureHostTest() {}
|
| +
|
| + protected:
|
| + virtual void SetUp() {
|
| + // Setup the VideoCaptureManager to use fake video capture device.
|
| + #ifndef TEST_REAL_CAPTURE_DEVICE
|
| + media_stream::VideoCaptureManager::CreateTestManager();
|
| + #endif
|
| + // Create a message loop so VideoCaptureHostTest can use it.
|
| + message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO));
|
| + io_thread_.reset(new BrowserThread(BrowserThread::IO, message_loop_.get()));
|
| + host_ = new MockVideoCaptureHost();
|
| +
|
| + // Simulate IPC channel connected.
|
| + host_->OnChannelConnected(base::GetCurrentProcId());
|
| + }
|
| +
|
| + virtual void TearDown() {
|
| + // Simulate closing the IPC channel.
|
| + host_->OnChannelClosing();
|
| +
|
| + // Release the reference to the mock object. The object will be destructed
|
| + // on message_loop_.
|
| + host_ = NULL;
|
| +
|
| + // We need to continue running message_loop_ to complete all destructions.
|
| + SyncWithVideoCaptureManagerThread();
|
| +
|
| + io_thread_.reset();
|
| + }
|
| +
|
| + // Called on the VideoCaptureManager thread.
|
| + static void PostQuitMessageLoop(MessageLoop* message_loop) {
|
| + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask());
|
| + }
|
| +
|
| + // Called on the main thread.
|
| + static void PostQuitOnVideoCaptureManagerThread(MessageLoop* message_loop) {
|
| + media_stream::VideoCaptureManager::Get()->GetMessageLoop()->PostTask(
|
| + FROM_HERE, NewRunnableFunction(&PostQuitMessageLoop, message_loop));
|
| + }
|
| +
|
| + // SyncWithVideoCaptureManagerThread() waits until all pending tasks on the
|
| + // video_capture_manager thread are executed while also processing pending
|
| + // task in message_loop_ on the current thread. It is used to synchronize
|
| + // with the video capture manager thread when we are stopping a video
|
| + // capture device.
|
| + void SyncWithVideoCaptureManagerThread() {
|
| + message_loop_->PostTask(
|
| + FROM_HERE, NewRunnableFunction(&PostQuitOnVideoCaptureManagerThread,
|
| + message_loop_.get()));
|
| + message_loop_->Run();
|
| + }
|
| +
|
| + void StartCapture() {
|
| + InSequence s;
|
| + // 1. First - get info about the new resolution
|
| + EXPECT_CALL(*host_, OnDeviceInfo(kRouteId, kDeviceId));
|
| +
|
| + // 2. Change state to started
|
| + EXPECT_CALL(*host_, OnStateChanged(kRouteId, kDeviceId,
|
| + media::VideoCapture::kStarted));
|
| +
|
| + // 3. First filled buffer will arrive.
|
| + EXPECT_CALL(*host_, OnBufferFilled(kRouteId, kDeviceId, _))
|
| + .Times(AnyNumber())
|
| + .WillOnce(ExitMessageLoop(message_loop_.get()));
|
| +
|
| + IPC::Message msg;
|
| + msg.set_routing_id(kRouteId);
|
| +
|
| + media::VideoCaptureParams params;
|
| + params.width = 352;
|
| + params.height = 288;
|
| + params.frame_per_second = 30;
|
| + params.session_id = kTestFakeDeviceId;
|
| + host_->OnStartCapture(msg, kDeviceId, params);
|
| + message_loop_->Run();
|
| + }
|
| +
|
| + void CaptureAndDumpVideo(int width, int heigt, int frame_rate) {
|
| + InSequence s;
|
| + // 1. First - get info about the new resolution
|
| + EXPECT_CALL(*host_, OnDeviceInfo(kRouteId, kDeviceId));
|
| +
|
| + // 2. Change state to started
|
| + EXPECT_CALL(*host_, OnStateChanged(kRouteId, kDeviceId,
|
| + media::VideoCapture::kStarted));
|
| +
|
| + // 3. First filled buffer will arrive.
|
| + EXPECT_CALL(*host_, OnBufferFilled(kRouteId, kDeviceId, _))
|
| + .Times(AnyNumber())
|
| + .WillOnce(ExitMessageLoop(message_loop_.get()));
|
| +
|
| + IPC::Message msg;
|
| + msg.set_routing_id(kRouteId);
|
| +
|
| + media::VideoCaptureParams params;
|
| + params.width = width;
|
| + params.height = heigt;
|
| + params.frame_per_second = frame_rate;
|
| + params.session_id = kTestFakeDeviceId;
|
| + host_->SetDumpVideo(true);
|
| + host_->OnStartCapture(msg, kDeviceId, params);
|
| + message_loop_->Run();
|
| + }
|
| +
|
| + void StopCapture() {
|
| + EXPECT_CALL(*host_, OnStateChanged(kRouteId, kDeviceId,
|
| + media::VideoCapture::kStopped))
|
| + .Times(1);
|
| +
|
| + IPC::Message msg;
|
| + msg.set_routing_id(kRouteId);
|
| + host_->OnStopCapture(msg, kDeviceId);
|
| + host_->SetReturnReceviedDibs(true);
|
| + host_->ReturnReceivedDibs(kDeviceId);
|
| +
|
| + SyncWithVideoCaptureManagerThread();
|
| + host_->SetReturnReceviedDibs(false);
|
| + }
|
| +
|
| + void NotifyPacketReady() {
|
| + EXPECT_CALL(*host_, OnBufferFilled(kRouteId, kDeviceId, _))
|
| + .Times(AnyNumber())
|
| + .WillOnce(ExitMessageLoop(message_loop_.get()))
|
| + .RetiresOnSaturation();
|
| + message_loop_->Run();
|
| + }
|
| +
|
| + void ReturnReceivedPackets() {
|
| + host_->ReturnReceivedDibs(kDeviceId);
|
| + }
|
| +
|
| + void SimulateError() {
|
| + // Expect a change state to error state sent through IPC.
|
| + EXPECT_CALL(*host_, OnStateChanged(kRouteId, kDeviceId,
|
| + media::VideoCapture::kError))
|
| + .Times(1);
|
| +
|
| + VideoCaptureMemory::VCEntryId entry(kRouteId, kDeviceId, kTestFakeDeviceId);
|
| + host_->OnError(entry);
|
| + SyncWithVideoCaptureManagerThread();
|
| + // Expect the VideoCaptureDevice has been stopped
|
| + EXPECT_EQ(0u, host_->vc_entries_.size());
|
| + }
|
| +
|
| + scoped_refptr<MockVideoCaptureHost> host_;
|
| + private:
|
| + scoped_ptr<MessageLoop> message_loop_;
|
| + scoped_ptr<BrowserThread> io_thread_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest);
|
| +};
|
| +
|
| +TEST_F(VideoCaptureHostTest, StartCapture) {
|
| + StartCapture();
|
| +}
|
| +
|
| +TEST_F(VideoCaptureHostTest, StartCapturePlayStop) {
|
| + StartCapture();
|
| + NotifyPacketReady();
|
| + NotifyPacketReady();
|
| + ReturnReceivedPackets();
|
| + StopCapture();
|
| +}
|
| +
|
| +TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) {
|
| + StartCapture();
|
| + SimulateError();
|
| + StopCapture();
|
| +}
|
| +
|
| +TEST_F(VideoCaptureHostTest, StartCaptureError) {
|
| + EXPECT_CALL(*host_, OnStateChanged(kRouteId, kDeviceId,
|
| + media::VideoCapture::kStopped))
|
| + .Times(0);
|
| + StartCapture();
|
| + NotifyPacketReady();
|
| + SimulateError();
|
| + base::PlatformThread::Sleep(200);
|
| +}
|
| +
|
| +#ifdef DUMP_VIDEO
|
| +TEST_F(VideoCaptureHostTest, CaptureAndDumpVideoVga) {
|
| + CaptureAndDumpVideo(640, 480, 30);
|
| +}
|
| +TEST_F(VideoCaptureHostTest, CaptureAndDump720P) {
|
| + CaptureAndDumpVideo(1280, 720, 30);
|
| +}
|
| +#endif
|
| +
|
| +
|
|
|