diff options
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-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 | ||||
-rw-r--r-- | media/base/yuv_convert.cc | 21 | ||||
-rw-r--r-- | media/base/yuv_convert.h | 16 | ||||
-rw-r--r-- | media/base/yuv_convert_c.cc | 58 | ||||
-rw-r--r-- | media/base/yuv_convert_internal.h | 19 |
11 files changed, 1146 insertions, 0 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 2e824e7..3636f98 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1930,6 +1930,7 @@ '../content/browser/renderer_host/render_widget_host_unittest.cc', '../content/browser/renderer_host/resource_dispatcher_host_unittest.cc', '../content/browser/renderer_host/resource_queue_unittest.cc', + '../content/browser/renderer_host/video_capture_host_unittest.cc', '../content/browser/resolve_proxy_msg_helper_unittest.cc', '../content/browser/site_instance_unittest.cc', '../content/browser/speech/endpointer/endpointer_unittest.cc', 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', diff --git a/media/base/yuv_convert.cc b/media/base/yuv_convert.cc index 2e4af1d..b4972083 100644 --- a/media/base/yuv_convert.cc +++ b/media/base/yuv_convert.cc @@ -374,4 +374,25 @@ void ConvertRGB32ToYUV(const uint8* rgbframe, rgbstride, ystride, uvstride); } +void ConvertRGB24ToYUV(const uint8* rgbframe, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height, + int rgbstride, + int ystride, + int uvstride) { + ConvertRGB24ToYUV_C(rgbframe, yplane, uplane, vplane, width, height, + rgbstride, ystride, uvstride); +} + +void ConvertYUY2ToYUV(const uint8* src, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height) { + ConvertYUY2ToYUV_C(src, yplane, uplane, vplane, width, height); +} } // namespace media diff --git a/media/base/yuv_convert.h b/media/base/yuv_convert.h index ab908b2..12dab83 100644 --- a/media/base/yuv_convert.h +++ b/media/base/yuv_convert.h @@ -77,6 +77,22 @@ void ConvertRGB32ToYUV(const uint8* rgbframe, int ystride, int uvstride); +void ConvertRGB24ToYUV(const uint8* rgbframe, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height, + int rgbstride, + int ystride, + int uvstride); + +void ConvertYUY2ToYUV(const uint8* src, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height); } // namespace media #endif // MEDIA_BASE_YUV_CONVERT_H_ diff --git a/media/base/yuv_convert_c.cc b/media/base/yuv_convert_c.cc index 986bf8e..b5a835b 100644 --- a/media/base/yuv_convert_c.cc +++ b/media/base/yuv_convert_c.cc @@ -48,4 +48,62 @@ void ConvertRGB32ToYUV_C(const uint8* rgbframe, } } +void ConvertRGB24ToYUV_C(const uint8* rgbframe, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height, + int rgbstride, + int ystride, + int uvstride) { + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + // Since the input pixel format is RGB24, there are 3 bytes per pixel. + const uint8* pixel = rgbframe + 3 * j; + yplane[j] = clip_byte(((pixel[2] * 66 + pixel[1] * 129 + + pixel[0] * 25 + 128) >> 8) + 16); + if (i % 2 == 0 && j % 2 == 0) { + uplane[j / 2] = clip_byte(((pixel[2] * -38 + pixel[1] * -74 + + pixel[0] * 112 + 128) >> 8) + 128); + vplane[j / 2] = clip_byte(((pixel[2] * 112 + pixel[1] * -94 + + pixel[1] * -18 + 128) >> 8) + 128); + } + } + + rgbframe += rgbstride; + yplane += ystride; + if (i % 2 == 0) { + uplane += uvstride; + vplane += uvstride; + } + } +} + +void ConvertYUY2ToYUV_C(const uint8* src, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height) { + for (int i = 0; i < height / 2; ++i) { + for (int j = 0; j < (width / 2); ++j) { + yplane[0] = src[0]; + *uplane = src[1]; + yplane[1] = src[2]; + *vplane = src[3]; + src += 4; + yplane += 2; + uplane++; + vplane++; + } + for (int j = 0; j < (width / 2); ++j) { + yplane[0] = src[0]; + yplane[1] = src[2]; + src += 4; + yplane += 2; + } + } +} + } // namespace media diff --git a/media/base/yuv_convert_internal.h b/media/base/yuv_convert_internal.h index 7a8ee34..da41be3 100644 --- a/media/base/yuv_convert_internal.h +++ b/media/base/yuv_convert_internal.h @@ -34,6 +34,25 @@ void ConvertRGB32ToYUV_C(const uint8* rgbframe, int ystride, int uvstride); +// C version of converting RGB24 to YV12. +void ConvertRGB24ToYUV_C(const uint8* rgbframe, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height, + int rgbstride, + int ystride, + int uvstride); + +// C version of converting YUY2 to YV12. +void ConvertYUY2ToYUV_C(const uint8* src, + uint8* yplane, + uint8* uplane, + uint8* vplane, + int width, + int height); + } // namespace media #endif // MEDIA_BASE_YUV_CONVERT_INTERNAL_H_ |