diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-26 23:30:51 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-26 23:30:51 +0000 |
commit | 88fa6bf2a6300335381178004eb434df99595f04 (patch) | |
tree | dcfc9976760f0f0132081f01df2179b224ee8617 /content | |
parent | 769c0eabac88d21875c5c1812687889d79e6dbef (diff) | |
download | chromium_src-88fa6bf2a6300335381178004eb434df99595f04.zip chromium_src-88fa6bf2a6300335381178004eb434df99595f04.tar.gz chromium_src-88fa6bf2a6300335381178004eb434df99595f04.tar.bz2 |
VideoCaptureHost
This is the patch containing code necessary for communicating with the VideoCaptureMessageFilter from the browser process and transferring Transport Dibs filled with video frames in I420 color format. It also contain code for color converting video frames to I420.
Color conversion has been tested on Linux and Windows by using video_capture_host_unittest and dumping I420 frames to file. See #define DUMP_VIDEO #define TEST_REAL_CAPTURE_DEVICE.
Patch by perk@google.com:
http://codereview.chromium.org/7002027/
BUG=none
TEST=try bots
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@86927 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/renderer_host/video_capture_controller.cc | 225 | ||||
-rw-r--r-- | content/browser/renderer_host/video_capture_controller.h | 116 | ||||
-rw-r--r-- | content/browser/renderer_host/video_capture_host.cc | 186 | ||||
-rw-r--r-- | content/browser/renderer_host/video_capture_host.h | 127 | ||||
-rw-r--r-- | content/browser/renderer_host/video_capture_host_unittest.cc | 373 | ||||
-rw-r--r-- | content/content_browser.gypi | 4 |
6 files changed, 1031 insertions, 0 deletions
diff --git a/content/browser/renderer_host/video_capture_controller.cc b/content/browser/renderer_host/video_capture_controller.cc new file mode 100644 index 0000000..56d2041 --- /dev/null +++ b/content/browser/renderer_host/video_capture_controller.cc @@ -0,0 +1,225 @@ +// 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 "content/browser/renderer_host/video_capture_controller.h" + +#include "base/logging.h" +#include "base/stl_util-inl.h" +#include "content/browser/browser_thread.h" +#include "content/browser/media_stream/video_capture_manager.h" +#include "media/base/yuv_convert.h" + +#if defined(OS_WIN) +#include "content/common/section_util_win.h" +#endif + +// The number of TransportDIBs VideoCaptureController allocate. +static const size_t kNoOfDIBS = 3; + +VideoCaptureController::VideoCaptureController( + ControllerId id, + base::ProcessHandle render_process, + EventHandler* event_handler) + : render_handle_(render_process), + report_ready_to_delete_(false), + event_handler_(event_handler), + id_(id) {} + +VideoCaptureController::~VideoCaptureController() { + // Delete all TransportDIBs. + STLDeleteContainerPairSecondPointers(owned_dibs_.begin(), + owned_dibs_.end()); +} + +void VideoCaptureController::StartCapture( + const media::VideoCaptureParams& params) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + params_ = params; + media_stream::VideoCaptureManager* manager = + media_stream::VideoCaptureManager::Get(); + // Order the manager to start the actual capture. + manager->Start(params, this); +} + +void VideoCaptureController::StopCapture(Task* stopped_task) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + media_stream::VideoCaptureManager* manager = + media_stream::VideoCaptureManager::Get(); + manager->Stop(params_.session_id, + NewRunnableMethod(this, + &VideoCaptureController::OnDeviceStopped, + stopped_task)); +} + +void VideoCaptureController::ReturnTransportDIB(TransportDIB::Handle handle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + bool ready_to_delete; + { + base::AutoLock lock(lock_); + free_dibs_.push_back(handle); + ready_to_delete = (free_dibs_.size() == owned_dibs_.size()); + } + if (report_ready_to_delete_ && ready_to_delete) { + event_handler_->OnReadyToDelete(id_); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Implements VideoCaptureDevice::EventHandler. +// OnIncomingCapturedFrame is called the thread running the capture device. +// I.e.- DirectShow thread on windows and v4l2_thread on Linux. +void VideoCaptureController::OnIncomingCapturedFrame(const uint8* data, + int length, + base::Time timestamp) { + TransportDIB::Handle handle; + TransportDIB* dib; + // Check if there is a TransportDIB to fill. + bool buffer_exist = false; + { + base::AutoLock lock(lock_); + if (!report_ready_to_delete_ && free_dibs_.size() > 0) { + handle = free_dibs_.back(); + free_dibs_.pop_back(); + DIBMap::iterator it = owned_dibs_.find(handle); + if (it != owned_dibs_.end()) { + dib = it->second; + buffer_exist = true; + } + } + } + + if (!buffer_exist) { + return; + } + + if (!dib->Map()) { + VLOG(1) << "OnIncomingCapturedFrame - Failed to map handle."; + base::AutoLock lock(lock_); + free_dibs_.push_back(handle); + return; + } + uint8* target = static_cast<uint8*>(dib->memory()); + CHECK(dib->size() >= static_cast<size_t> (frame_info_.width * + frame_info_.height * 3) / + 2); + + // Do color conversion from the camera format to I420. + switch (frame_info_.color) { + case media::VideoCaptureDevice::kColorUnknown: // Color format not set. + break; + case media::VideoCaptureDevice::kI420: { + memcpy(target, data, (frame_info_.width * frame_info_.height * 3) / 2); + break; + } + case media::VideoCaptureDevice::kYUY2: { + uint8* yplane = target; + uint8* uplane = target + frame_info_.width * frame_info_.height; + uint8* vplane = uplane + (frame_info_.width * frame_info_.height) / 4; + media::ConvertYUY2ToYUV(data, yplane, uplane, vplane, frame_info_.width, + frame_info_.height); + break; + } + case media::VideoCaptureDevice::kRGB24: { +#if defined(OS_WIN) // RGB on Windows start at the bottom line. + uint8* yplane = target; + uint8* uplane = target + frame_info_.width * frame_info_.height; + uint8* vplane = uplane + (frame_info_.width * frame_info_.height) / 4; + int ystride = frame_info_.width; + int uvstride = frame_info_.width / 2; + int rgb_stride = - 3 * frame_info_.width; + const uint8* rgb_src = data + 3 * frame_info_.width * + (frame_info_.height -1); +#else + uint8* yplane = target; + uint8* uplane = target + frame_info_.width * frame_info_.height; + uint8* vplane = uplane + (frame_info_.width * frame_info_.height) / 4; + int ystride = frame_info_.width; + int uvstride = frame_info_.width / 2; + int rgb_stride = 3 * frame_info_.width; + const uint8* rgb_src = data; +#endif + media::ConvertRGB24ToYUV(rgb_src, yplane, uplane, vplane, + frame_info_.width, frame_info_.height, + rgb_stride, ystride, uvstride); + break; + } + case media::VideoCaptureDevice::kARGB: { + uint8* yplane = target; + uint8* uplane = target + frame_info_.width * frame_info_.height; + uint8* vplane = uplane + (frame_info_.width * frame_info_.height) / 4; + media::ConvertRGB32ToYUV(data, yplane, uplane, vplane, frame_info_.width, + frame_info_.height, frame_info_.width * 4, + frame_info_.width, frame_info_.width / 2); + break; + } + default: + NOTREACHED(); + } + + event_handler_->OnBufferReady(id_, handle, timestamp); +} + +void VideoCaptureController::OnError() { + event_handler_->OnError(id_); +} + +void VideoCaptureController::OnFrameInfo( + const media::VideoCaptureDevice::Capability& info) { + DCHECK(owned_dibs_.empty()); + bool frames_created = true; + const size_t needed_size = (info.width * info.height * 3) / 2; + for (size_t i = 0; i < kNoOfDIBS; ++i) { + TransportDIB* dib = TransportDIB::Create(needed_size, i); + if (!dib) { + frames_created = false; + break; + } + // Lock needed since the buffers are used in OnIncomingFrame + // and we need to use it there in order to avoid memcpy of complete frames. + base::AutoLock lock(lock_); +#if defined(OS_WIN) + // On Windows we need to get a handle the can be used in the render process. + TransportDIB::Handle handle = chrome::GetSectionForProcess( + dib->handle(), render_handle_, false); +#else + TransportDIB::Handle handle = dib->handle(); +#endif + owned_dibs_.insert(std::make_pair(handle, dib)); + free_dibs_.push_back(handle); + } + frame_info_= info; + + // Check that all DIBs where created successfully. + if (!frames_created) { + event_handler_->OnError(id_); + } + event_handler_->OnFrameInfo(id_, info.width, info.height, + info.frame_rate); +} + +/////////////////////////////////////////////////////////////////////////////// +// Called by VideoCaptureManager when a device have been stopped. +// This will report to the event handler that this object is ready to be deleted +// if all DIBS have been returned. +void VideoCaptureController::OnDeviceStopped(Task* stopped_task) { + bool ready_to_delete_now; + + // Set flag to indicate we need to report when all DIBs have been returned. + report_ready_to_delete_ = true; + { + base::AutoLock lock(lock_); + ready_to_delete_now = (free_dibs_.size() == owned_dibs_.size()); + } + + if (ready_to_delete_now) { + event_handler_->OnReadyToDelete(id_); + } + if (stopped_task) { + stopped_task->Run(); + delete stopped_task; + } +} diff --git a/content/browser/renderer_host/video_capture_controller.h b/content/browser/renderer_host/video_capture_controller.h new file mode 100644 index 0000000..18341ad --- /dev/null +++ b/content/browser/renderer_host/video_capture_controller.h @@ -0,0 +1,116 @@ +// 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. + +// VideoCaptureController is the glue between VideoCaptureHost, +// VideoCaptureManager and VideoCaptureDevice. +// It provides functions for VideoCaptureHost to start a VideoCaptureDevice and +// is responsible for keeping track of TransportDIBs and filling them with I420 +// video frames for IPC communication between VideoCaptureHost and +// VideoCaptureMessageFilter. +// It implements media::VideoCaptureDevice::EventHandler to get video frames +// from a VideoCaptureDevice object and do color conversion straight into the +// TransportDIBs to avoid a memory copy. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_CONTROLLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_CONTROLLER_H_ + +#include <list> +#include <map> + +#include "base/memory/ref_counted.h" +#include "base/process.h" +#include "base/synchronization/lock.h" +#include "base/task.h" +#include "media/video/capture/video_capture_device.h" +#include "media/video/capture/video_capture_types.h" +#include "ui/gfx/surface/transport_dib.h" + +class VideoCaptureController + : public base::RefCountedThreadSafe<VideoCaptureController>, + public media::VideoCaptureDevice::EventHandler { + public: + // Id used for identifying an object of VideoCaptureController. + typedef std::pair<int32, int> ControllerId; + class EventHandler { + public: + // An Error have occurred in the VideoCaptureDevice. + virtual void OnError(ControllerId id) = 0; + + // An TransportDIB have been filled with I420 video. + virtual void OnBufferReady(ControllerId id, + TransportDIB::Handle handle, + base::Time timestamp) = 0; + + // The frame resolution the VideoCaptureDevice capture video in. + virtual void OnFrameInfo(ControllerId id, + int width, + int height, + int frame_rate) = 0; + + // Report that this object can be deleted. + virtual void OnReadyToDelete(ControllerId id) = 0; + + protected: + virtual ~EventHandler() {} + }; + + VideoCaptureController(ControllerId id, + base::ProcessHandle render_process, + EventHandler* event_handler); + virtual ~VideoCaptureController(); + + // Starts video capturing and tries to use the resolution specified in + // params. + // When capturing has started EventHandler::OnFrameInfo is called with + // resolution that best matches the requested that the video capture device + // support. + void StartCapture(const media::VideoCaptureParams& params); + + // Stop video capture. + // When the capture is stopped and all TransportDIBS have been returned + // EventHandler::OnReadyToDelete will be called. + // stopped_task may be null but it can be used to get a notification when the + // device is stopped. + void StopCapture(Task* stopped_task); + + // Return a DIB previously given in EventHandler::OnBufferReady. + void ReturnTransportDIB(TransportDIB::Handle handle); + + // Implement media::VideoCaptureDevice::EventHandler. + virtual void OnIncomingCapturedFrame(const uint8* data, + int length, + base::Time timestamp); + virtual void OnError(); + virtual void OnFrameInfo(const media::VideoCaptureDevice::Capability& info); + + private: + // Called by VideoCaptureManager when a device have been stopped. + void OnDeviceStopped(Task* stopped_task); + + // Lock to protect free_dibs_ and owned_dibs_. + base::Lock lock_; + // Handle to the render process that will receive the DIBs. + base::ProcessHandle render_handle_; + bool report_ready_to_delete_; + typedef std::list<TransportDIB::Handle> DIBHandleList; + typedef std::map<TransportDIB::Handle, TransportDIB*> DIBMap; + + // Free DIBS that can be filled with video frames. + DIBHandleList free_dibs_; + + // All DIBS created by this object. + DIBMap owned_dibs_; + EventHandler* event_handler_; + + // The parameter that was requested when starting the capture device. + media::VideoCaptureParams params_; + + // Id used for identifying this object. + ControllerId id_; + media::VideoCaptureDevice::Capability frame_info_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureController); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_CONTROLLER_H_ diff --git a/content/browser/renderer_host/video_capture_host.cc b/content/browser/renderer_host/video_capture_host.cc new file mode 100644 index 0000000..f5b9924 --- /dev/null +++ b/content/browser/renderer_host/video_capture_host.cc @@ -0,0 +1,186 @@ +// 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 "content/browser/renderer_host/video_capture_host.h" + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util-inl.h" +#include "content/common/video_capture_messages.h" + +VideoCaptureHost::VideoCaptureHost() {} + +VideoCaptureHost::~VideoCaptureHost() {} + +void VideoCaptureHost::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + // Since the IPC channel is gone, close all requested VideCaptureDevices. + for (EntryMap::iterator it = entries_.begin(); it != entries_.end(); it++) { + VideoCaptureController* controller = it->second; + // Since the channel is closing we need a task to make sure VideoCaptureHost + // is not deleted before VideoCaptureController. + controller->StopCapture( + NewRunnableMethod(this, &VideoCaptureHost::OnReadyToDelete, it->first)); + } +} + +void VideoCaptureHost::OnDestruct() const { + BrowserThread::DeleteOnIOThread::Destruct(this); +} + +/////////////////////////////////////////////////////////////////////////////// + +// Implements VideoCaptureController::EventHandler. +void VideoCaptureHost::OnError(VideoCaptureController::ControllerId id) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &VideoCaptureHost::DoHandleError, id.first, + id.second)); +} + +void VideoCaptureHost::OnBufferReady( + VideoCaptureController::ControllerId id, + TransportDIB::Handle handle, + base::Time timestamp) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &VideoCaptureHost::DoSendFilledBuffer, id.first, + id.second, handle, timestamp)); +} + +void VideoCaptureHost::OnFrameInfo(VideoCaptureController::ControllerId id, + int width, + int height, + int frame_per_second) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &VideoCaptureHost::DoSendFrameInfo, id.first, + id.second, width, height, frame_per_second)); +} + +void VideoCaptureHost::OnReadyToDelete( + VideoCaptureController::ControllerId id) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &VideoCaptureHost::DoDeleteVideoCaptureController, + id)); +} + +void VideoCaptureHost::DoSendFilledBuffer(int32 routing_id, + int device_id, + TransportDIB::Handle handle, + base::Time timestamp) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + Send(new VideoCaptureMsg_BufferReady(routing_id, device_id, handle, + timestamp)); +} + +void VideoCaptureHost::DoHandleError(int32 routing_id, int device_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + Send(new VideoCaptureMsg_StateChanged(routing_id, device_id, + media::VideoCapture::kError)); + + VideoCaptureController::ControllerId id(routing_id, device_id); + EntryMap::iterator it = entries_.find(id); + if (it != entries_.end()) { + VideoCaptureController* controller = it->second; + controller->StopCapture(NULL); + } +} + +void VideoCaptureHost::DoSendFrameInfo(int32 routing_id, + int device_id, + int width, + int height, + int frame_per_second) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + media::VideoCaptureParams params; + params.width = width; + params.height = height; + params.frame_per_second = frame_per_second; + Send(new VideoCaptureMsg_DeviceInfo(routing_id, device_id, params)); + Send(new VideoCaptureMsg_StateChanged(routing_id, device_id, + media::VideoCapture::kStarted)); +} + +/////////////////////////////////////////////////////////////////////////////// +// IPC Messages handler. +bool VideoCaptureHost::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(VideoCaptureHost, message, *message_was_ok) + IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_Start, OnStartCapture) + IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_Pause, OnPauseCapture) + IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_Stop, OnStopCapture) + IPC_MESSAGE_HANDLER(VideoCaptureHostMsg_BufferReady, OnReceiveEmptyBuffer) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + return handled; +} + +void VideoCaptureHost::OnStartCapture(const IPC::Message& msg, int device_id, + const media::VideoCaptureParams& params) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + VideoCaptureController::ControllerId controller_id(msg.routing_id(), + device_id); + + DCHECK(entries_.find(controller_id) == entries_.end()); + + scoped_refptr<VideoCaptureController> controller = + new VideoCaptureController(controller_id, peer_handle(), this); + entries_.insert(std::make_pair(controller_id, controller)); + controller->StartCapture(params); +} + +void VideoCaptureHost::OnStopCapture(const IPC::Message& msg, int device_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + VideoCaptureController::ControllerId controller_id(msg.routing_id(), + device_id); + EntryMap::iterator it = entries_.find(controller_id); + if (it != entries_.end()) { + scoped_refptr<VideoCaptureController> controller = it->second; + controller->StopCapture(NULL); + } else { + // It does not exist so it must have been stopped already. + Send(new VideoCaptureMsg_StateChanged(msg.routing_id(), device_id, + media::VideoCapture::kStopped)); + } +} + +void VideoCaptureHost::OnPauseCapture(const IPC::Message& msg, int device_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // Not used. + Send(new VideoCaptureMsg_StateChanged(msg.routing_id(), device_id, + media::VideoCapture::kError)); +} + +void VideoCaptureHost::OnReceiveEmptyBuffer(const IPC::Message& msg, + int device_id, + TransportDIB::Handle handle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + VideoCaptureController::ControllerId controller_id(msg.routing_id(), + device_id); + EntryMap::iterator it = entries_.find(controller_id); + if (it != entries_.end()) { + scoped_refptr<VideoCaptureController> controller = it->second; + controller->ReturnTransportDIB(handle); + } +} + +void VideoCaptureHost::DoDeleteVideoCaptureController( + VideoCaptureController::ControllerId id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Report that the device have successfully been stopped. + Send(new VideoCaptureMsg_StateChanged(id.first, id.second, + media::VideoCapture::kStopped)); + entries_.erase(id); +} diff --git a/content/browser/renderer_host/video_capture_host.h b/content/browser/renderer_host/video_capture_host.h new file mode 100644 index 0000000..3ee7717 --- /dev/null +++ b/content/browser/renderer_host/video_capture_host.h @@ -0,0 +1,127 @@ +// 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. +// +// VideoCaptureHost serves video capture related messages from +// VideCaptureMessageFilter which lives inside the render process. +// +// This class is owned by BrowserRenderProcessHost, and instantiated on UI +// thread, but all other operations and method calls happen on IO thread. +// +// Here's an example of a typical IPC dialog for video capture: +// +// Renderer VideoCaptureHost +// | | +// | VideoCaptureHostMsg_Start > | +// | < VideoCaptureMsg_DeviceInfo | +// | | +// | < VideoCaptureMsg_StateChanged | +// | (kStarted) | +// | < VideoCaptureMsg_BufferReady | +// | ... | +// | < VideoCaptureMsg_BufferReady | +// | ... | +// | VideoCaptureHostMsg_BufferReady > | +// | VideoCaptureHostMsg_BufferReady > | +// | | +// | ... | +// | | +// | < VideoCaptureMsg_BufferReady | +// | VideoCaptureHostMsg_Stop > | +// | VideoCaptureHostMsg_BufferReady > | +// | < VideoCaptureMsg_StateChanged | +// | (kStopped) | +// v v + +#ifndef CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_HOST_H_ + +#include <map> + +#include "base/memory/ref_counted.h" +#include "content/browser/browser_message_filter.h" +#include "content/browser/renderer_host/video_capture_controller.h" +#include "ipc/ipc_message.h" + +class VideoCaptureHost : public BrowserMessageFilter, + public VideoCaptureController::EventHandler { + public: + VideoCaptureHost(); + + // BrowserMessageFilter implementation. + virtual void OnChannelClosing(); + virtual void OnDestruct() const; + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + // VideoCaptureController::EventHandler implementation. + virtual void OnError(VideoCaptureController::ControllerId id); + virtual void OnBufferReady(VideoCaptureController::ControllerId id, + TransportDIB::Handle handle, + base::Time timestamp); + virtual void OnFrameInfo(VideoCaptureController::ControllerId id, + int width, + int height, + int frame_per_second); + virtual void OnReadyToDelete(VideoCaptureController::ControllerId id); + + private: + friend class BrowserThread; + friend class DeleteTask<VideoCaptureHost>; + friend class MockVideoCaptureHost; + friend class VideoCaptureHostTest; + + virtual ~VideoCaptureHost(); + + // IPC message: Start capture on the VideoCaptureDevice referenced by + // VideoCaptureParams::session_id. device_id is an id created by + // VideCaptureMessageFilter to identify a session + // between a VideCaptureMessageFilter and a VideoCaptureHost. + void OnStartCapture(const IPC::Message& msg, int device_id, + const media::VideoCaptureParams& params); + + // IPC message: Stop capture on device referenced by device_id. + void OnStopCapture(const IPC::Message& msg, int device_id); + + // IPC message: Pause capture on device referenced by device_id. + void OnPauseCapture(const IPC::Message& msg, int device_id); + + // IPC message: Receive an empty buffer from renderer. Send it to device + // referenced by |device_id|. + void OnReceiveEmptyBuffer(const IPC::Message& msg, + int device_id, + TransportDIB::Handle handle); + + + // Called on the IO thread when VideoCaptureController have + // reported that all DIBs have been returned. + void DoDeleteVideoCaptureController(VideoCaptureController::ControllerId id); + + // Send a filled buffer to the VideoCaptureMessageFilter. + void DoSendFilledBuffer(int32 routing_id, + int device_id, + TransportDIB::Handle handle, + base::Time timestamp); + + // Send a information about frame resolution and frame rate + // to the VideoCaptureMessageFilter. + void DoSendFrameInfo(int32 routing_id, + int device_id, + int width, + int height, + int frame_per_second); + + // Handle error coming from VideoCaptureDevice. + void DoHandleError(int32 routing_id, int device_id); + + typedef std::map<VideoCaptureController::ControllerId, + scoped_refptr<VideoCaptureController> >EntryMap; + + // A map of VideoCaptureController::ControllerId to VideoCaptureController + // objects that is currently active. + EntryMap entries_; + + DISALLOW_COPY_AND_ASSIGN(VideoCaptureHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_VIDEO_CAPTURE_HOST_H_ diff --git a/content/browser/renderer_host/video_capture_host_unittest.cc b/content/browser/renderer_host/video_capture_host_unittest.cc new file mode 100644 index 0000000..7b029d6 --- /dev/null +++ b/content/browser/renderer_host/video_capture_host_unittest.cc @@ -0,0 +1,373 @@ +// 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/media_stream/video_capture_manager.h" +#include "content/browser/renderer_host/video_capture_host.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::AtLeast; +using ::testing::AnyNumber; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Mock; +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 width, 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", width, + height)); + file_.reset(file_util::OpenFile(file_name, "wb")); + expected_size_ = width * 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 (TransportDIB::is_valid_handle(handle)) { + IPC::Message msg; + msg.set_routing_id(kRouteId); + this->OnReceiveEmptyBuffer(msg, device_id, handle); + handle = GetReceivedDib(); + } + } + TransportDIB::Handle GetReceivedDib() { + if (filled_dib_.empty()) + return TransportDIB::DefaultHandleValue(); + 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* manager = + media_stream::VideoCaptureManager::Get(); + manager->UseFakeDevice(); +#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() { + // Verifies and removes the expectations on host_ and + // returns true iff successful. + Mock::VerifyAndClearExpectations(host_); + + EXPECT_CALL(*host_, OnStateChanged(kRouteId, kDeviceId, + media::VideoCapture::kStopped)) + .Times(AnyNumber()); + + // 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(AtLeast(1)); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnStopCapture(msg, kDeviceId); + host_->SetReturnReceviedDibs(true); + host_->ReturnReceivedDibs(kDeviceId); + + SyncWithVideoCaptureManagerThread(); + host_->SetReturnReceviedDibs(false); + // Expect the VideoCaptureDevice has been stopped + EXPECT_EQ(0u, host_->entries_.size()); + } + + 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); + VideoCaptureController::ControllerId id(kRouteId, kDeviceId); + host_->OnError(id); + SyncWithVideoCaptureManagerThread(); + } + + 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 diff --git a/content/content_browser.gypi b/content/content_browser.gypi index aba8917..bc04327 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -281,6 +281,10 @@ 'browser/renderer_host/sync_resource_handler.h', 'browser/renderer_host/x509_user_cert_resource_handler.cc', 'browser/renderer_host/x509_user_cert_resource_handler.h', + 'browser/renderer_host/video_capture_controller.cc', + 'browser/renderer_host/video_capture_controller.h', + 'browser/renderer_host/video_capture_host.cc', + 'browser/renderer_host/video_capture_host.h', 'browser/resolve_proxy_msg_helper.cc', 'browser/resolve_proxy_msg_helper.h', 'browser/resource_context.cc', |