blob: 6787a385e6ea46b6e1b361559ab4a26e61db4afc [file] [log] [blame]
// Copyright (c) 2012 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 <map>
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/media_stream_requester.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/browser/renderer_host/media/video_capture_host.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/media/video_capture_messages.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/mock_resource_context.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_content_browser_client.h"
#include "media/audio/audio_manager.h"
#include "media/base/media_switches.h"
#include "media/base/video_frame.h"
#include "media/video/capture/video_capture_types.h"
#include "net/url_request/url_request_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_CHROMEOS)
#include "chromeos/audio/cras_audio_handler.h"
#endif
using ::testing::_;
using ::testing::AtLeast;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace content {
// Id used to identify the capture session between renderer and
// video_capture_host. This is an arbitrary value.
static const int kDeviceId = 555;
// 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 width, int height) {
base::FilePath file_name = base::FilePath(base::StringPrintf(
FILE_PATH_LITERAL("dump_w%d_h%d.yuv"), width, height));
file_.reset(base::OpenFile(file_name, "wb"));
expected_size_ = media::VideoFrame::AllocationSize(
media::VideoFrame::I420, gfx::Size(width, height));
}
void NewVideoFrame(const void* buffer) {
if (file_.get() != NULL) {
ASSERT_EQ(1U, fwrite(buffer, expected_size_, 1, file_.get()));
}
}
private:
base::ScopedFILE file_;
int expected_size_;
};
class MockMediaStreamRequester : public MediaStreamRequester {
public:
MockMediaStreamRequester() {}
virtual ~MockMediaStreamRequester() {}
// MediaStreamRequester implementation.
MOCK_METHOD5(StreamGenerated,
void(int render_frame_id,
int page_request_id,
const std::string& label,
const StreamDeviceInfoArray& audio_devices,
const StreamDeviceInfoArray& video_devices));
MOCK_METHOD3(StreamGenerationFailed,
void(int render_frame_id,
int page_request_id,
content::MediaStreamRequestResult result));
MOCK_METHOD3(DeviceStopped, void(int render_frame_id,
const std::string& label,
const StreamDeviceInfo& device));
MOCK_METHOD4(DevicesEnumerated, void(int render_frame_id,
int page_request_id,
const std::string& label,
const StreamDeviceInfoArray& devices));
MOCK_METHOD4(DeviceOpened, void(int render_frame_id,
int page_request_id,
const std::string& label,
const StreamDeviceInfo& device_info));
private:
DISALLOW_COPY_AND_ASSIGN(MockMediaStreamRequester);
};
class MockVideoCaptureHost : public VideoCaptureHost {
public:
MockVideoCaptureHost(MediaStreamManager* manager)
: VideoCaptureHost(manager),
return_buffers_(false),
dump_video_(false) {}
// A list of mock methods.
MOCK_METHOD4(OnNewBufferCreated,
void(int device_id,
base::SharedMemoryHandle handle,
int length,
int buffer_id));
MOCK_METHOD2(OnBufferFreed,
void(int device_id, int buffer_id));
MOCK_METHOD5(OnBufferFilled,
void(int device_id,
int buffer_id,
const media::VideoCaptureFormat& format,
const gfx::Rect& visible_rect,
base::TimeTicks timestamp));
MOCK_METHOD5(OnMailboxBufferFilled,
void(int device_id,
int buffer_id,
const gpu::MailboxHolder& mailbox_holder,
const media::VideoCaptureFormat& format,
base::TimeTicks timestamp));
MOCK_METHOD2(OnStateChanged, void(int device_id, VideoCaptureState state));
// Use class DumpVideo to write I420 video to file.
void SetDumpVideo(bool enable) {
dump_video_ = enable;
}
void SetReturnReceivedDibs(bool enable) {
return_buffers_ = enable;
}
// Return Dibs we currently have received.
void ReturnReceivedDibs(int device_id) {
int handle = GetReceivedDib();
while (handle) {
this->OnReceiveEmptyBuffer(device_id, handle, 0);
handle = GetReceivedDib();
}
}
int GetReceivedDib() {
if (filled_dib_.empty())
return 0;
std::map<int, base::SharedMemory*>::iterator it = filled_dib_.begin();
int h = it->first;
delete it->second;
filled_dib_.erase(it);
return h;
}
private:
virtual ~MockVideoCaptureHost() {
STLDeleteContainerPairSecondPointers(filled_dib_.begin(),
filled_dib_.end());
}
// 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) override {
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_NewBuffer, OnNewBufferCreatedDispatch)
IPC_MESSAGE_HANDLER(VideoCaptureMsg_FreeBuffer, OnBufferFreedDispatch)
IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilledDispatch)
IPC_MESSAGE_HANDLER(VideoCaptureMsg_MailboxBufferReady,
OnMailboxBufferFilledDispatch)
IPC_MESSAGE_HANDLER(VideoCaptureMsg_StateChanged, OnStateChangedDispatch)
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 OnNewBufferCreatedDispatch(int device_id,
base::SharedMemoryHandle handle,
uint32 length,
int buffer_id) {
OnNewBufferCreated(device_id, handle, length, buffer_id);
base::SharedMemory* dib = new base::SharedMemory(handle, false);
dib->Map(length);
filled_dib_[buffer_id] = dib;
}
void OnBufferFreedDispatch(int device_id, int buffer_id) {
OnBufferFreed(device_id, buffer_id);
std::map<int, base::SharedMemory*>::iterator it =
filled_dib_.find(buffer_id);
ASSERT_TRUE(it != filled_dib_.end());
delete it->second;
filled_dib_.erase(it);
}
void OnBufferFilledDispatch(int device_id,
int buffer_id,
const media::VideoCaptureFormat& frame_format,
const gfx::Rect& visible_rect,
base::TimeTicks timestamp) {
base::SharedMemory* dib = filled_dib_[buffer_id];
ASSERT_TRUE(dib != NULL);
if (dump_video_) {
if (!format_.IsValid()) {
dumper_.StartDump(frame_format.frame_size.width(),
frame_format.frame_size.height());
format_ = frame_format;
}
ASSERT_EQ(format_.frame_size.width(), frame_format.frame_size.width())
<< "Dump format does not handle variable resolution.";
ASSERT_EQ(format_.frame_size.height(), frame_format.frame_size.height())
<< "Dump format does not handle variable resolution.";
dumper_.NewVideoFrame(dib->memory());
}
OnBufferFilled(device_id, buffer_id, frame_format, visible_rect, timestamp);
if (return_buffers_) {
VideoCaptureHost::OnReceiveEmptyBuffer(device_id, buffer_id, 0);
}
}
void OnMailboxBufferFilledDispatch(int device_id,
int buffer_id,
const gpu::MailboxHolder& mailbox_holder,
const media::VideoCaptureFormat& format,
base::TimeTicks timestamp) {
OnMailboxBufferFilled(
device_id, buffer_id, mailbox_holder, format, timestamp);
if (return_buffers_) {
VideoCaptureHost::OnReceiveEmptyBuffer(device_id, buffer_id, 0);
}
}
void OnStateChangedDispatch(int device_id, VideoCaptureState state) {
OnStateChanged(device_id, state);
}
std::map<int, base::SharedMemory*> filled_dib_;
bool return_buffers_;
bool dump_video_;
media::VideoCaptureFormat format_;
DumpVideo dumper_;
};
ACTION_P2(ExitMessageLoop, message_loop, quit_closure) {
message_loop->PostTask(FROM_HERE, quit_closure);
}
// This is an integration test of VideoCaptureHost in conjunction with
// MediaStreamManager, VideoCaptureManager, VideoCaptureController, and
// VideoCaptureDevice.
class VideoCaptureHostTest : public testing::Test {
public:
VideoCaptureHostTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
message_loop_(base::MessageLoopProxy::current()),
opened_session_id_(kInvalidMediaCaptureSessionId) {}
virtual void SetUp() override {
SetBrowserClientForTesting(&browser_client_);
#if defined(OS_CHROMEOS)
chromeos::CrasAudioHandler::InitializeForTesting();
#endif
// Create our own MediaStreamManager.
audio_manager_.reset(media::AudioManager::CreateForTesting());
#ifndef TEST_REAL_CAPTURE_DEVICE
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kUseFakeDeviceForMediaStream);
#endif
media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
media_stream_manager_->UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy>());
// Create a Host and connect it to a simulated IPC channel.
host_ = new MockVideoCaptureHost(media_stream_manager_.get());
host_->OnChannelConnected(base::GetCurrentProcId());
OpenSession();
}
virtual void TearDown() override {
// Verifies and removes the expectations on host_ and
// returns true iff successful.
Mock::VerifyAndClearExpectations(host_.get());
EXPECT_EQ(0u, host_->entries_.size());
CloseSession();
// Simulate closing the IPC sender.
host_->OnChannelClosing();
// Release the reference to the mock object. The object will be destructed
// on the current message loop.
host_ = NULL;
#if defined(OS_CHROMEOS)
chromeos::CrasAudioHandler::Shutdown();
#endif
}
void OpenSession() {
const int render_process_id = 1;
const int render_frame_id = 1;
const int page_request_id = 1;
const GURL security_origin("http://test.com");
ASSERT_TRUE(opened_device_label_.empty());
// Enumerate video devices.
StreamDeviceInfoArray devices;
{
base::RunLoop run_loop;
std::string label = media_stream_manager_->EnumerateDevices(
&stream_requester_,
render_process_id,
render_frame_id,
browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
page_request_id,
MEDIA_DEVICE_VIDEO_CAPTURE,
security_origin);
EXPECT_CALL(stream_requester_, DevicesEnumerated(render_frame_id,
page_request_id,
label,
_))
.Times(1).WillOnce(
DoAll(ExitMessageLoop(message_loop_, run_loop.QuitClosure()),
SaveArg<3>(&devices)));
run_loop.Run();
Mock::VerifyAndClearExpectations(&stream_requester_);
media_stream_manager_->CancelRequest(label);
}
ASSERT_FALSE(devices.empty());
ASSERT_EQ(StreamDeviceInfo::kNoId, devices[0].session_id);
// Open the first device.
{
base::RunLoop run_loop;
StreamDeviceInfo opened_device;
media_stream_manager_->OpenDevice(
&stream_requester_,
render_process_id,
render_frame_id,
browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
page_request_id,
devices[0].device.id,
MEDIA_DEVICE_VIDEO_CAPTURE,
security_origin);
EXPECT_CALL(stream_requester_, DeviceOpened(render_frame_id,
page_request_id,
_,
_))
.Times(1).WillOnce(
DoAll(ExitMessageLoop(message_loop_, run_loop.QuitClosure()),
SaveArg<2>(&opened_device_label_),
SaveArg<3>(&opened_device)));
run_loop.Run();
Mock::VerifyAndClearExpectations(&stream_requester_);
ASSERT_NE(StreamDeviceInfo::kNoId, opened_device.session_id);
opened_session_id_ = opened_device.session_id;
}
}
void CloseSession() {
if (opened_device_label_.empty())
return;
media_stream_manager_->CancelRequest(opened_device_label_);
opened_device_label_.clear();
opened_session_id_ = kInvalidMediaCaptureSessionId;
}
protected:
void StartCapture() {
EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _))
.Times(AnyNumber())
.WillRepeatedly(Return());
base::RunLoop run_loop;
EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId, _, _, _, _))
.Times(AnyNumber())
.WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure()));
media::VideoCaptureParams params;
params.requested_format = media::VideoCaptureFormat(
gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420);
host_->OnStartCapture(kDeviceId, opened_session_id_, params);
run_loop.Run();
}
void StartStopCapture() {
// Quickly start and then stop capture, without giving much chance for
// asynchronous start operations to complete.
InSequence s;
base::RunLoop run_loop;
EXPECT_CALL(*host_.get(),
OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED));
media::VideoCaptureParams params;
params.requested_format = media::VideoCaptureFormat(
gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420);
host_->OnStartCapture(kDeviceId, opened_session_id_, params);
host_->OnStopCapture(kDeviceId);
run_loop.RunUntilIdle();
}
#ifdef DUMP_VIDEO
void CaptureAndDumpVideo(int width, int height, int frame_rate) {
InSequence s;
EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _))
.Times(AnyNumber()).WillRepeatedly(Return());
base::RunLoop run_loop;
EXPECT_CALL(*host_, OnBufferFilled(kDeviceId, _, _, _, _))
.Times(AnyNumber())
.WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure()));
media::VideoCaptureParams params;
params.requested_format =
media::VideoCaptureFormat(gfx::Size(width, height), frame_rate);
host_->SetDumpVideo(true);
host_->OnStartCapture(kDeviceId, opened_session_id_, params);
run_loop.Run();
}
#endif
void StopCapture() {
base::RunLoop run_loop;
EXPECT_CALL(*host_.get(),
OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED))
.WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure()));
host_->OnStopCapture(kDeviceId);
host_->SetReturnReceivedDibs(true);
host_->ReturnReceivedDibs(kDeviceId);
run_loop.Run();
host_->SetReturnReceivedDibs(false);
// Expect the VideoCaptureDevice has been stopped
EXPECT_EQ(0u, host_->entries_.size());
}
void NotifyPacketReady() {
base::RunLoop run_loop;
EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId, _, _, _, _))
.Times(AnyNumber())
.WillOnce(ExitMessageLoop(message_loop_, run_loop.QuitClosure()))
.RetiresOnSaturation();
run_loop.Run();
}
void ReturnReceivedPackets() {
host_->ReturnReceivedDibs(kDeviceId);
}
void SimulateError() {
// Expect a change state to error state sent through IPC.
EXPECT_CALL(*host_.get(),
OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ERROR)).Times(1);
VideoCaptureControllerID id(kDeviceId);
host_->OnError(id);
// Wait for the error callback.
base::RunLoop().RunUntilIdle();
}
scoped_refptr<MockVideoCaptureHost> host_;
private:
StrictMock<MockMediaStreamRequester> stream_requester_;
scoped_ptr<media::AudioManager> audio_manager_;
scoped_ptr<MediaStreamManager> media_stream_manager_;
content::TestBrowserThreadBundle thread_bundle_;
content::TestBrowserContext browser_context_;
content::TestContentBrowserClient browser_client_;
scoped_refptr<base::MessageLoopProxy> message_loop_;
int opened_session_id_;
std::string opened_device_label_;
DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest);
};
TEST_F(VideoCaptureHostTest, CloseSessionWithoutStopping) {
StartCapture();
// When the session is closed via the stream without stopping capture, the
// ENDED event is sent.
EXPECT_CALL(*host_.get(),
OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ENDED)).Times(1);
CloseSession();
base::RunLoop().RunUntilIdle();
}
TEST_F(VideoCaptureHostTest, StopWhileStartPending) {
StartStopCapture();
}
TEST_F(VideoCaptureHostTest, StartCapturePlayStop) {
StartCapture();
NotifyPacketReady();
NotifyPacketReady();
ReturnReceivedPackets();
StopCapture();
}
TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) {
StartCapture();
SimulateError();
StopCapture();
}
TEST_F(VideoCaptureHostTest, StartCaptureError) {
EXPECT_CALL(*host_.get(),
OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)).Times(0);
StartCapture();
NotifyPacketReady();
SimulateError();
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200));
}
#ifdef DUMP_VIDEO
TEST_F(VideoCaptureHostTest, CaptureAndDumpVideoVga) {
CaptureAndDumpVideo(640, 480, 30);
}
TEST_F(VideoCaptureHostTest, CaptureAndDump720P) {
CaptureAndDumpVideo(1280, 720, 30);
}
#endif
} // namespace content