summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authorwjia@google.com <wjia@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-26 01:03:22 +0000
committerwjia@google.com <wjia@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-26 01:03:22 +0000
commitae70b1aaca4b3d8c74105fddef83dd153411cae2 (patch)
treedbb89ad89dd455b96182b7cac29e98daa026d3e7 /content
parent38eacdfec6492cf9f11e924ec9614d2294318ff7 (diff)
downloadchromium_src-ae70b1aaca4b3d8c74105fddef83dd153411cae2.zip
chromium_src-ae70b1aaca4b3d8c74105fddef83dd153411cae2.tar.gz
chromium_src-ae70b1aaca4b3d8c74105fddef83dd153411cae2.tar.bz2
Add VideoCaptureImpl and VideoCaptureImplManager
BUG=none TEST=try bots Review URL: http://codereview.chromium.org/6902166 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@86758 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/content_renderer.gypi4
-rw-r--r--content/renderer/media/video_capture_impl.cc386
-rw-r--r--content/renderer/media/video_capture_impl.h111
-rw-r--r--content/renderer/media/video_capture_impl_manager.cc115
-rw-r--r--content/renderer/media/video_capture_impl_manager.h67
-rw-r--r--content/renderer/media/video_capture_impl_unittest.cc107
-rw-r--r--content/renderer/video_capture_message_filter.cc24
-rw-r--r--content/renderer/video_capture_message_filter.h11
-rw-r--r--content/renderer/video_capture_message_filter_unittest.cc37
9 files changed, 845 insertions, 17 deletions
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index fc7e8683..b1d25d7 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -70,6 +70,10 @@
'renderer/media/gles2_video_decode_context.h',
'renderer/media/ipc_video_decoder.cc',
'renderer/media/ipc_video_decoder.h',
+ 'renderer/media/video_capture_impl.cc',
+ 'renderer/media/video_capture_impl.h',
+ 'renderer/media/video_capture_impl_manager.cc',
+ 'renderer/media/video_capture_impl_manager.h',
'renderer/navigation_state.cc',
'renderer/navigation_state.h',
'renderer/notification_provider.cc',
diff --git a/content/renderer/media/video_capture_impl.cc b/content/renderer/media/video_capture_impl.cc
new file mode 100644
index 0000000..faf4565
--- /dev/null
+++ b/content/renderer/media/video_capture_impl.cc
@@ -0,0 +1,386 @@
+// 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/renderer/media/video_capture_impl.h"
+
+#include "content/common/child_process.h"
+#include "content/common/video_capture_messages.h"
+
+VideoCaptureImpl::DIBBuffer::DIBBuffer(
+ TransportDIB* d, media::VideoCapture::VideoFrameBuffer* ptr)
+ : dib(d),
+ mapped_memory(ptr) {}
+
+VideoCaptureImpl::DIBBuffer::~DIBBuffer() {
+ delete dib;
+}
+
+bool VideoCaptureImpl::CaptureStarted() {
+ return state_ == kStarted;
+}
+
+int VideoCaptureImpl::CaptureWidth() {
+ return width_;
+}
+
+int VideoCaptureImpl::CaptureHeight() {
+ return height_;
+}
+
+int VideoCaptureImpl::CaptureFrameRate() {
+ return frame_rate_;
+}
+
+VideoCaptureImpl::VideoCaptureImpl(
+ const media::VideoCaptureSessionId id,
+ scoped_refptr<base::MessageLoopProxy> ml_proxy,
+ VideoCaptureMessageFilter* filter)
+ : VideoCapture(),
+ message_filter_(filter),
+ session_id_(id),
+ ml_proxy_(ml_proxy),
+ device_id_(0),
+ width_(0),
+ height_(0),
+ frame_rate_(0),
+ video_type_(media::VideoFrame::I420),
+ new_width_(0),
+ new_height_(0),
+ state_(kStopped) {
+ DCHECK(filter);
+}
+
+VideoCaptureImpl::~VideoCaptureImpl() {}
+
+void VideoCaptureImpl::Init() {
+ base::MessageLoopProxy* io_message_loop_proxy =
+ ChildProcess::current()->io_message_loop_proxy();
+
+ if (!io_message_loop_proxy->BelongsToCurrentThread()) {
+ io_message_loop_proxy->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::AddDelegateOnIOThread));
+ return;
+ }
+
+ AddDelegateOnIOThread();
+}
+
+void VideoCaptureImpl::DeInit(Task* task) {
+ if (state_ == kStarted)
+ message_filter_->Send(new VideoCaptureHostMsg_Stop(0, device_id_));
+
+ base::MessageLoopProxy* io_message_loop_proxy =
+ ChildProcess::current()->io_message_loop_proxy();
+
+ if (!io_message_loop_proxy->BelongsToCurrentThread()) {
+ io_message_loop_proxy->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::RemoveDelegateOnIOThread,
+ task));
+ return;
+ }
+
+ RemoveDelegateOnIOThread(task);
+}
+
+void VideoCaptureImpl::StartCapture(
+ media::VideoCapture::EventHandler* handler,
+ const VideoCaptureCapability& capability) {
+ DCHECK_EQ(capability.raw_type, media::VideoFrame::I420);
+
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::StartCapture, handler,
+ capability));
+ return;
+ }
+
+ ClientInfo::iterator it = pending_clients_.find(handler);
+
+ if (it != pending_clients_.end()) {
+ handler->OnError(this, 1);
+ return;
+ }
+
+ if (!device_id_) {
+ pending_clients_[handler] = capability;
+ return;
+ }
+
+ if (capability.resolution_fixed && master_clients_.size() &&
+ (capability.width != width_ || capability.height != height_)) {
+ // Can't have 2 master clients with different resolutions.
+ handler->OnError(this, 1);
+ return;
+ }
+
+ clients_[handler] = capability;
+ if (capability.resolution_fixed) {
+ master_clients_.push_back(handler);
+ if (master_clients_.size() > 1)
+ return;
+ }
+
+ if (state_ == kStarted) {
+ // Take the resolution of master client.
+ if (capability.resolution_fixed &&
+ (capability.width != width_ || capability.height != height_)) {
+ new_width_ = capability.width;
+ new_height_ = capability.height;
+ DLOG(INFO) << "StartCapture: Got master client with new resolution "
+ "during started, try to restart.";
+ StopDevice();
+ }
+ handler->OnStarted(this);
+ return;
+ }
+
+ if (state_ == kStopping) {
+ if (capability.resolution_fixed || !pending_start()) {
+ new_width_ = capability.width;
+ new_height_ = capability.height;
+ DLOG(INFO) << "StartCapture: Got new resolution, already in stopping.";
+ }
+ handler->OnStarted(this);
+ return;
+ }
+
+ DCHECK_EQ(clients_.size(), 1ul);
+ video_type_ = capability.raw_type;
+ new_width_ = 0;
+ new_height_ = 0;
+ width_ = capability.width;
+ height_ = capability.height;
+
+ StartCaptureInternal();
+}
+
+void VideoCaptureImpl::StopCapture(media::VideoCapture::EventHandler* handler) {
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::StopCapture, handler));
+ return;
+ }
+
+ ClientInfo::iterator it = pending_clients_.find(handler);
+ if (it != pending_clients_.end()) {
+ handler->OnStopped(this);
+ pending_clients_.erase(it);
+ return;
+ }
+
+ if (clients_.find(handler) == clients_.end())
+ return;
+
+ handler->OnStopped(this);
+ clients_.erase(handler);
+ master_clients_.remove(handler);
+
+ // Still have at least one master client.
+ if (master_clients_.size() > 0)
+ return;
+
+ // TODO(wjia): Is it really needed to handle resolution change for non-master
+ // clients, except no client case?
+ if (clients_.size() > 0) {
+ DLOG(INFO) << "StopCapture: No master client.";
+ int maxw = 0;
+ int maxh = 0;
+ for (ClientInfo::iterator it = clients_.begin();
+ it != clients_.end(); it++) {
+ if (it->second.width > maxw && it->second.height > maxh) {
+ maxw = it->second.width;
+ maxh = it->second.height;
+ }
+ }
+
+ if (state_ == kStarted) {
+ // Only handle resolution reduction.
+ if (maxw < width_ && maxh < height_) {
+ new_width_ = maxw;
+ new_height_ = maxh;
+ DLOG(INFO) << "StopCapture: New smaller resolution, stopping ...";
+ StopDevice();
+ }
+ return;
+ }
+
+ if (state_ == kStopping) {
+ new_width_ = maxw;
+ new_height_ = maxh;
+ DLOG(INFO) << "StopCapture: New resolution, during stopping.";
+ return;
+ }
+ } else {
+ new_width_ = width_ = 0;
+ new_height_ = height_ = 0;
+ DLOG(INFO) << "StopCapture: No more client, stopping ...";
+ StopDevice();
+ }
+}
+
+void VideoCaptureImpl::OnBufferReceived(TransportDIB::Handle handle,
+ base::Time timestamp) {
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::OnBufferReceived,
+ handle, timestamp));
+ return;
+ }
+
+ if (state_ != kStarted) {
+ message_filter_->Send(
+ new VideoCaptureHostMsg_BufferReady(0, device_id_, handle));
+ return;
+ }
+
+ media::VideoCapture::VideoFrameBuffer* buffer;
+ CachedDIB::iterator it;
+ for (it = cached_dibs_.begin(); it != cached_dibs_.end(); it++) {
+ if ((*it)->dib->handle() == handle)
+ break;
+ }
+ if (it == cached_dibs_.end()) {
+ TransportDIB* dib = TransportDIB::Map(handle);
+ buffer = new VideoFrameBuffer();
+ buffer->memory_pointer = dib->memory();
+ buffer->buffer_size = dib->size();
+ buffer->width = width_;
+ buffer->height = height_;
+
+ DIBBuffer* dib_buffer = new DIBBuffer(dib, buffer);
+ cached_dibs_.push_back(dib_buffer);
+ } else {
+ buffer = (*it)->mapped_memory;
+ }
+
+ // TODO(wjia): handle buffer sharing with downstream modules.
+ for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); it++) {
+ it->first->OnBufferReady(this, buffer);
+ }
+
+ message_filter_->Send(
+ new VideoCaptureHostMsg_BufferReady(0, device_id_, handle));
+}
+
+void VideoCaptureImpl::OnStateChanged(
+ const media::VideoCapture::State& state) {
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::OnStateChanged, state));
+ return;
+ }
+
+ switch (state) {
+ case media::VideoCapture::kStarted:
+ for (ClientInfo::iterator it = clients_.begin();
+ it != clients_.end(); it++) {
+ it->first->OnStarted(this);
+ }
+ break;
+ case media::VideoCapture::kStopped:
+ state_ = kStopped;
+ DLOG(INFO) << "OnStateChanged: stopped!, device_id = " << device_id_;
+ if (pending_start())
+ RestartCapture();
+ break;
+ case media::VideoCapture::kPaused:
+ for (ClientInfo::iterator it = clients_.begin();
+ it != clients_.end(); it++) {
+ it->first->OnPaused(this);
+ }
+ break;
+ case media::VideoCapture::kError:
+ for (ClientInfo::iterator it = clients_.begin();
+ it != clients_.end(); it++) {
+ // TODO(wjia): browser process would send error code.
+ it->first->OnError(this, 1);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void VideoCaptureImpl::OnDeviceInfoReceived(
+ const media::VideoCaptureParams& device_info) {
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::OnDeviceInfoReceived,
+ device_info));
+ return;
+ }
+
+ for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); it++) {
+ it->first->OnDeviceInfoReceived(this, device_info);
+ }
+}
+
+void VideoCaptureImpl::OnDelegateAdded(int32 device_id) {
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::OnDelegateAdded, device_id));
+ return;
+ }
+
+ device_id_ = device_id;
+ for (ClientInfo::iterator it = pending_clients_.begin();
+ it != pending_clients_.end(); ) {
+ media::VideoCapture::EventHandler* handler = it->first;
+ const VideoCaptureCapability capability = it->second;
+ pending_clients_.erase(it++);
+ StartCapture(handler, capability);
+ }
+}
+
+void VideoCaptureImpl::StopDevice() {
+ if (!ml_proxy_->BelongsToCurrentThread()) {
+ ml_proxy_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &VideoCaptureImpl::StopDevice));
+ return;
+ }
+
+ if (state_ == kStarted) {
+ state_ = kStopping;
+ message_filter_->Send(new VideoCaptureHostMsg_Stop(0, device_id_));
+ width_ = height_ = 0;
+ }
+}
+
+void VideoCaptureImpl::RestartCapture() {
+ DCHECK(ml_proxy_->BelongsToCurrentThread());
+ DCHECK_EQ(state_, kStopped);
+
+ width_ = new_width_;
+ height_ = new_height_;
+ new_width_ = 0;
+ new_height_ = 0;
+
+ DLOG(INFO) << "RestartCapture, " << width_ << ", " << height_;
+ StartCaptureInternal();
+}
+
+void VideoCaptureImpl::StartCaptureInternal() {
+ DCHECK(ml_proxy_->BelongsToCurrentThread());
+ DCHECK(device_id_);
+
+ media::VideoCaptureParams params;
+ params.width = width_;
+ params.height = height_;
+ params.session_id = session_id_;
+
+ message_filter_->Send(new VideoCaptureHostMsg_Start(0, device_id_, params));
+ state_ = kStarted;
+ for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); it++) {
+ it->first->OnStarted(this);
+ }
+}
+
+void VideoCaptureImpl::AddDelegateOnIOThread() {
+ message_filter_->AddDelegate(this);
+}
+
+void VideoCaptureImpl::RemoveDelegateOnIOThread(Task* task) {
+ message_filter_->RemoveDelegate(this);
+ media::AutoTaskRunner auto_runner(task);
+}
diff --git a/content/renderer/media/video_capture_impl.h b/content/renderer/media/video_capture_impl.h
new file mode 100644
index 0000000..4613e4b
--- /dev/null
+++ b/content/renderer/media/video_capture_impl.h
@@ -0,0 +1,111 @@
+// 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.
+
+// VideoCaptureImpl represents a capture device in renderer process. It provides
+// interfaces for clients to Start/Stop capture. It also communicates to clients
+// when buffer is ready, state of capture device is changed.
+
+#ifndef CONTENT_RENDERER_MEDIA_VIDEO_CAPTURE_IMPL_H_
+#define CONTENT_RENDERER_MEDIA_VIDEO_CAPTURE_IMPL_H_
+
+#include <list>
+#include <map>
+
+#include "content/renderer/video_capture_message_filter.h"
+#include "media/base/callback.h"
+#include "media/video/capture/video_capture.h"
+#include "ui/gfx/surface/transport_dib.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+class VideoCaptureImpl
+ : public media::VideoCapture,
+ public VideoCaptureMessageFilter::Delegate {
+ public:
+ // media::VideoCapture interface.
+ virtual void StartCapture(media::VideoCapture::EventHandler* handler,
+ const VideoCaptureCapability& capability);
+ virtual void StopCapture(media::VideoCapture::EventHandler* handler);
+ virtual bool CaptureStarted();
+ virtual int CaptureWidth();
+ virtual int CaptureHeight();
+ virtual int CaptureFrameRate();
+
+ // VideoCaptureMessageFilter::Delegate interface.
+ virtual void OnBufferReceived(TransportDIB::Handle handle,
+ base::Time timestamp);
+ virtual void OnStateChanged(const media::VideoCapture::State& state);
+ virtual void OnDeviceInfoReceived(
+ const media::VideoCaptureParams& device_info);
+ virtual void OnDelegateAdded(int32 device_id);
+
+ bool pending_start() {
+ return (new_width_ > 0 && new_height_ > 0);
+ }
+
+ private:
+ friend class VideoCaptureImplManager;
+ friend class VideoCaptureImplTest;
+
+ enum State {
+ kStarted,
+ kStopping,
+ kStopped
+ };
+
+ struct DIBBuffer {
+ public:
+ DIBBuffer(TransportDIB* d, media::VideoCapture::VideoFrameBuffer* ptr);
+ ~DIBBuffer();
+
+ TransportDIB* dib;
+ scoped_refptr<media::VideoCapture::VideoFrameBuffer> mapped_memory;
+ };
+
+ VideoCaptureImpl(media::VideoCaptureSessionId id,
+ scoped_refptr<base::MessageLoopProxy> ml_proxy,
+ VideoCaptureMessageFilter* filter);
+ virtual ~VideoCaptureImpl();
+
+ void Init();
+ void DeInit(Task* task);
+ void StopDevice();
+ void RestartCapture();
+ void StartCaptureInternal();
+ void AddDelegateOnIOThread();
+ void RemoveDelegateOnIOThread(Task* task);
+
+ scoped_refptr<VideoCaptureMessageFilter> message_filter_;
+ media::VideoCaptureSessionId session_id_;
+ scoped_refptr<base::MessageLoopProxy> ml_proxy_;
+ int device_id_;
+
+ // Pool of DIBs.
+ typedef std::list<DIBBuffer*> CachedDIB;
+ CachedDIB cached_dibs_;
+
+ typedef std::map<media::VideoCapture::EventHandler*, VideoCaptureCapability>
+ ClientInfo;
+ ClientInfo clients_;
+ std::list<media::VideoCapture::EventHandler*> master_clients_;
+
+ ClientInfo pending_clients_;
+
+ int width_;
+ int height_;
+ int frame_rate_;
+ media::VideoFrame::Format video_type_;
+
+ int new_width_;
+ int new_height_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureImpl);
+};
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(VideoCaptureImpl);
+
+#endif // CONTENT_RENDERER_MEDIA_VIDEO_CAPTURE_IMPL_H_
diff --git a/content/renderer/media/video_capture_impl_manager.cc b/content/renderer/media/video_capture_impl_manager.cc
new file mode 100644
index 0000000..902518f
--- /dev/null
+++ b/content/renderer/media/video_capture_impl_manager.cc
@@ -0,0 +1,115 @@
+// 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/renderer/media/video_capture_impl_manager.h"
+
+#include "base/memory/singleton.h"
+#include "content/common/child_process.h"
+#include "content/common/child_thread.h"
+#include "content/common/video_capture_messages.h"
+#include "content/common/view_messages.h"
+#include "media/base/message_loop_factory_impl.h"
+
+namespace {
+
+// VideoCaptureMessageFilterCreator is to be used as a singleton so we can get
+// access to a shared VideoCaptureMessageFilter.
+// Example usage:
+// VideoCaptureMessageFilter* filter =
+// VideoCaptureMessageFilterCreator::SharedFilter();
+
+class VideoCaptureMessageFilterCreator {
+ public:
+ VideoCaptureMessageFilterCreator() {
+ int routing_id;
+ ChildThread::current()->Send(
+ new ViewHostMsg_GenerateRoutingID(&routing_id));
+ filter_ = new VideoCaptureMessageFilter(routing_id);
+ filter_->AddFilter();
+ }
+
+ static VideoCaptureMessageFilter* SharedFilter() {
+ return GetInstance()->filter_.get();
+ }
+
+ static VideoCaptureMessageFilterCreator* GetInstance() {
+ return Singleton<VideoCaptureMessageFilterCreator>::get();
+ }
+
+ private:
+ scoped_refptr<VideoCaptureMessageFilter> filter_;
+};
+
+} // namespace
+
+VideoCaptureImplManager::VideoCaptureImplManager() {
+ ml_factory_.reset(new media::MessageLoopFactoryImpl());
+ ml_proxy_ = ml_factory_->GetMessageLoopProxy("VC manager");
+}
+
+VideoCaptureImplManager::~VideoCaptureImplManager() {}
+
+// static
+media::VideoCapture* VideoCaptureImplManager::AddDevice(
+ media::VideoCaptureSessionId id,
+ media::VideoCapture::EventHandler* handler) {
+ DCHECK(handler);
+ VideoCaptureImplManager* manager = GetInstance();
+
+ base::AutoLock auto_lock(manager->lock_);
+ Devices::iterator it = manager->devices_.find(id);
+ if (it == manager->devices_.end()) {
+ VideoCaptureImpl* vc =
+ new VideoCaptureImpl(id, manager->ml_proxy_,
+ VideoCaptureMessageFilterCreator::SharedFilter());
+ manager->devices_[id] = Device(vc, handler);
+ vc->Init();
+ return vc;
+ }
+
+ manager->devices_[id].clients.push_front(handler);
+ return it->second.vc;
+}
+
+// static
+void VideoCaptureImplManager::RemoveDevice(
+ media::VideoCaptureSessionId id,
+ media::VideoCapture::EventHandler* handler) {
+ DCHECK(handler);
+ VideoCaptureImplManager* manager = GetInstance();
+
+ base::AutoLock auto_lock(manager->lock_);
+ Devices::iterator it = manager->devices_.find(id);
+ if (it == manager->devices_.end())
+ return;
+
+ size_t size = it->second.clients.size();
+ it->second.clients.remove(handler);
+
+ if (size == it->second.clients.size() || size > 1)
+ return;
+
+ manager->devices_[id].vc->DeInit(NewRunnableMethod(manager,
+ &VideoCaptureImplManager::FreeDevice, manager->devices_[id].vc));
+ manager->devices_.erase(id);
+ return;
+}
+
+// static
+VideoCaptureImplManager* VideoCaptureImplManager::GetInstance() {
+ return Singleton<VideoCaptureImplManager>::get();
+}
+
+void VideoCaptureImplManager::FreeDevice(VideoCaptureImpl* vc) {
+ delete vc;
+}
+
+VideoCaptureImplManager::Device::Device() : vc(NULL) {}
+
+VideoCaptureImplManager::Device::Device(
+ VideoCaptureImpl* device,
+ media::VideoCapture::EventHandler* handler)
+ : vc(device) {
+ clients.push_front(handler);
+}
diff --git a/content/renderer/media/video_capture_impl_manager.h b/content/renderer/media/video_capture_impl_manager.h
new file mode 100644
index 0000000..1e93b51
--- /dev/null
+++ b/content/renderer/media/video_capture_impl_manager.h
@@ -0,0 +1,67 @@
+// 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.
+
+// VideoCaptureImplManager manages video capture devices in renderer process.
+// The video capture clients use AddDevice() to get a pointer to
+// video capture device. VideoCaputreImplManager supports multiple clients
+// accessing same device.
+
+#ifndef CONTENT_RENDERER_MEDIA_VIDEO_CAPTURE_IMPL_MANAGER_H_
+#define CONTENT_RENDERER_MEDIA_VIDEO_CAPTURE_IMPL_MANAGER_H_
+
+#include <list>
+#include <map>
+
+#include "base/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "content/renderer/media/video_capture_impl.h"
+#include "content/renderer/video_capture_message_filter.h"
+#include "media/base/callback.h"
+#include "media/base/message_loop_factory.h"
+#include "media/video/capture/video_capture.h"
+
+class VideoCaptureImplManager {
+ public:
+ VideoCaptureImplManager();
+ ~VideoCaptureImplManager();
+
+ // Called by video capture client |handler| to add device referenced
+ // by |id| to VideoCaptureImplManager's list of opened device list.
+ // A pointer to VideoCapture is returned to client so that client can
+ // operate on that pointer, such as StartCaptrue, StopCapture.
+ static media::VideoCapture* AddDevice(
+ media::VideoCaptureSessionId id,
+ media::VideoCapture::EventHandler* handler);
+
+ // Called by video capture client |handler| to remove device referenced
+ // by |id| from VideoCaptureImplManager's list of opened device list.
+ static void RemoveDevice(media::VideoCaptureSessionId id,
+ media::VideoCapture::EventHandler* handler);
+
+ static VideoCaptureImplManager* GetInstance();
+
+ private:
+ struct Device {
+ Device();
+ Device(VideoCaptureImpl* device,
+ media::VideoCapture::EventHandler* handler);
+
+ VideoCaptureImpl* vc;
+ std::list<media::VideoCapture::EventHandler*> clients;
+ };
+
+ void FreeDevice(VideoCaptureImpl* vc);
+
+ typedef std::map<media::VideoCaptureSessionId, Device> Devices;
+ Devices devices_;
+ base::Lock lock_;
+ scoped_refptr<base::MessageLoopProxy> ml_proxy_;
+ scoped_ptr<media::MessageLoopFactory> ml_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureImplManager);
+};
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(VideoCaptureImplManager);
+
+#endif // CONTENT_RENDERER_MEDIA_VIDEO_CAPTURE_IMPL_MANAGER_H_
diff --git a/content/renderer/media/video_capture_impl_unittest.cc b/content/renderer/media/video_capture_impl_unittest.cc
new file mode 100644
index 0000000..e3db8b6
--- /dev/null
+++ b/content/renderer/media/video_capture_impl_unittest.cc
@@ -0,0 +1,107 @@
+// 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 "base/message_loop.h"
+#include "content/renderer/media/video_capture_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+#define DEFAULT_CAPABILITY {176, 144, 30, 0, media::VideoFrame::I420, \
+ false, false }
+
+ACTION_P(DeleteMessage, return_value) {
+ delete arg0;
+ return return_value;
+}
+
+class MockVideoCaptureMessageFilter : public VideoCaptureMessageFilter {
+ public:
+ MockVideoCaptureMessageFilter() : VideoCaptureMessageFilter(1) {}
+ virtual ~MockVideoCaptureMessageFilter() {}
+
+ // Filter implementation.
+ MOCK_METHOD1(Send, bool(IPC::Message* message));
+ MOCK_METHOD0(ReadyToSend, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureMessageFilter);
+};
+
+class MockVideoCaptureClient : public media::VideoCapture::EventHandler {
+ public:
+ MockVideoCaptureClient() {}
+ virtual ~MockVideoCaptureClient() {}
+
+ // Filter implementation.
+ MOCK_METHOD1(OnStarted, void(media::VideoCapture* capture));
+ MOCK_METHOD1(OnStopped, void(media::VideoCapture* capture));
+ MOCK_METHOD1(OnPaused, void(media::VideoCapture* capture));
+ MOCK_METHOD2(OnError, void(media::VideoCapture* capture, int error_code));
+ MOCK_METHOD2(OnBufferReady,
+ void(media::VideoCapture* capture,
+ scoped_refptr<media::VideoCapture::VideoFrameBuffer> buf));
+ MOCK_METHOD2(OnDeviceInfoReceived,
+ void(media::VideoCapture* capture,
+ const media::VideoCaptureParams& device_info));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureClient);
+};
+
+class VideoCaptureImplTest : public ::testing::Test {
+ public:
+ VideoCaptureImplTest() {
+ message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO));
+ message_loop_proxy_ =
+ base::MessageLoopProxy::CreateForCurrentThread().get();
+
+ message_filter_ = new MockVideoCaptureMessageFilter;
+ session_id_ = 1;
+
+ video_capture_impl_ = new VideoCaptureImpl(session_id_, message_loop_proxy_,
+ message_filter_);
+
+ video_capture_impl_->device_id_ = 2;
+ }
+
+ virtual ~VideoCaptureImplTest() {
+ delete video_capture_impl_;
+ }
+
+ protected:
+ scoped_ptr<MessageLoop> message_loop_;
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
+ scoped_refptr<MockVideoCaptureMessageFilter> message_filter_;
+ media::VideoCaptureSessionId session_id_;
+ VideoCaptureImpl* video_capture_impl_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureImplTest);
+};
+
+TEST_F(VideoCaptureImplTest, Simple) {
+ // Execute SetCapture() and StopCapture().
+
+ scoped_ptr<MockVideoCaptureClient> client(new MockVideoCaptureClient);
+ media::VideoCapture::VideoCaptureCapability capability = DEFAULT_CAPABILITY;
+
+ EXPECT_CALL(*message_filter_, Send(_))
+ .WillRepeatedly(DeleteMessage(true));
+
+ EXPECT_CALL(*message_filter_, ReadyToSend())
+ .WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*client, OnStarted(_))
+ .WillOnce(Return());
+
+ video_capture_impl_->StartCapture(client.get(), capability);
+
+ EXPECT_CALL(*client, OnStopped(_))
+ .WillOnce(Return());
+
+ video_capture_impl_->StopCapture(client.get());
+}
diff --git a/content/renderer/video_capture_message_filter.cc b/content/renderer/video_capture_message_filter.cc
index c679e2b..f9db7e5 100644
--- a/content/renderer/video_capture_message_filter.cc
+++ b/content/renderer/video_capture_message_filter.cc
@@ -54,6 +54,13 @@ void VideoCaptureMessageFilter::OnFilterAdded(IPC::Channel* channel) {
// Captures the message loop proxy for IPC.
message_loop_proxy_ = base::MessageLoopProxy::CreateForCurrentThread();
channel_ = channel;
+
+ for (Delegates::iterator it = pending_delegates_.begin();
+ it != pending_delegates_.end(); it++) {
+ it->second->OnDelegateAdded(it->first);
+ delegates_[it->first] = it->second;
+ }
+ pending_delegates_.clear();
}
void VideoCaptureMessageFilter::OnFilterRemoved() {
@@ -114,14 +121,18 @@ void VideoCaptureMessageFilter::OnDeviceInfoReceived(
delegate->OnDeviceInfoReceived(params);
}
-int32 VideoCaptureMessageFilter::AddDelegate(Delegate* delegate) {
+void VideoCaptureMessageFilter::AddDelegate(Delegate* delegate) {
if (++last_device_id_ <= 0)
last_device_id_ = 1;
while (delegates_.find(last_device_id_) != delegates_.end())
last_device_id_++;
- delegates_[last_device_id_] = delegate;
- return last_device_id_;
+ if (channel_) {
+ delegates_[last_device_id_] = delegate;
+ delegate->OnDelegateAdded(last_device_id_);
+ } else {
+ pending_delegates_[last_device_id_] = delegate;
+ }
}
void VideoCaptureMessageFilter::RemoveDelegate(Delegate* delegate) {
@@ -132,6 +143,13 @@ void VideoCaptureMessageFilter::RemoveDelegate(Delegate* delegate) {
break;
}
}
+ for (Delegates::iterator it = pending_delegates_.begin();
+ it != pending_delegates_.end(); it++) {
+ if (it->second == delegate) {
+ pending_delegates_.erase(it);
+ break;
+ }
+ }
}
void VideoCaptureMessageFilter::AddFilter() {
diff --git a/content/renderer/video_capture_message_filter.h b/content/renderer/video_capture_message_filter.h
index 2a885e9..c476616 100644
--- a/content/renderer/video_capture_message_filter.h
+++ b/content/renderer/video_capture_message_filter.h
@@ -34,6 +34,10 @@ class VideoCaptureMessageFilter : public IPC::ChannelProxy::MessageFilter {
virtual void OnDeviceInfoReceived(
const media::VideoCaptureParams& device_info) = 0;
+ // Called when the delegate has been added to filter's delegate list.
+ // |device_id| is the device id for the delegate.
+ virtual void OnDelegateAdded(int32 device_id) = 0;
+
protected:
virtual ~Delegate() {}
};
@@ -41,14 +45,14 @@ class VideoCaptureMessageFilter : public IPC::ChannelProxy::MessageFilter {
explicit VideoCaptureMessageFilter(int32 route_id);
virtual ~VideoCaptureMessageFilter();
- // Add a delegate to the map and return id of the entry.
- int32 AddDelegate(Delegate* delegate);
+ // Add a delegate to the map.
+ void AddDelegate(Delegate* delegate);
// Remove a delegate from the map.
void RemoveDelegate(Delegate* delegate);
// Send a message asynchronously.
- bool Send(IPC::Message* message);
+ virtual bool Send(IPC::Message* message);
void AddFilter();
@@ -79,6 +83,7 @@ class VideoCaptureMessageFilter : public IPC::ChannelProxy::MessageFilter {
// A map of device ids to delegates.
Delegates delegates_;
+ Delegates pending_delegates_;
int32 last_device_id_;
int32 route_id_;
diff --git a/content/renderer/video_capture_message_filter_unittest.cc b/content/renderer/video_capture_message_filter_unittest.cc
index d41c709..1d647db 100644
--- a/content/renderer/video_capture_message_filter_unittest.cc
+++ b/content/renderer/video_capture_message_filter_unittest.cc
@@ -13,6 +13,8 @@ class MockVideoCaptureDelegate : public VideoCaptureMessageFilter::Delegate {
public:
MockVideoCaptureDelegate() {
Reset();
+ device_id_received_ = false;
+ device_id_ = 0;
}
virtual void OnBufferReceived(TransportDIB::Handle handle,
@@ -34,6 +36,11 @@ class MockVideoCaptureDelegate : public VideoCaptureMessageFilter::Delegate {
params_.frame_per_second = params.frame_per_second;
}
+ virtual void OnDelegateAdded(int32 device_id) {
+ device_id_received_ = true;
+ device_id_ = device_id;
+ }
+
void Reset() {
buffer_received_ = false;
handle_ = TransportDIB::DefaultHandleValue();
@@ -58,6 +65,9 @@ class MockVideoCaptureDelegate : public VideoCaptureMessageFilter::Delegate {
bool device_info_receive() { return device_info_received_; }
const media::VideoCaptureParams& received_device_info() { return params_; }
+ bool device_id_received() { return device_id_received_; }
+ int32 device_id() { return device_id_; }
+
private:
bool buffer_received_;
TransportDIB::Handle handle_;
@@ -69,6 +79,9 @@ class MockVideoCaptureDelegate : public VideoCaptureMessageFilter::Delegate {
bool device_info_received_;
media::VideoCaptureParams params_;
+ bool device_id_received_;
+ int32 device_id_;
+
DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureDelegate);
};
@@ -80,14 +93,15 @@ TEST(VideoCaptureMessageFilterTest, Basic) {
const int kRouteId = 0;
scoped_refptr<VideoCaptureMessageFilter> filter(
new VideoCaptureMessageFilter(kRouteId));
+ filter->channel_ = reinterpret_cast<IPC::Channel*>(1);
MockVideoCaptureDelegate delegate;
- int device_id = filter->AddDelegate(&delegate);
+ filter->AddDelegate(&delegate);
// VideoCaptureMsg_StateChanged
EXPECT_FALSE(delegate.state_changed_received());
filter->OnMessageReceived(
- VideoCaptureMsg_StateChanged(kRouteId, device_id,
+ VideoCaptureMsg_StateChanged(kRouteId, delegate.device_id(),
media::VideoCapture::kStarted));
EXPECT_TRUE(delegate.state_changed_received());
EXPECT_TRUE(media::VideoCapture::kStarted == delegate.state());
@@ -99,7 +113,7 @@ TEST(VideoCaptureMessageFilterTest, Basic) {
EXPECT_FALSE(delegate.buffer_received());
filter->OnMessageReceived(VideoCaptureMsg_BufferReady(
- kRouteId, device_id, handle, timestamp));
+ kRouteId, delegate.device_id(), handle, timestamp));
EXPECT_TRUE(delegate.buffer_received());
#if defined(OS_MACOSX)
EXPECT_EQ(handle.fd, delegate.received_buffer_handle().fd);
@@ -117,7 +131,7 @@ TEST(VideoCaptureMessageFilterTest, Basic) {
EXPECT_FALSE(delegate.device_info_receive());
filter->OnMessageReceived(VideoCaptureMsg_DeviceInfo(
- kRouteId, device_id, params));
+ kRouteId, delegate.device_id(), params));
EXPECT_TRUE(delegate.device_info_receive());
EXPECT_EQ(params.width, delegate.received_device_info().width);
EXPECT_EQ(params.height, delegate.received_device_info().height);
@@ -134,18 +148,19 @@ TEST(VideoCaptureMessageFilterTest, Delegates) {
const int kRouteId = 0;
scoped_refptr<VideoCaptureMessageFilter> filter(
new VideoCaptureMessageFilter(kRouteId));
+ filter->channel_ = reinterpret_cast<IPC::Channel*>(1);
MockVideoCaptureDelegate delegate1;
MockVideoCaptureDelegate delegate2;
- int device_id1 = filter->AddDelegate(&delegate1);
- int device_id2 = filter->AddDelegate(&delegate2);
+ filter->AddDelegate(&delegate1);
+ filter->AddDelegate(&delegate2);
// Send an IPC message. Make sure the correct delegate gets called.
EXPECT_FALSE(delegate1.state_changed_received());
EXPECT_FALSE(delegate2.state_changed_received());
filter->OnMessageReceived(
- VideoCaptureMsg_StateChanged(kRouteId, device_id1,
+ VideoCaptureMsg_StateChanged(kRouteId, delegate1.device_id(),
media::VideoCapture::kStarted));
EXPECT_TRUE(delegate1.state_changed_received());
EXPECT_FALSE(delegate2.state_changed_received());
@@ -154,7 +169,7 @@ TEST(VideoCaptureMessageFilterTest, Delegates) {
EXPECT_FALSE(delegate1.state_changed_received());
EXPECT_FALSE(delegate2.state_changed_received());
filter->OnMessageReceived(
- VideoCaptureMsg_StateChanged(kRouteId, device_id2,
+ VideoCaptureMsg_StateChanged(kRouteId, delegate2.device_id(),
media::VideoCapture::kStarted));
EXPECT_FALSE(delegate1.state_changed_received());
EXPECT_TRUE(delegate2.state_changed_received());
@@ -163,7 +178,7 @@ TEST(VideoCaptureMessageFilterTest, Delegates) {
// Send a message of a different route id, a message is not received.
EXPECT_FALSE(delegate1.state_changed_received());
filter->OnMessageReceived(
- VideoCaptureMsg_StateChanged(kRouteId+1, device_id1,
+ VideoCaptureMsg_StateChanged(kRouteId+1, delegate1.device_id(),
media::VideoCapture::kStarted));
EXPECT_FALSE(delegate1.state_changed_received());
@@ -171,14 +186,14 @@ TEST(VideoCaptureMessageFilterTest, Delegates) {
filter->RemoveDelegate(&delegate1);
EXPECT_FALSE(delegate1.state_changed_received());
filter->OnMessageReceived(
- VideoCaptureMsg_StateChanged(kRouteId, device_id1,
+ VideoCaptureMsg_StateChanged(kRouteId, delegate1.device_id(),
media::VideoCapture::kStarted));
EXPECT_FALSE(delegate1.state_changed_received());
filter->RemoveDelegate(&delegate2);
EXPECT_FALSE(delegate2.state_changed_received());
filter->OnMessageReceived(
- VideoCaptureMsg_StateChanged(kRouteId, device_id2,
+ VideoCaptureMsg_StateChanged(kRouteId, delegate2.device_id(),
media::VideoCapture::kStarted));
EXPECT_FALSE(delegate2.state_changed_received());