summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--content/browser/renderer_host/video_capture_controller.cc225
-rw-r--r--content/browser/renderer_host/video_capture_controller.h116
-rw-r--r--content/browser/renderer_host/video_capture_host.cc186
-rw-r--r--content/browser/renderer_host/video_capture_host.h127
-rw-r--r--content/browser/renderer_host/video_capture_host_unittest.cc373
-rw-r--r--content/content_browser.gypi4
-rw-r--r--media/base/yuv_convert.cc21
-rw-r--r--media/base/yuv_convert.h16
-rw-r--r--media/base/yuv_convert_c.cc58
-rw-r--r--media/base/yuv_convert_internal.h19
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_