summaryrefslogtreecommitdiffstats
path: root/content/browser
diff options
context:
space:
mode:
authornick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-12 22:21:51 +0000
committernick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-12 22:21:51 +0000
commit8f5815242ddd4e395a4fa8259c578e66eacd7b5e (patch)
treef532faee488b39097ccc0afbb731eb21f7ab26a2 /content/browser
parentf800f9b964b97c62d905d7fc63676b9361b70b42 (diff)
downloadchromium_src-8f5815242ddd4e395a4fa8259c578e66eacd7b5e.zip
chromium_src-8f5815242ddd4e395a4fa8259c578e66eacd7b5e.tar.gz
chromium_src-8f5815242ddd4e395a4fa8259c578e66eacd7b5e.tar.bz2
Rewrite VideoCaptureManager to streamline the lifetimes of
VideoCaptureController vs. VideoCaptureDevice. The goal of this change is to eliminate code and statefulness from VCM, VCC and VCD and to move towards more consistent threading behavior. Move most of the state of the VCM to the IO thread only. Track both the VCC and the VCD objects in a single collection that lives on the IO thread. Move the allocation of the Controller to the IO thread. Always allocate a Controller before a Device, and have the Controller outlive its Device. Device creation and destruction is farmed out to the device thread, but the decision to create or destroy occurs on the IO thread. VCM::Open() no longer creates the device but instead just records the ID of the device to be created. De-duplication of active devices will occur when a client actually Starts() a session. From the Controller, the changes to the VCM enable some simplifications here. Remove the notion of pending_clients_ and device restart. This is possible to do because the decision of which devices are open lives on the IO thread, so we don't have to worry about inconsistencies. Grant the Controller sole responsibility for tracking its clients, rather than having the Manager maintain a parallel list of handlers. In turn, the Manager asks the Controller how many clients remain, and takes on responsibility for actually starting and stopping the Device. Removing the Controller's calls into the manager enables some new unit tests on the public API of the Controller. Two small, subtle bugfixes are included: [1] In VCC, we were sometimes still delivering events to clients after |session_close|. This was uncovered by unit tests. [2] In VCM, the "in_use" state of devices (meaning whether any client had opened the device) was being returned to the MediaStreamManager in the devices-enumerated event. This change sets it to always be false, as it would appear that the MSM intends |in_use| to indicate per- session, not a systemwide per- device, bit. Several new unit tests are included. TEST=Manual tests of windows webcam creation, basic tests of chromecast mirroring BUG=284829,289731,289684 Review URL: https://chromiumcodereview.appspot.com/22866015 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222883 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
-rw-r--r--content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc10
-rw-r--r--content/browser/renderer_host/media/video_capture_controller.cc196
-rw-r--r--content/browser/renderer_host/media/video_capture_controller.h102
-rw-r--r--content/browser/renderer_host/media/video_capture_controller_unittest.cc561
-rw-r--r--content/browser/renderer_host/media/video_capture_host.cc23
-rw-r--r--content/browser/renderer_host/media/video_capture_host_unittest.cc39
-rw-r--r--content/browser/renderer_host/media/video_capture_manager.cc644
-rw-r--r--content/browser/renderer_host/media/video_capture_manager.h215
-rw-r--r--content/browser/renderer_host/media/video_capture_manager_unittest.cc135
9 files changed, 1012 insertions, 913 deletions
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
index 05804266..2b7d683 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
@@ -274,16 +274,6 @@ TEST_F(MediaStreamDispatcherHostTest, GenerateThreeStreams) {
EXPECT_EQ(host_->NumberOfStreams(), 0u);
}
-TEST_F(MediaStreamDispatcherHostTest, FailOpenVideoDevice) {
- StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
-
- media::FakeVideoCaptureDevice::SetFailNextCreate();
- SetupFakeUI(false);
- EXPECT_CALL(*host_.get(),
- OnStreamGenerationFailed(kRenderId, kPageRequestId));
- GenerateStreamAndWaitForResult(kPageRequestId, options);
-}
-
TEST_F(MediaStreamDispatcherHostTest, CancelPendingStreamsOnChannelClosing) {
StreamOptions options(MEDIA_NO_SERVICE, MEDIA_DEVICE_VIDEO_CAPTURE);
diff --git a/content/browser/renderer_host/media/video_capture_controller.cc b/content/browser/renderer_host/media/video_capture_controller.cc
index ae10a6f..dbafeba 100644
--- a/content/browser/renderer_host/media/video_capture_controller.cc
+++ b/content/browser/renderer_host/media/video_capture_controller.cc
@@ -80,28 +80,34 @@ struct VideoCaptureController::ControllerClient {
// Buffers used by this client.
std::set<int> buffers;
- // State of capture session, controlled by VideoCaptureManager directly.
+ // State of capture session, controlled by VideoCaptureManager directly. This
+ // transitions to true as soon as StopSession() occurs, at which point the
+ // client is sent an OnEnded() event. However, because the client retains a
+ // VideoCaptureController* pointer, its ControllerClient entry lives on until
+ // it unregisters itself via RemoveClient(), which may happen asynchronously.
+ //
+ // TODO(nick): If we changed the semantics of VideoCaptureHost so that
+ // OnEnded() events were processed synchronously (with the RemoveClient() done
+ // implicitly), we could avoid tracking this state here in the Controller, and
+ // simplify the code in both places.
bool session_closed;
};
-VideoCaptureController::VideoCaptureController(
- VideoCaptureManager* video_capture_manager)
+VideoCaptureController::VideoCaptureController()
: chopped_width_(0),
chopped_height_(0),
frame_info_available_(false),
- video_capture_manager_(video_capture_manager),
- device_in_use_(false),
- state_(VIDEO_CAPTURE_STATE_STOPPED) {
+ state_(VIDEO_CAPTURE_STATE_STARTED) {
memset(&current_params_, 0, sizeof(current_params_));
}
-void VideoCaptureController::StartCapture(
+void VideoCaptureController::AddClient(
const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* event_handler,
base::ProcessHandle render_process,
const media::VideoCaptureParams& params) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DVLOG(1) << "VideoCaptureController::StartCapture, id " << id.device_id
+ DVLOG(1) << "VideoCaptureController::AddClient, id " << id.device_id
<< ", (" << params.width
<< ", " << params.height
<< ", " << params.frame_rate
@@ -114,71 +120,32 @@ void VideoCaptureController::StartCapture(
return;
}
- // Do nothing if this client has called StartCapture before.
- if (FindClient(id, event_handler, controller_clients_) ||
- FindClient(id, event_handler, pending_clients_))
+ // Do nothing if this client has called AddClient before.
+ if (FindClient(id, event_handler, controller_clients_))
return;
ControllerClient* client = new ControllerClient(id, event_handler,
render_process, params);
- // In case capture has been started, need to check different conditions.
+ // If we already have gotten frame_info from the device, repeat it to the new
+ // client.
if (state_ == VIDEO_CAPTURE_STATE_STARTED) {
- // TODO(wjia): Temporarily disable restarting till client supports resampling.
-#if 0
- // This client has higher resolution than what is currently requested.
- // Need restart capturing.
- if (params.width > current_params_.width ||
- params.height > current_params_.height) {
- video_capture_manager_->Stop(current_params_.session_id,
- base::Bind(&VideoCaptureController::OnDeviceStopped, this));
- frame_info_available_ = false;
- state_ = VIDEO_CAPTURE_STATE_STOPPING;
- pending_clients_.push_back(client);
- return;
- }
-#endif
-
- // This client's resolution is no larger than what's currently requested.
- // When frame_info has been returned by device, send them to client.
if (frame_info_available_) {
SendFrameInfoAndBuffers(client);
}
controller_clients_.push_back(client);
return;
}
-
- // In case the device is in the middle of stopping, put the client in
- // pending queue.
- if (state_ == VIDEO_CAPTURE_STATE_STOPPING) {
- pending_clients_.push_back(client);
- return;
- }
-
- // Fresh start.
- controller_clients_.push_back(client);
- current_params_ = params;
- // Order the manager to start the actual capture.
- video_capture_manager_->Start(params, this);
- state_ = VIDEO_CAPTURE_STATE_STARTED;
- device_in_use_ = true;
}
-void VideoCaptureController::StopCapture(
+int VideoCaptureController::RemoveClient(
const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* event_handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DVLOG(1) << "VideoCaptureController::StopCapture, id " << id.device_id;
+ DVLOG(1) << "VideoCaptureController::RemoveClient, id " << id.device_id;
- ControllerClient* client = FindClient(id, event_handler, pending_clients_);
- // If the client is still in pending queue, just remove it.
- if (client) {
- pending_clients_.remove(client);
- return;
- }
-
- client = FindClient(id, event_handler, controller_clients_);
+ ControllerClient* client = FindClient(id, event_handler, controller_clients_);
if (!client)
- return;
+ return kInvalidMediaCaptureSessionId;
// Take back all buffers held by the |client|.
if (buffer_pool_.get()) {
@@ -192,18 +159,10 @@ void VideoCaptureController::StopCapture(
client->buffers.clear();
int session_id = client->parameters.session_id;
- delete client;
controller_clients_.remove(client);
+ delete client;
- // No more clients. Stop device.
- if (controller_clients_.empty() &&
- (state_ == VIDEO_CAPTURE_STATE_STARTED ||
- state_ == VIDEO_CAPTURE_STATE_ERROR)) {
- video_capture_manager_->Stop(session_id,
- base::Bind(&VideoCaptureController::OnDeviceStopped, this));
- frame_info_available_ = false;
- state_ = VIDEO_CAPTURE_STATE_STOPPING;
- }
+ return session_id;
}
void VideoCaptureController::StopSession(
@@ -211,9 +170,7 @@ void VideoCaptureController::StopSession(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << "VideoCaptureController::StopSession, id " << session_id;
- ControllerClient* client = FindClient(session_id, pending_clients_);
- if (!client)
- client = FindClient(session_id, controller_clients_);
+ ControllerClient* client = FindClient(session_id, controller_clients_);
if (client) {
client->session_closed = true;
@@ -227,25 +184,18 @@ void VideoCaptureController::ReturnBuffer(
int buffer_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- ControllerClient* client = FindClient(id, event_handler,
- controller_clients_);
+ ControllerClient* client = FindClient(id, event_handler, controller_clients_);
// If this buffer is not held by this client, or this client doesn't exist
// in controller, do nothing.
if (!client ||
- client->buffers.find(buffer_id) == client->buffers.end())
+ client->buffers.find(buffer_id) == client->buffers.end()) {
+ NOTREACHED();
return;
+ }
client->buffers.erase(buffer_id);
buffer_pool_->RelinquishConsumerHold(buffer_id, 1);
-
- // When all buffers have been returned by clients and device has been
- // called to stop, check if restart is needed. This could happen when
- // capture needs to be restarted due to resolution change.
- if (!buffer_pool_->IsAnyBufferHeldForConsumers() &&
- state_ == VIDEO_CAPTURE_STATE_STOPPING) {
- PostStopping();
- }
}
scoped_refptr<media::VideoFrame> VideoCaptureController::ReserveOutputBuffer() {
@@ -637,15 +587,6 @@ VideoCaptureController::~VideoCaptureController() {
buffer_pool_ = NULL; // Release all buffers.
STLDeleteContainerPointers(controller_clients_.begin(),
controller_clients_.end());
- STLDeleteContainerPointers(pending_clients_.begin(),
- pending_clients_.end());
-}
-
-// Called by VideoCaptureManager when a device have been stopped.
-void VideoCaptureController::OnDeviceStopped() {
- BrowserThread::PostTask(BrowserThread::IO,
- FROM_HERE,
- base::Bind(&VideoCaptureController::DoDeviceStoppedOnIOThread, this));
}
void VideoCaptureController::DoIncomingCapturedFrameOnIOThread(
@@ -682,8 +623,7 @@ void VideoCaptureController::DoIncomingCapturedFrameOnIOThread(
void VideoCaptureController::DoFrameInfoOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DCHECK(!buffer_pool_.get())
- << "Device is restarted without releasing shared memory.";
+ DCHECK(!buffer_pool_.get()) << "Frame info should happen only once.";
// Allocate memory only when device has been started.
if (state_ != VIDEO_CAPTURE_STATE_STARTED)
@@ -698,11 +638,7 @@ void VideoCaptureController::DoFrameInfoOnIOThread() {
// Check whether all buffers were created successfully.
if (!buffer_pool->Allocate()) {
- state_ = VIDEO_CAPTURE_STATE_ERROR;
- for (ControllerClients::iterator client_it = controller_clients_.begin();
- client_it != controller_clients_.end(); ++client_it) {
- (*client_it)->event_handler->OnError((*client_it)->controller_id);
- }
+ DoErrorOnIOThread();
return;
}
@@ -714,6 +650,9 @@ void VideoCaptureController::DoFrameInfoOnIOThread() {
for (ControllerClients::iterator client_it = controller_clients_.begin();
client_it != controller_clients_.end(); ++client_it) {
+ if ((*client_it)->session_closed)
+ continue;
+
SendFrameInfoAndBuffers(*client_it);
}
}
@@ -725,6 +664,9 @@ void VideoCaptureController::DoFrameInfoChangedOnIOThread(
// needed, to support the new video capture format. See crbug.com/266082.
for (ControllerClients::iterator client_it = controller_clients_.begin();
client_it != controller_clients_.end(); ++client_it) {
+ if ((*client_it)->session_closed)
+ continue;
+
(*client_it)->event_handler->OnFrameInfoChanged(
(*client_it)->controller_id,
info.width,
@@ -739,19 +681,10 @@ void VideoCaptureController::DoErrorOnIOThread() {
ControllerClients::iterator client_it;
for (client_it = controller_clients_.begin();
client_it != controller_clients_.end(); ++client_it) {
- (*client_it)->event_handler->OnError((*client_it)->controller_id);
- }
- for (client_it = pending_clients_.begin();
- client_it != pending_clients_.end(); ++client_it) {
- (*client_it)->event_handler->OnError((*client_it)->controller_id);
- }
-}
+ if ((*client_it)->session_closed)
+ continue;
-void VideoCaptureController::DoDeviceStoppedOnIOThread() {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- device_in_use_ = false;
- if (state_ == VIDEO_CAPTURE_STATE_STOPPING) {
- PostStopping();
+ (*client_it)->event_handler->OnError((*client_it)->controller_id);
}
}
@@ -799,54 +732,9 @@ VideoCaptureController::FindClient(
return NULL;
}
-// This function is called when all buffers have been returned to controller,
-// or when device is stopped. It decides whether the device needs to be
-// restarted.
-void VideoCaptureController::PostStopping() {
+int VideoCaptureController::GetClientCount() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DCHECK_EQ(state_, VIDEO_CAPTURE_STATE_STOPPING);
-
- // When clients still have some buffers, or device has not been stopped yet,
- // do nothing.
- if ((buffer_pool_.get() && buffer_pool_->IsAnyBufferHeldForConsumers()) ||
- device_in_use_)
- return;
-
- {
- base::AutoLock lock(buffer_pool_lock_);
- buffer_pool_ = NULL;
- }
-
- // No more client. Therefore the controller is stopped.
- if (controller_clients_.empty() && pending_clients_.empty()) {
- state_ = VIDEO_CAPTURE_STATE_STOPPED;
- return;
- }
-
- // Restart the device.
- current_params_.width = 0;
- current_params_.height = 0;
- ControllerClients::iterator client_it;
- for (client_it = controller_clients_.begin();
- client_it != controller_clients_.end(); ++client_it) {
- if (current_params_.width < (*client_it)->parameters.width)
- current_params_.width = (*client_it)->parameters.width;
- if (current_params_.height < (*client_it)->parameters.height)
- current_params_.height = (*client_it)->parameters.height;
- }
- for (client_it = pending_clients_.begin();
- client_it != pending_clients_.end(); ) {
- if (current_params_.width < (*client_it)->parameters.width)
- current_params_.width = (*client_it)->parameters.width;
- if (current_params_.height < (*client_it)->parameters.height)
- current_params_.height = (*client_it)->parameters.height;
- controller_clients_.push_back((*client_it));
- pending_clients_.erase(client_it++);
- }
- // Request the manager to start the actual capture.
- video_capture_manager_->Start(current_params_, this);
- state_ = VIDEO_CAPTURE_STATE_STARTED;
- device_in_use_ = true;
+ return controller_clients_.size();
}
} // namespace content
diff --git a/content/browser/renderer_host/media/video_capture_controller.h b/content/browser/renderer_host/media/video_capture_controller.h
index 5d33d01..2c29450 100644
--- a/content/browser/renderer_host/media/video_capture_controller.h
+++ b/content/browser/renderer_host/media/video_capture_controller.h
@@ -1,17 +1,49 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-
-// 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 shared DIBs 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
-// shared DIBs to avoid a memory copy.
-// It serves multiple VideoCaptureControllerEventHandlers.
+//
+// VideoCaptureController is the glue between a VideoCaptureDevice and all
+// VideoCaptureHosts that have connected to it. A controller exists on behalf of
+// one (and only one) VideoCaptureDevice; both are owned by the
+// VideoCaptureManager.
+//
+// The VideoCaptureController is responsible for:
+//
+// * Allocating and keeping track of shared memory buffers, and filling them
+// with I420 video frames for IPC communication between VideoCaptureHost (in
+// the browser process) and VideoCaptureMessageFilter (in the renderer
+// process).
+// * Conveying events from the device thread (where capture devices live) to
+// the IO thread (where the clients can be reached).
+// * Broadcasting the events from a single VideoCaptureDevice, fanning them
+// out to multiple clients.
+// * Keeping track of the clients on behalf of the VideoCaptureManager, making
+// it possible for the Manager to delete the Controller and its Device when
+// there are no clients left.
+// * Performing some image transformations on the output of the Device;
+// specifically, colorspace conversion and rotation.
+//
+// Interactions between VideoCaptureController and other classes:
+//
+// * VideoCaptureController receives events from its VideoCaptureDevice
+// entirely through the VideoCaptureDevice::EventHandler interface, which
+// VCC implements.
+// * A VideoCaptureController interacts with its clients (VideoCaptureHosts)
+// via the VideoCaptureControllerEventHandler interface.
+// * Conversely, a VideoCaptureControllerEventHandler (typically,
+// VideoCaptureHost) will interact directly with VideoCaptureController to
+// return leased buffers by means of the ReturnBuffer() public method of
+// VCC.
+// * VideoCaptureManager (which owns the VCC) interacts directly with
+// VideoCaptureController through its public methods, to add and remove
+// clients.
+//
+// Thread safety:
+//
+// The public methods implementing the VCD::EventHandler interface are safe to
+// call from any thread -- in practice, this is the thread on which the Device
+// is running. For this reason, it is RefCountedThreadSafe. ALL OTHER METHODS
+// are only safe to run on the IO browser thread.
#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_H_
#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_CONTROLLER_H_
@@ -32,31 +64,32 @@
#include "media/video/capture/video_capture_types.h"
namespace content {
-class VideoCaptureManager;
class VideoCaptureBufferPool;
class CONTENT_EXPORT VideoCaptureController
: public base::RefCountedThreadSafe<VideoCaptureController>,
public media::VideoCaptureDevice::EventHandler {
public:
- VideoCaptureController(VideoCaptureManager* video_capture_manager);
+ VideoCaptureController();
// Start video capturing and try to use the resolution specified in
// |params|.
- // When capturing has started, the |event_handler| receives a call OnFrameInfo
- // with resolution that best matches the requested that the video
- // capture device support.
- void StartCapture(const VideoCaptureControllerID& id,
- VideoCaptureControllerEventHandler* event_handler,
- base::ProcessHandle render_process,
- const media::VideoCaptureParams& params);
-
- // Stop video capture.
- // This will take back all buffers held by by |event_handler|, and
- // |event_handler| shouldn't use those buffers any more.
- void StopCapture(const VideoCaptureControllerID& id,
+ // When capturing starts, the |event_handler| will receive an OnFrameInfo()
+ // call informing it of the resolution that was actually picked by the device.
+ void AddClient(const VideoCaptureControllerID& id,
+ VideoCaptureControllerEventHandler* event_handler,
+ base::ProcessHandle render_process,
+ const media::VideoCaptureParams& params);
+
+ // Stop video capture. This will take back all buffers held by by
+ // |event_handler|, and |event_handler| shouldn't use those buffers any more.
+ // Returns the session_id of the stopped client, or
+ // kInvalidMediaCaptureSessionId if the indicated client was not registered.
+ int RemoveClient(const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* event_handler);
+ int GetClientCount();
+
// API called directly by VideoCaptureManager in case the device is
// prematurely closed.
void StopSession(int session_id);
@@ -92,9 +125,6 @@ class CONTENT_EXPORT VideoCaptureController
struct ControllerClient;
typedef std::list<ControllerClient*> ControllerClients;
- // Callback when manager has stopped device.
- void OnDeviceStopped();
-
// Worker functions on IO thread.
void DoIncomingCapturedFrameOnIOThread(
const scoped_refptr<media::VideoFrame>& captured_frame,
@@ -118,10 +148,6 @@ class CONTENT_EXPORT VideoCaptureController
int session_id,
const ControllerClients& clients);
- // Decide what to do after kStopping state. Dependent on events, controller
- // can stay in kStopping state, or go to kStopped, or restart capture.
- void PostStopping();
-
// Protects access to the |buffer_pool_| pointer on non-IO threads. IO thread
// must hold this lock when modifying the |buffer_pool_| pointer itself.
// TODO(nick): Make it so that this lock isn't required.
@@ -133,9 +159,6 @@ class CONTENT_EXPORT VideoCaptureController
// All clients served by this controller.
ControllerClients controller_clients_;
- // All clients waiting for service.
- ControllerClients pending_clients_;
-
// The parameter that currently used for the capturing.
media::VideoCaptureParams current_params_;
@@ -144,19 +167,18 @@ class CONTENT_EXPORT VideoCaptureController
media::VideoCaptureCapability frame_info_;
// Chopped pixels in width/height in case video capture device has odd numbers
- // for width/height.
+ // for width/height. Accessed only on the device thread.
int chopped_width_;
int chopped_height_;
// It's accessed only on IO thread.
bool frame_info_available_;
- VideoCaptureManager* video_capture_manager_;
-
- bool device_in_use_;
+ // Takes on only the states 'STARTED' and 'ERROR'. 'ERROR' is an absorbing
+ // state which stops the flow of data to clients. Accessed only on IO thread.
VideoCaptureState state_;
- DISALLOW_IMPLICIT_CONSTRUCTORS(VideoCaptureController);
+ DISALLOW_COPY_AND_ASSIGN(VideoCaptureController);
};
} // namespace content
diff --git a/content/browser/renderer_host/media/video_capture_controller_unittest.cc b/content/browser/renderer_host/media/video_capture_controller_unittest.cc
index 5a0cffa..db1c1d9 100644
--- a/content/browser/renderer_host/media/video_capture_controller_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_controller_unittest.cc
@@ -7,141 +7,74 @@
#include <string>
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
-#include "content/browser/browser_thread_impl.h"
+#include "base/run_loop.h"
#include "content/browser/renderer_host/media/media_stream_provider.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
+#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/media/media_stream_options.h"
-#include "media/video/capture/fake_video_capture_device.h"
-#include "media/video/capture/video_capture_device.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.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::AnyNumber;
-using ::testing::AtLeast;
using ::testing::InSequence;
-using ::testing::Return;
+using ::testing::Mock;
namespace content {
-enum { kDeviceId = 1 };
-
-ACTION_P4(StopCapture, controller, controller_id, controller_handler,
- message_loop) {
- message_loop->PostTask(FROM_HERE,
- base::Bind(&VideoCaptureController::StopCapture,
- controller, controller_id, controller_handler));
- message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
-}
-
-ACTION_P3(StopSession, controller, session_id, message_loop) {
- message_loop->PostTask(FROM_HERE,
- base::Bind(&VideoCaptureController::StopSession,
- controller, session_id));
- message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
-}
-
class MockVideoCaptureControllerEventHandler
: public VideoCaptureControllerEventHandler {
public:
- MockVideoCaptureControllerEventHandler(VideoCaptureController* controller,
- base::MessageLoop* message_loop)
- : controller_(controller),
- message_loop_(message_loop),
- controller_id_(kDeviceId),
- process_handle_(base::kNullProcessHandle) {
- }
+ explicit MockVideoCaptureControllerEventHandler(
+ VideoCaptureController* controller)
+ : controller_(controller) {}
virtual ~MockVideoCaptureControllerEventHandler() {}
+ // These mock methods are delegated to by our fake implementation of
+ // VideoCaptureControllerEventHandler, to be used in EXPECT_CALL().
MOCK_METHOD1(DoBufferCreated, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoBufferReady, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoFrameInfo, void(const VideoCaptureControllerID&));
MOCK_METHOD1(DoEnded, void(const VideoCaptureControllerID&));
+ MOCK_METHOD1(DoError, void(const VideoCaptureControllerID&));
- virtual void OnError(const VideoCaptureControllerID& id) OVERRIDE {}
+ virtual void OnError(const VideoCaptureControllerID& id) OVERRIDE {
+ DoError(id);
+ }
virtual void OnBufferCreated(const VideoCaptureControllerID& id,
base::SharedMemoryHandle handle,
int length, int buffer_id) OVERRIDE {
- EXPECT_EQ(id, controller_id_);
DoBufferCreated(id);
}
virtual void OnBufferReady(const VideoCaptureControllerID& id,
int buffer_id,
base::Time timestamp) OVERRIDE {
- EXPECT_EQ(id, controller_id_);
DoBufferReady(id);
- message_loop_->PostTask(FROM_HERE,
+ base::MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&VideoCaptureController::ReturnBuffer,
- controller_, controller_id_, this, buffer_id));
+ controller_, id, this, buffer_id));
}
virtual void OnFrameInfo(
const VideoCaptureControllerID& id,
const media::VideoCaptureCapability& format) OVERRIDE {
- EXPECT_EQ(id, controller_id_);
DoFrameInfo(id);
}
virtual void OnEnded(const VideoCaptureControllerID& id) OVERRIDE {
- EXPECT_EQ(id, controller_id_);
DoEnded(id);
+ // OnEnded() must respond by (eventually) unregistering the client.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(base::IgnoreResult(&VideoCaptureController::RemoveClient),
+ controller_, id, this));
}
scoped_refptr<VideoCaptureController> controller_;
- base::MessageLoop* message_loop_;
- VideoCaptureControllerID controller_id_;
- base::ProcessHandle process_handle_;
-};
-
-class MockVideoCaptureManager : public VideoCaptureManager {
- public:
- MockVideoCaptureManager()
- : video_session_id_(kStartOpenSessionId),
- device_name_("fake_device_0", "/dev/video0") {}
-
- void Init() {
- video_capture_device_.reset(
- media::FakeVideoCaptureDevice::Create(device_name_));
- ASSERT_TRUE(video_capture_device_.get() != NULL);
- }
-
- MOCK_METHOD3(StartCapture, void(int, int,
- media::VideoCaptureDevice::EventHandler*));
- MOCK_METHOD1(StopCapture, void(const media::VideoCaptureSessionId&));
-
- void Start(const media::VideoCaptureParams& capture_params,
- media::VideoCaptureDevice::EventHandler* vc_receiver) OVERRIDE {
- StartCapture(capture_params.width, capture_params.height, vc_receiver);
- // TODO(mcasas): Add testing for variable resolution video capture devices,
- // supported by FakeVideoCaptureDevice. See crbug.com/261410, second part.
- media::VideoCaptureCapability capture_format(
- capture_params.width,
- capture_params.height,
- capture_params.frame_rate,
- media::PIXEL_FORMAT_I420,
- 0,
- false,
- media::ConstantResolutionVideoCaptureDevice);
- video_capture_device_->Allocate(capture_format, vc_receiver);
- video_capture_device_->Start();
- }
-
- void Stop(const media::VideoCaptureSessionId& capture_session_id,
- base::Closure stopped_cb) OVERRIDE {
- StopCapture(capture_session_id);
- video_capture_device_->Stop();
- video_capture_device_->DeAllocate();
- }
-
- int video_session_id_;
- media::VideoCaptureDevice::Name device_name_;
- scoped_ptr<media::VideoCaptureDevice> video_capture_device_;
-
- private:
- virtual ~MockVideoCaptureManager() {}
- DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureManager);
};
// Test class.
@@ -151,115 +84,369 @@ class VideoCaptureControllerTest : public testing::Test {
virtual ~VideoCaptureControllerTest() {}
protected:
+ static const int kPoolSize = 3;
+
virtual void SetUp() OVERRIDE {
- message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO));
- file_thread_.reset(new BrowserThreadImpl(BrowserThread::FILE,
- message_loop_.get()));
- io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO,
- message_loop_.get()));
-
- vcm_ = new MockVideoCaptureManager();
- vcm_->Init();
- controller_ = new VideoCaptureController(vcm_.get());
- controller_handler_.reset(new MockVideoCaptureControllerEventHandler(
- controller_.get(), message_loop_.get()));
+ controller_ = new VideoCaptureController();
+ client_a_.reset(new MockVideoCaptureControllerEventHandler(
+ controller_.get()));
+ client_b_.reset(new MockVideoCaptureControllerEventHandler(
+ controller_.get()));
}
- virtual void TearDown() OVERRIDE {}
+ virtual void TearDown() OVERRIDE {
+ base::RunLoop().RunUntilIdle();
+ }
- scoped_ptr<base::MessageLoop> message_loop_;
- scoped_ptr<BrowserThreadImpl> file_thread_;
- scoped_ptr<BrowserThreadImpl> io_thread_;
- scoped_refptr<MockVideoCaptureManager> vcm_;
- scoped_ptr<MockVideoCaptureControllerEventHandler> controller_handler_;
+ TestBrowserThreadBundle bindle_;
+ scoped_ptr<MockVideoCaptureControllerEventHandler> client_a_;
+ scoped_ptr<MockVideoCaptureControllerEventHandler> client_b_;
scoped_refptr<VideoCaptureController> controller_;
private:
DISALLOW_COPY_AND_ASSIGN(VideoCaptureControllerTest);
};
-// Try to start and stop capture.
-TEST_F(VideoCaptureControllerTest, StartAndStop) {
- media::VideoCaptureParams capture_params;
- capture_params.session_id = vcm_->video_session_id_;
- capture_params.width = 320;
- capture_params.height = 240;
- capture_params.frame_rate = 30;
-
- InSequence s;
- EXPECT_CALL(*vcm_.get(),
- StartCapture(capture_params.width,
- capture_params.height,
- controller_.get())).Times(1);
- EXPECT_CALL(*controller_handler_,
- DoFrameInfo(controller_handler_->controller_id_))
- .Times(AtLeast(1));
- EXPECT_CALL(*controller_handler_,
- DoBufferCreated(controller_handler_->controller_id_))
- .Times(AtLeast(1));
- EXPECT_CALL(*controller_handler_,
- DoBufferReady(controller_handler_->controller_id_))
- .Times(AtLeast(1))
- .WillOnce(StopCapture(controller_.get(),
- controller_handler_->controller_id_,
- controller_handler_.get(),
- message_loop_.get()));
- EXPECT_CALL(*vcm_.get(), StopCapture(vcm_->video_session_id_)).Times(1);
-
- controller_->StartCapture(controller_handler_->controller_id_,
- controller_handler_.get(),
- controller_handler_->process_handle_,
- capture_params);
- message_loop_->Run();
+// A simple test of VideoCaptureController's ability to add, remove, and keep
+// track of clients.
+TEST_F(VideoCaptureControllerTest, AddAndRemoveClients) {
+ media::VideoCaptureParams session_100;
+ session_100.session_id = 100;
+ session_100.width = 320;
+ session_100.height = 240;
+ session_100.frame_rate = 30;
+
+ media::VideoCaptureParams session_200 = session_100;
+ session_200.session_id = 200;
+
+ media::VideoCaptureParams session_300 = session_100;
+ session_300.session_id = 300;
+
+ media::VideoCaptureParams session_400 = session_100;
+ session_400.session_id = 400;
+
+ // Intentionally use the same route ID for two of the clients: the device_ids
+ // are a per-VideoCaptureHost namespace, and can overlap across hosts.
+ const VideoCaptureControllerID client_a_route_1(44);
+ const VideoCaptureControllerID client_a_route_2(30);
+ const VideoCaptureControllerID client_b_route_1(30);
+ const VideoCaptureControllerID client_b_route_2(1);
+
+ // Clients in controller: []
+ ASSERT_EQ(0, controller_->GetClientCount())
+ << "Client count should initially be zero.";
+ controller_->AddClient(client_a_route_1, client_a_.get(),
+ base::kNullProcessHandle, session_100);
+ // Clients in controller: [A/1]
+ ASSERT_EQ(1, controller_->GetClientCount())
+ << "Adding client A/1 should bump client count.";;
+ controller_->AddClient(client_a_route_2, client_a_.get(),
+ base::kNullProcessHandle, session_200);
+ // Clients in controller: [A/1, A/2]
+ ASSERT_EQ(2, controller_->GetClientCount())
+ << "Adding client A/2 should bump client count.";
+ controller_->AddClient(client_b_route_1, client_b_.get(),
+ base::kNullProcessHandle, session_300);
+ // Clients in controller: [A/1, A/2, B/1]
+ ASSERT_EQ(3, controller_->GetClientCount())
+ << "Adding client B/1 should bump client count.";
+ ASSERT_EQ(200,
+ controller_->RemoveClient(client_a_route_2, client_a_.get()))
+ << "Removing client A/1 should return its session_id.";
+ // Clients in controller: [A/1, B/1]
+ ASSERT_EQ(2, controller_->GetClientCount());
+ ASSERT_EQ(static_cast<int>(kInvalidMediaCaptureSessionId),
+ controller_->RemoveClient(client_a_route_2, client_a_.get()))
+ << "Removing a nonexistant client should fail.";
+ // Clients in controller: [A/1, B/1]
+ ASSERT_EQ(2, controller_->GetClientCount());
+ ASSERT_EQ(300,
+ controller_->RemoveClient(client_b_route_1, client_b_.get()))
+ << "Removing client B/1 should return its session_id.";
+ // Clients in controller: [A/1]
+ ASSERT_EQ(1, controller_->GetClientCount());
+ controller_->AddClient(client_b_route_2, client_b_.get(),
+ base::kNullProcessHandle, session_400);
+ // Clients in controller: [A/1, B/2]
+
+ EXPECT_CALL(*client_a_, DoEnded(client_a_route_1)).Times(1);
+ controller_->StopSession(100); // Session 100 == client A/1
+ Mock::VerifyAndClearExpectations(client_a_.get());
+ ASSERT_EQ(2, controller_->GetClientCount())
+ << "Client should be closed but still exist after StopSession.";
+ // Clients in controller: [A/1 (closed, removal pending), B/2]
+ base::RunLoop().RunUntilIdle();
+ // Clients in controller: [B/2]
+ ASSERT_EQ(1, controller_->GetClientCount())
+ << "Client A/1 should be deleted by now.";
+ controller_->StopSession(200); // Session 200 does not exist anymore
+ // Clients in controller: [B/2]
+ ASSERT_EQ(1, controller_->GetClientCount())
+ << "Stopping non-existant session 200 should be a no-op.";
+ controller_->StopSession(256); // Session 256 never existed.
+ // Clients in controller: [B/2]
+ ASSERT_EQ(1, controller_->GetClientCount())
+ << "Stopping non-existant session 256 should be a no-op.";
+ ASSERT_EQ(static_cast<int>(kInvalidMediaCaptureSessionId),
+ controller_->RemoveClient(client_a_route_1, client_a_.get()))
+ << "Removing already-removed client A/1 should fail.";
+ // Clients in controller: [B/2]
+ ASSERT_EQ(1, controller_->GetClientCount())
+ << "Removing non-existant session 200 should be a no-op.";
+ ASSERT_EQ(400,
+ controller_->RemoveClient(client_b_route_2, client_b_.get()))
+ << "Removing client B/2 should return its session_id.";
+ // Clients in controller: []
+ ASSERT_EQ(0, controller_->GetClientCount())
+ << "Client count should return to zero after all clients are gone.";
+}
+
+// This test will connect and disconnect several clients while simulating an
+// active capture device being started and generating frames. It runs on one
+// thread and is intended to behave deterministically.
+TEST_F(VideoCaptureControllerTest, NormalCaptureMultipleClients) {
+ media::VideoCaptureParams session_100;
+ session_100.session_id = 100;
+ session_100.width = 320;
+ session_100.height = 240;
+ session_100.frame_rate = 30;
+
+ media::VideoCaptureParams session_200 = session_100;
+ session_200.session_id = 200;
+
+ media::VideoCaptureParams session_300 = session_100;
+ session_300.session_id = 300;
+
+ // session_id of 1 is kStartOpenSessionId, which should have special meaning
+ // to VideoCaptureManager, but not to VideoCaptureController ... so test it.
+ media::VideoCaptureParams session_1 = session_100;
+ session_1.session_id = VideoCaptureManager::kStartOpenSessionId;
+
+ // The device format needn't match the VideoCaptureParams (the camera can do
+ // what it wants). Pick something random to use for OnFrameInfo.
+ media::VideoCaptureCapability device_format(
+ 10, 10, 25, media::PIXEL_FORMAT_RGB24, 10, false,
+ media::ConstantResolutionVideoCaptureDevice);
+
+ const VideoCaptureControllerID client_a_route_1(0xa1a1a1a1);
+ const VideoCaptureControllerID client_a_route_2(0xa2a2a2a2);
+ const VideoCaptureControllerID client_b_route_1(0xb1b1b1b1);
+ const VideoCaptureControllerID client_b_route_2(0xb2b2b2b2);
+
+ // Start with two clients.
+ controller_->AddClient(client_a_route_1, client_a_.get(),
+ base::kNullProcessHandle, session_100);
+ controller_->AddClient(client_b_route_1, client_b_.get(),
+ base::kNullProcessHandle, session_300);
+ ASSERT_EQ(2, controller_->GetClientCount());
+
+ {
+ InSequence s;
+ EXPECT_CALL(*client_a_, DoFrameInfo(client_a_route_1)).Times(1);
+ EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_1)).Times(kPoolSize);
+ }
+ {
+ InSequence s;
+ EXPECT_CALL(*client_b_, DoFrameInfo(client_b_route_1)).Times(1);
+ EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_1)).Times(kPoolSize);
+ }
+ // The Controller OnFrameInfo(), when processed, should turn into client
+ // OnFrameInfo() and OnBufferCreated() events.
+ controller_->OnFrameInfo(device_format);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+ Mock::VerifyAndClearExpectations(client_b_.get());
+
+ // When a third clients is subsequently added, the frame info and buffers
+ // should immediately be shared to the new clients.
+ {
+ InSequence s;
+ EXPECT_CALL(*client_a_, DoFrameInfo(client_a_route_2)).Times(1);
+ EXPECT_CALL(*client_a_, DoBufferCreated(client_a_route_2)).Times(kPoolSize);
+ }
+ controller_->AddClient(client_a_route_2, client_a_.get(),
+ base::kNullProcessHandle, session_200);
+ Mock::VerifyAndClearExpectations(client_a_.get());
+
+ // Now, simulate an incoming captured frame from the capture device.
+ uint8 frame_no = 1;
+ scoped_refptr<media::VideoFrame> frame;
+ frame = controller_->ReserveOutputBuffer();
+ ASSERT_TRUE(frame);
+ media::FillYUV(frame, frame_no++, 0x22, 0x44);
+ controller_->OnIncomingCapturedVideoFrame(frame, base::Time());
+ frame = NULL;
+
+ // The buffer should be delivered to the clients in any order.
+ EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_1)).Times(1);
+ EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_2)).Times(1);
+ EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_1)).Times(1);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+ Mock::VerifyAndClearExpectations(client_b_.get());
+
+ // Second frame. In this case pretend that the VideoFrame pointer is held
+ // by the device for a long delay. This shouldn't affect anything.
+ frame = controller_->ReserveOutputBuffer();
+ ASSERT_TRUE(frame);
+ media::FillYUV(frame, frame_no++, 0x22, 0x44);
+ controller_->OnIncomingCapturedVideoFrame(frame, base::Time());
+
+ // The buffer should be delivered to the clients in any order.
+ EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_1)).Times(1);
+ EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_2)).Times(1);
+ EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_1)).Times(1);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+ Mock::VerifyAndClearExpectations(client_b_.get());
+ frame = NULL;
+
+ // Add a fourth client now that some frames have come through. It should get
+ // the buffer info, but it won't get any frames until new ones are captured.
+ {
+ InSequence s;
+ EXPECT_CALL(*client_b_, DoFrameInfo(client_b_route_2)).Times(1);
+ EXPECT_CALL(*client_b_, DoBufferCreated(client_b_route_2)).Times(kPoolSize);
+ }
+ controller_->AddClient(client_b_route_2, client_b_.get(),
+ base::kNullProcessHandle, session_1);
+ Mock::VerifyAndClearExpectations(client_b_.get());
+
+ // Third, fourth, and fifth frames. Pretend they all arrive at the same time.
+ for (int i = 0; i < kPoolSize; i++) {
+ frame = controller_->ReserveOutputBuffer();
+ ASSERT_TRUE(frame);
+ ASSERT_EQ(media::VideoFrame::I420, frame->format());
+ media::FillYUV(frame, frame_no++, 0x22, 0x44);
+ controller_->OnIncomingCapturedVideoFrame(frame, base::Time());
+
+ }
+ // ReserveOutputBuffer ought to fail now, because the pool is depleted.
+ ASSERT_FALSE(controller_->ReserveOutputBuffer());
+ EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_1)).Times(kPoolSize);
+ EXPECT_CALL(*client_a_, DoBufferReady(client_a_route_2)).Times(kPoolSize);
+ EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_1)).Times(kPoolSize);
+ EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_2)).Times(kPoolSize);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+ Mock::VerifyAndClearExpectations(client_b_.get());
+
+ // Now test the interaction of client shutdown and frame delivery.
+ // Kill A1 via renderer disconnect (synchronous).
+ controller_->RemoveClient(client_a_route_1, client_a_.get());
+ // Kill B1 via session close (posts a task to disconnect).
+ EXPECT_CALL(*client_b_, DoEnded(client_b_route_1)).Times(1);
+ controller_->StopSession(300);
+ // Queue up another frame.
+ frame = controller_->ReserveOutputBuffer();
+ ASSERT_TRUE(frame);
+ media::FillYUV(frame, frame_no++, 0x22, 0x44);
+ controller_->OnIncomingCapturedVideoFrame(frame, base::Time());
+ frame = controller_->ReserveOutputBuffer();
+ {
+ // Kill A2 via session close (posts a task to disconnect, but A2 must not
+ // be sent either of these two frames)..
+ EXPECT_CALL(*client_a_, DoEnded(client_a_route_2)).Times(1);
+ controller_->StopSession(200);
+ }
+ ASSERT_TRUE(frame);
+ media::FillYUV(frame, frame_no++, 0x22, 0x44);
+ controller_->OnIncomingCapturedVideoFrame(frame, base::Time());
+ // B2 is the only client left, and is the only one that should
+ // get the frame.
+ EXPECT_CALL(*client_b_, DoBufferReady(client_b_route_2)).Times(2);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+ Mock::VerifyAndClearExpectations(client_b_.get());
+}
+
+// Exercises the OnError() codepath of VideoCaptureController, and tests the
+// behavior of various operations after the error state has been signalled.
+TEST_F(VideoCaptureControllerTest, ErrorBeforeDeviceCreation) {
+ media::VideoCaptureParams session_100;
+ session_100.session_id = 100;
+ session_100.width = 320;
+ session_100.height = 240;
+ session_100.frame_rate = 30;
+
+ media::VideoCaptureParams session_200 = session_100;
+ session_200.session_id = 200;
+
+ const VideoCaptureControllerID route_id(0x99);
+
+ // Start with one client.
+ controller_->AddClient(route_id, client_a_.get(),
+ base::kNullProcessHandle, session_100);
+ controller_->OnError();
+ EXPECT_CALL(*client_a_, DoError(route_id)).Times(1);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+
+ // Second client connects after the error state. It also should get told of
+ // the error.
+ EXPECT_CALL(*client_b_, DoError(route_id)).Times(1);
+ controller_->AddClient(route_id, client_b_.get(),
+ base::kNullProcessHandle, session_200);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_b_.get());
+
+ // OnFrameInfo from the VCD should become a no-op after the error occurs.
+ media::VideoCaptureCapability device_format(
+ 10, 10, 25, media::PIXEL_FORMAT_ARGB, 10, false,
+ media::ConstantResolutionVideoCaptureDevice);
+
+ controller_->OnFrameInfo(device_format);
+ base::RunLoop().RunUntilIdle();
}
-// Try to stop session before stopping capture.
-TEST_F(VideoCaptureControllerTest, StopSession) {
- media::VideoCaptureParams capture_params;
- capture_params.session_id = vcm_->video_session_id_;
- capture_params.width = 320;
- capture_params.height = 240;
- capture_params.frame_rate = 30;
-
- InSequence s;
- EXPECT_CALL(*vcm_.get(),
- StartCapture(capture_params.width,
- capture_params.height,
- controller_.get())).Times(1);
- EXPECT_CALL(*controller_handler_,
- DoFrameInfo(controller_handler_->controller_id_))
- .Times(AtLeast(1));
- EXPECT_CALL(*controller_handler_,
- DoBufferCreated(controller_handler_->controller_id_))
- .Times(AtLeast(1));
- EXPECT_CALL(*controller_handler_,
- DoBufferReady(controller_handler_->controller_id_))
- .Times(AtLeast(1))
- .WillOnce(StopSession(controller_.get(),
- vcm_->video_session_id_,
- message_loop_.get()));
- EXPECT_CALL(*controller_handler_,
- DoEnded(controller_handler_->controller_id_))
- .Times(1);
-
- controller_->StartCapture(controller_handler_->controller_id_,
- controller_handler_.get(),
- controller_handler_->process_handle_,
- capture_params);
- message_loop_->Run();
-
- // The session is stopped now. There should be no buffer coming from
- // controller.
- EXPECT_CALL(*controller_handler_,
- DoBufferReady(controller_handler_->controller_id_))
- .Times(0);
- message_loop_->PostDelayedTask(FROM_HERE,
- base::MessageLoop::QuitClosure(), base::TimeDelta::FromSeconds(1));
- message_loop_->Run();
-
- EXPECT_CALL(*vcm_.get(), StopCapture(vcm_->video_session_id_)).Times(1);
- controller_->StopCapture(controller_handler_->controller_id_,
- controller_handler_.get());
+// Exercises the OnError() codepath of VideoCaptureController, and tests the
+// behavior of various operations after the error state has been signalled.
+TEST_F(VideoCaptureControllerTest, ErrorAfterDeviceCreation) {
+ media::VideoCaptureParams session_100;
+ session_100.session_id = 100;
+ session_100.width = 320;
+ session_100.height = 240;
+ session_100.frame_rate = 30;
+
+ media::VideoCaptureParams session_200 = session_100;
+ session_200.session_id = 200;
+
+ const VideoCaptureControllerID route_id(0x99);
+
+ // Start with one client.
+ controller_->AddClient(route_id, client_a_.get(),
+ base::kNullProcessHandle, session_100);
+ // OnFrameInfo from the VCD should become a no-op after the error occurs.
+ media::VideoCaptureCapability device_format(
+ 10, 10, 25, media::PIXEL_FORMAT_ARGB, 10, false,
+ media::ConstantResolutionVideoCaptureDevice);
+
+ // Start the device and get as far as exchanging buffers with the subprocess.
+ // Then, signal an error and deliver the frame. The error should be propagated
+ // to clients; the frame should not be.
+ controller_->OnFrameInfo(device_format);
+ EXPECT_CALL(*client_a_, DoFrameInfo(route_id)).Times(1);
+ EXPECT_CALL(*client_a_, DoBufferCreated(route_id)).Times(kPoolSize);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+
+ scoped_refptr<media::VideoFrame> frame = controller_->ReserveOutputBuffer();
+ ASSERT_TRUE(frame);
+
+ controller_->OnError();
+ controller_->OnIncomingCapturedVideoFrame(frame, base::Time());
+ frame = NULL;
+
+ EXPECT_CALL(*client_a_, DoError(route_id)).Times(1);
+ base::RunLoop().RunUntilIdle();
+ Mock::VerifyAndClearExpectations(client_a_.get());
+
+ // Second client connects after the error state. It also should get told of
+ // the error.
+ EXPECT_CALL(*client_b_, DoError(route_id)).Times(1);
+ controller_->AddClient(route_id, client_b_.get(),
+ base::kNullProcessHandle, session_200);
+ Mock::VerifyAndClearExpectations(client_b_.get());
}
} // namespace content
diff --git a/content/browser/renderer_host/media/video_capture_host.cc b/content/browser/renderer_host/media/video_capture_host.cc
index f623d7d..9d3c634 100644
--- a/content/browser/renderer_host/media/video_capture_host.cc
+++ b/content/browser/renderer_host/media/video_capture_host.cc
@@ -32,14 +32,13 @@ VideoCaptureHost::~VideoCaptureHost() {}
void VideoCaptureHost::OnChannelClosing() {
BrowserMessageFilter::OnChannelClosing();
- // Since the IPC channel is gone, close all requested VideCaptureDevices.
+ // Since the IPC channel is gone, close all requested VideoCaptureDevices.
for (EntryMap::iterator it = entries_.begin(); it != entries_.end(); it++) {
VideoCaptureController* controller = it->second->controller.get();
if (controller) {
VideoCaptureControllerID controller_id(it->first);
- controller->StopCapture(controller_id, this);
- media_stream_manager_->video_capture_manager()->RemoveController(
- controller, this);
+ media_stream_manager_->video_capture_manager()->StopCaptureForClient(
+ controller, controller_id, this);
}
}
STLDeleteValues(&entries_);
@@ -224,9 +223,9 @@ void VideoCaptureHost::OnStartCapture(int device_id,
DCHECK(entries_.find(controller_id) == entries_.end());
entries_[controller_id] = new Entry(NULL);
- media_stream_manager_->video_capture_manager()->AddController(
- params, this, base::Bind(&VideoCaptureHost::OnControllerAdded, this,
- device_id, params));
+ media_stream_manager_->video_capture_manager()->StartCaptureForClient(
+ params, PeerHandle(), controller_id, this, base::Bind(
+ &VideoCaptureHost::OnControllerAdded, this, device_id, params));
}
void VideoCaptureHost::OnControllerAdded(
@@ -246,8 +245,8 @@ void VideoCaptureHost::DoControllerAddedOnIOThread(
EntryMap::iterator it = entries_.find(controller_id);
if (it == entries_.end()) {
if (controller) {
- media_stream_manager_->video_capture_manager()->RemoveController(
- controller, this);
+ media_stream_manager_->video_capture_manager()->StopCaptureForClient(
+ controller, controller_id, this);
}
return;
}
@@ -261,7 +260,6 @@ void VideoCaptureHost::DoControllerAddedOnIOThread(
}
it->second->controller = controller;
- controller->StartCapture(controller_id, this, PeerHandle(), params);
}
void VideoCaptureHost::OnStopCapture(int device_id) {
@@ -304,9 +302,8 @@ void VideoCaptureHost::DeleteVideoCaptureControllerOnIOThread(
VideoCaptureController* controller = it->second->controller.get();
if (controller) {
- controller->StopCapture(controller_id, this);
- media_stream_manager_->video_capture_manager()->RemoveController(
- controller, this);
+ media_stream_manager_->video_capture_manager()->StopCaptureForClient(
+ controller, controller_id, this);
}
delete it->second;
entries_.erase(controller_id);
diff --git a/content/browser/renderer_host/media/video_capture_host_unittest.cc b/content/browser/renderer_host/media/video_capture_host_unittest.cc
index b36476e..ca1891d 100644
--- a/content/browser/renderer_host/media/video_capture_host_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_host_unittest.cc
@@ -37,10 +37,10 @@ using ::testing::Return;
namespace content {
// Id used to identify the capture session between renderer and
-// video_capture_host.
-static const int kDeviceId = 1;
+// video_capture_host. This is an arbitrary value.
+static const int kDeviceId = 555;
// Id of a video capture device
-static const media::VideoCaptureSessionId kTestFakeDeviceId =
+static const media::VideoCaptureSessionId kTestFakeSessionId =
VideoCaptureManager::kStartOpenSessionId;
// Define to enable test where video is dumped to file.
@@ -253,13 +253,30 @@ class VideoCaptureHostTest : public testing::Test {
params.width = 352;
params.height = 288;
params.frame_rate = 30;
- params.session_id = kTestFakeDeviceId;
+ params.session_id = kTestFakeSessionId;
host_->OnStartCapture(kDeviceId, params);
run_loop.Run();
}
+ void StartStopCapture() {
+ // Quickly start and then stop capture, without giving much chance for
+ // asynchronous start operations to complete.
+ InSequence s;
+ base::RunLoop run_loop;
+ EXPECT_CALL(*host_.get(),
+ OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED));
+ media::VideoCaptureParams params;
+ params.width = 352;
+ params.height = 288;
+ params.frame_rate = 30;
+ params.session_id = kTestFakeSessionId;
+ host_->OnStartCapture(kDeviceId, params);
+ host_->OnStopCapture(kDeviceId);
+ run_loop.RunUntilIdle();
+ }
+
#ifdef DUMP_VIDEO
- void CaptureAndDumpVideo(int width, int heigt, int frame_rate) {
+ void CaptureAndDumpVideo(int width, int height, int frame_rate) {
InSequence s;
// 1. First - get info about the new resolution
EXPECT_CALL(*host_, OnDeviceInfo(kDeviceId));
@@ -275,9 +292,9 @@ class VideoCaptureHostTest : public testing::Test {
media::VideoCaptureParams params;
params.width = width;
- params.height = heigt;
- params.frame_per_second = frame_rate;
- params.session_id = kTestFakeDeviceId;
+ params.height = height;
+ params.frame_rate = frame_rate;
+ params.session_id = kTestFakeSessionId;
host_->SetDumpVideo(true);
host_->OnStartCapture(kDeviceId, params);
run_loop.Run();
@@ -339,6 +356,12 @@ TEST_F(VideoCaptureHostTest, StartCapture) {
StartCapture();
}
+// Disabled because of a sometimes race between completion of implicit device
+// enumeration and the capture stop. http://crbug.com/289684
+TEST_F(VideoCaptureHostTest, DISABLED_StopWhileStartOpening) {
+ StartStopCapture();
+}
+
TEST_F(VideoCaptureHostTest, StartCapturePlayStop) {
StartCapture();
NotifyPacketReady();
diff --git a/content/browser/renderer_host/media/video_capture_manager.cc b/content/browser/renderer_host/media/video_capture_manager.cc
index f066239..30b6c27 100644
--- a/content/browser/renderer_host/media/video_capture_manager.cc
+++ b/content/browser/renderer_host/media/video_capture_manager.cc
@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
@@ -33,20 +34,15 @@ namespace content {
// explicitly calling open device.
enum { kFirstSessionId = VideoCaptureManager::kStartOpenSessionId + 1 };
-struct VideoCaptureManager::Controller {
- Controller(
- VideoCaptureController* vc_controller,
- VideoCaptureControllerEventHandler* handler)
- : controller(vc_controller),
- ready_to_delete(false) {
- handlers.push_front(handler);
- }
- ~Controller() {}
+VideoCaptureManager::DeviceEntry::DeviceEntry(
+ MediaStreamType stream_type,
+ const std::string& id,
+ scoped_refptr<VideoCaptureController> controller)
+ : stream_type(stream_type),
+ id(id),
+ video_capture_controller(controller) {}
- scoped_refptr<VideoCaptureController> controller;
- bool ready_to_delete;
- Handlers handlers;
-};
+VideoCaptureManager::DeviceEntry::~DeviceEntry() {}
VideoCaptureManager::VideoCaptureManager()
: listener_(NULL),
@@ -56,7 +52,6 @@ VideoCaptureManager::VideoCaptureManager()
VideoCaptureManager::~VideoCaptureManager() {
DCHECK(devices_.empty());
- DCHECK(controllers_.empty());
}
void VideoCaptureManager::Register(MediaStreamProviderListener* listener,
@@ -75,109 +70,87 @@ void VideoCaptureManager::Unregister() {
void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureManager::EnumerateDevices, type " << stream_type;
DCHECK(listener_);
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnEnumerateDevices, this, stream_type));
+ base::PostTaskAndReplyWithResult(
+ device_loop_, FROM_HERE,
+ base::Bind(&VideoCaptureManager::GetAvailableDevicesOnDeviceThread, this,
+ stream_type),
+ base::Bind(&VideoCaptureManager::OnDevicesEnumerated, this, stream_type));
}
-int VideoCaptureManager::Open(const StreamDeviceInfo& device) {
+int VideoCaptureManager::Open(const StreamDeviceInfo& device_info) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(listener_);
- // Generate a new id for this device.
- int video_capture_session_id = new_capture_session_id_++;
+ // Generate a new id for the session being opened.
+ const int capture_session_id = new_capture_session_id_++;
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnOpen, this, video_capture_session_id,
- device));
+ DCHECK(sessions_.find(capture_session_id) == sessions_.end());
+ DVLOG(1) << "VideoCaptureManager::Open, id " << capture_session_id;
- return video_capture_session_id;
+ // We just save the stream info for processing later.
+ sessions_[capture_session_id] = device_info.device;
+
+ // Notify our listener asynchronously; this ensures that we return
+ // |capture_session_id| to the caller of this function before using that same
+ // id in a listener event.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnOpened, this,
+ device_info.device.type, capture_session_id));
+ return capture_session_id;
}
void VideoCaptureManager::Close(int capture_session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(listener_);
DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id;
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnClose, this, capture_session_id));
-}
-void VideoCaptureManager::Start(
- const media::VideoCaptureParams& capture_params,
- media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnStart, this, capture_params,
- video_capture_receiver));
-}
+ std::map<int, MediaStreamDevice>::iterator session_it =
+ sessions_.find(capture_session_id);
+ if (session_it == sessions_.end()) {
+ NOTREACHED();
+ return;
+ }
-void VideoCaptureManager::Stop(
- const media::VideoCaptureSessionId& capture_session_id,
- base::Closure stopped_cb) {
- DVLOG(1) << "VideoCaptureManager::Stop, id " << capture_session_id;
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnStop, this, capture_session_id,
- stopped_cb));
+ DeviceEntry* const existing_device = GetDeviceEntryForMediaStreamDevice(
+ session_it->second);
+ if (existing_device) {
+ // Remove any client that is still using the session. This is safe to call
+ // even if there are no clients using the session.
+ existing_device->video_capture_controller->StopSession(capture_session_id);
+
+ // StopSession() may have removed the last client, so we might need to
+ // close the device.
+ DestroyDeviceEntryIfNoClients(existing_device);
+ }
+
+ // Notify listeners asynchronously, and forget the session.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnClosed, this, session_it->second.type,
+ capture_session_id));
+ sessions_.erase(session_it);
}
void VideoCaptureManager::UseFakeDevice() {
use_fake_device_ = true;
}
-void VideoCaptureManager::OnEnumerateDevices(MediaStreamType stream_type) {
- SCOPED_UMA_HISTOGRAM_TIMER(
- "Media.VideoCaptureManager.OnEnumerateDevicesTime");
+void VideoCaptureManager::DoStartDeviceOnDeviceThread(
+ DeviceEntry* entry,
+ const media::VideoCaptureCapability& capture_params,
+ media::VideoCaptureDevice::EventHandler* controller_as_handler) {
+ SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StartDeviceTime");
DCHECK(IsOnDeviceThread());
- media::VideoCaptureDevice::Names device_names;
- GetAvailableDevices(stream_type, &device_names);
-
- scoped_ptr<StreamDeviceInfoArray> devices(new StreamDeviceInfoArray());
- for (media::VideoCaptureDevice::Names::iterator it =
- device_names.begin(); it != device_names.end(); ++it) {
- bool opened = DeviceOpened(*it);
- devices->push_back(StreamDeviceInfo(
- stream_type, it->GetNameAndModel(), it->id(), opened));
- }
-
- PostOnDevicesEnumerated(stream_type, devices.Pass());
-}
-
-void VideoCaptureManager::OnOpen(int capture_session_id,
- const StreamDeviceInfo& device) {
- SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnOpenTime");
- DCHECK(IsOnDeviceThread());
- DCHECK(devices_.find(capture_session_id) == devices_.end());
- DVLOG(1) << "VideoCaptureManager::OnOpen, id " << capture_session_id;
-
- // Check if another session has already opened this device. If so, just
- // use that opened device.
- media::VideoCaptureDevice* opened_video_capture_device =
- GetOpenedDevice(device);
- if (opened_video_capture_device) {
- DeviceEntry& new_entry = devices_[capture_session_id];
- new_entry.stream_type = device.device.type;
- new_entry.capture_device = opened_video_capture_device;
- PostOnOpened(device.device.type, capture_session_id);
- return;
- }
-
scoped_ptr<media::VideoCaptureDevice> video_capture_device;
-
- // Open the device.
- switch (device.device.type) {
+ switch (entry->stream_type) {
case MEDIA_DEVICE_VIDEO_CAPTURE: {
// We look up the device id from the renderer in our local enumeration
// since the renderer does not have all the information that might be
// held in the browser-side VideoCaptureDevice::Name structure.
media::VideoCaptureDevice::Name* found =
- video_capture_devices_.FindById(device.device.id);
+ video_capture_devices_.FindById(entry->id);
if (found) {
video_capture_device.reset(use_fake_device_ ?
media::FakeVideoCaptureDevice::Create(*found) :
@@ -187,12 +160,12 @@ void VideoCaptureManager::OnOpen(int capture_session_id,
}
case MEDIA_TAB_VIDEO_CAPTURE: {
video_capture_device.reset(
- WebContentsVideoCaptureDevice::Create(device.device.id));
+ WebContentsVideoCaptureDevice::Create(entry->id));
break;
}
case MEDIA_DESKTOP_VIDEO_CAPTURE: {
#if defined(ENABLE_SCREEN_CAPTURE)
- DesktopMediaID id = DesktopMediaID::Parse(device.device.id);
+ DesktopMediaID id = DesktopMediaID::Parse(entry->id);
if (id.type != DesktopMediaID::TYPE_NONE) {
video_capture_device = DesktopCaptureDevice::Create(id);
}
@@ -206,128 +179,133 @@ void VideoCaptureManager::OnOpen(int capture_session_id,
}
if (!video_capture_device) {
- PostOnError(capture_session_id, kDeviceNotAvailable);
+ controller_as_handler->OnError();
return;
}
- DeviceEntry& new_entry = devices_[capture_session_id];
- new_entry.stream_type = device.device.type;
- new_entry.capture_device = video_capture_device.release();
- PostOnOpened(device.device.type, capture_session_id);
+ // TODO(nick): Merge Allocate() and Start(). http://crbug.com/285562
+ video_capture_device->Allocate(capture_params, controller_as_handler);
+ video_capture_device->Start();
+ entry->video_capture_device = video_capture_device.Pass();
}
-void VideoCaptureManager::OnClose(int capture_session_id) {
- SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnCloseTime");
- DCHECK(IsOnDeviceThread());
- DVLOG(1) << "VideoCaptureManager::OnClose, id " << capture_session_id;
-
- VideoCaptureDevices::iterator device_it = devices_.find(capture_session_id);
- if (device_it == devices_.end()) {
+void VideoCaptureManager::StartCaptureForClient(
+ const media::VideoCaptureParams& capture_params,
+ base::ProcessHandle client_render_process,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler,
+ base::Callback<void(VideoCaptureController*)> done_cb) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DVLOG(1) << "VideoCaptureManager::StartCaptureForClient, ("
+ << capture_params.width
+ << ", " << capture_params.height
+ << ", " << capture_params.frame_rate
+ << ", #" << capture_params.session_id
+ << ")";
+
+ if (capture_params.session_id == kStartOpenSessionId) {
+ // Solution for not using MediaStreamManager. Enumerate the devices and
+ // open the first one, and then start it.
+ base::PostTaskAndReplyWithResult(device_loop_, FROM_HERE,
+ base::Bind(&VideoCaptureManager::GetAvailableDevicesOnDeviceThread,
+ this, MEDIA_DEVICE_VIDEO_CAPTURE),
+ base::Bind(&VideoCaptureManager::OpenAndStartDefaultSession, this,
+ capture_params, client_render_process, client_id,
+ client_handler, done_cb));
return;
+ } else {
+ DoStartCaptureForClient(capture_params, client_render_process, client_id,
+ client_handler, done_cb);
}
- const DeviceEntry removed_entry = device_it->second;
- devices_.erase(device_it);
-
- Controllers::iterator cit = controllers_.find(removed_entry.capture_device);
- if (cit != controllers_.end()) {
- BrowserThread::PostTask(
- BrowserThread::IO, FROM_HERE,
- base::Bind(&VideoCaptureController::StopSession,
- cit->second->controller, capture_session_id));
- }
+}
- if (!DeviceInUse(removed_entry.capture_device)) {
- // No other users of this device, deallocate (if not done already) and
- // delete the device. No need to take care of the controller, that is done
- // by |OnStop|.
- removed_entry.capture_device->DeAllocate();
- Controllers::iterator cit = controllers_.find(removed_entry.capture_device);
- if (cit != controllers_.end()) {
- delete cit->second;
- controllers_.erase(cit);
- }
- delete removed_entry.capture_device;
+void VideoCaptureManager::DoStartCaptureForClient(
+ const media::VideoCaptureParams& capture_params,
+ base::ProcessHandle client_render_process,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler,
+ base::Callback<void(VideoCaptureController*)> done_cb) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ DeviceEntry* entry = GetOrCreateDeviceEntry(capture_params.session_id);
+ if (!entry) {
+ done_cb.Run(NULL);
+ return;
}
- PostOnClosed(removed_entry.stream_type, capture_session_id);
-}
+ DCHECK(entry->video_capture_controller);
-void VideoCaptureManager::OnStart(
- const media::VideoCaptureParams capture_params,
- media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
- SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnStartTime");
- DCHECK(IsOnDeviceThread());
- DCHECK(video_capture_receiver != NULL);
- DVLOG(1) << "VideoCaptureManager::OnStart, (" << capture_params.width
- << ", " << capture_params.height
- << ", " << capture_params.frame_rate
- << ", " << capture_params.session_id
- << ")";
-
- media::VideoCaptureDevice* video_capture_device =
- GetDeviceInternal(capture_params.session_id);
- if (!video_capture_device) {
- // Invalid session id.
- video_capture_receiver->OnError();
- return;
+ // First client starts the device.
+ if (entry->video_capture_controller->GetClientCount() == 0) {
+ DVLOG(1) << "VideoCaptureManager starting device (type = "
+ << entry->stream_type << ", id = " << entry->id << ")";
+
+ media::VideoCaptureCapability params_as_capability;
+ params_as_capability.width = capture_params.width;
+ params_as_capability.height = capture_params.height;
+ params_as_capability.frame_rate = capture_params.frame_rate;
+ params_as_capability.session_id = capture_params.session_id;
+ params_as_capability.frame_size_type = capture_params.frame_size_type;
+
+ device_loop_->PostTask(FROM_HERE, base::Bind(
+ &VideoCaptureManager::DoStartDeviceOnDeviceThread, this,
+ entry, params_as_capability, entry->video_capture_controller));
}
- // TODO(mcasas): Variable resolution video capture devices, are not yet
- // fully supported, see crbug.com/261410, second part, and crbug.com/266082 .
- if (capture_params.frame_size_type !=
- media::ConstantResolutionVideoCaptureDevice) {
- LOG(DFATAL) << "Only constant Video Capture resolution device supported.";
- video_capture_receiver->OnError();
+ // Run the callback first, as AddClient() may trigger OnFrameInfo().
+ done_cb.Run(entry->video_capture_controller);
+ entry->video_capture_controller->AddClient(client_id,
+ client_handler,
+ client_render_process,
+ capture_params);
+}
+
+void VideoCaptureManager::StopCaptureForClient(
+ VideoCaptureController* controller,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ DCHECK(controller);
+ DCHECK(client_handler);
+
+ DeviceEntry* entry = GetDeviceEntryForController(controller);
+ if (!entry) {
+ NOTREACHED();
return;
}
- Controllers::iterator cit = controllers_.find(video_capture_device);
- if (cit != controllers_.end()) {
- cit->second->ready_to_delete = false;
- }
- // Possible errors are signaled to video_capture_receiver by
- // video_capture_device. video_capture_receiver to perform actions.
- media::VideoCaptureCapability params_as_capability_copy;
- params_as_capability_copy.width = capture_params.width;
- params_as_capability_copy.height = capture_params.height;
- params_as_capability_copy.frame_rate = capture_params.frame_rate;
- params_as_capability_copy.session_id = capture_params.session_id;
- params_as_capability_copy.frame_size_type = capture_params.frame_size_type;
- video_capture_device->Allocate(params_as_capability_copy,
- video_capture_receiver);
- video_capture_device->Start();
+ // Detach client from controller.
+ int session_id = controller->RemoveClient(client_id, client_handler);
+ DVLOG(1) << "VideoCaptureManager::StopCaptureForClient, session_id = "
+ << session_id;
+
+ // If controller has no more clients, delete controller and device.
+ DestroyDeviceEntryIfNoClients(entry);
+
+ // Close the session if it was auto-opened by StartCaptureForClient().
+ if (session_id == kStartOpenSessionId) {
+ sessions_.erase(session_id);
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnClosed, this,
+ MEDIA_DEVICE_VIDEO_CAPTURE, kStartOpenSessionId));
+ }
}
-void VideoCaptureManager::OnStop(
- const media::VideoCaptureSessionId capture_session_id,
- base::Closure stopped_cb) {
- SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnStopTime");
+void VideoCaptureManager::DoStopDeviceOnDeviceThread(DeviceEntry* entry) {
+ SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StopDeviceTime");
DCHECK(IsOnDeviceThread());
- DVLOG(1) << "VideoCaptureManager::OnStop, id " << capture_session_id;
-
- VideoCaptureDevices::iterator it = devices_.find(capture_session_id);
- if (it != devices_.end()) {
- media::VideoCaptureDevice* video_capture_device = it->second.capture_device;
- // Possible errors are signaled to video_capture_receiver by
- // video_capture_device. video_capture_receiver to perform actions.
- video_capture_device->Stop();
- video_capture_device->DeAllocate();
- Controllers::iterator cit = controllers_.find(video_capture_device);
- if (cit != controllers_.end()) {
- cit->second->ready_to_delete = true;
- if (cit->second->handlers.empty()) {
- delete cit->second;
- controllers_.erase(cit);
- }
- }
+ if (entry->video_capture_device) {
+ // TODO(nick): Merge Stop() and DeAllocate(). http://crbug.com/285562
+ entry->video_capture_device->Stop();
+ entry->video_capture_device->DeAllocate();
+ entry->video_capture_device.reset();
}
+}
- if (!stopped_cb.is_null())
- stopped_cb.Run();
-
- if (capture_session_id == kStartOpenSessionId) {
- // This device was opened from Start(), not Open(). Close it!
- OnClose(capture_session_id);
- }
+void VideoCaptureManager::FreeDeviceEntryOnIOThread(
+ scoped_ptr<DeviceEntry> entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ entry->video_capture_controller = NULL;
+ entry.reset();
}
void VideoCaptureManager::OnOpened(MediaStreamType stream_type,
@@ -352,242 +330,174 @@ void VideoCaptureManager::OnClosed(MediaStreamType stream_type,
void VideoCaptureManager::OnDevicesEnumerated(
MediaStreamType stream_type,
- scoped_ptr<StreamDeviceInfoArray> devices) {
+ const media::VideoCaptureDevice::Names& device_names) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- if (!listener_) {
- // Listener has been removed.
- return;
- }
- listener_->DevicesEnumerated(stream_type, *devices);
-}
-void VideoCaptureManager::OnError(MediaStreamType stream_type,
- int capture_session_id,
- MediaStreamProviderError error) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!listener_) {
// Listener has been removed.
return;
}
- listener_->Error(stream_type, capture_session_id, error);
-}
-
-void VideoCaptureManager::PostOnOpened(
- MediaStreamType stream_type, int capture_session_id) {
- DCHECK(IsOnDeviceThread());
- BrowserThread::PostTask(BrowserThread::IO,
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnOpened, this,
- stream_type, capture_session_id));
-}
-
-void VideoCaptureManager::PostOnClosed(
- MediaStreamType stream_type, int capture_session_id) {
- DCHECK(IsOnDeviceThread());
- BrowserThread::PostTask(BrowserThread::IO,
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnClosed, this,
- stream_type, capture_session_id));
-}
-void VideoCaptureManager::PostOnDevicesEnumerated(
- MediaStreamType stream_type,
- scoped_ptr<StreamDeviceInfoArray> devices) {
- DCHECK(IsOnDeviceThread());
- BrowserThread::PostTask(
- BrowserThread::IO, FROM_HERE,
- base::Bind(&VideoCaptureManager::OnDevicesEnumerated,
- this, stream_type, base::Passed(&devices)));
-}
+ // Transform from VCD::Name to StreamDeviceInfo.
+ StreamDeviceInfoArray devices;
+ for (media::VideoCaptureDevice::Names::const_iterator it =
+ device_names.begin(); it != device_names.end(); ++it) {
+ devices.push_back(StreamDeviceInfo(
+ stream_type, it->GetNameAndModel(), it->id(), false));
+ }
-void VideoCaptureManager::PostOnError(int capture_session_id,
- MediaStreamProviderError error) {
- DCHECK(IsOnDeviceThread());
- MediaStreamType stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
- VideoCaptureDevices::const_iterator it = devices_.find(capture_session_id);
- if (it != devices_.end())
- stream_type = it->second.stream_type;
- BrowserThread::PostTask(BrowserThread::IO,
- FROM_HERE,
- base::Bind(&VideoCaptureManager::OnError, this,
- stream_type, capture_session_id, error));
+ listener_->DevicesEnumerated(stream_type, devices);
}
bool VideoCaptureManager::IsOnDeviceThread() const {
return device_loop_->BelongsToCurrentThread();
}
-void VideoCaptureManager::GetAvailableDevices(
- MediaStreamType stream_type,
- media::VideoCaptureDevice::Names* device_names) {
+media::VideoCaptureDevice::Names
+VideoCaptureManager::GetAvailableDevicesOnDeviceThread(
+ MediaStreamType stream_type) {
+ SCOPED_UMA_HISTOGRAM_TIMER(
+ "Media.VideoCaptureManager.GetAvailableDevicesTime");
DCHECK(IsOnDeviceThread());
+ media::VideoCaptureDevice::Names result;
switch (stream_type) {
case MEDIA_DEVICE_VIDEO_CAPTURE:
// Cache the latest enumeration of video capture devices.
// We'll refer to this list again in OnOpen to avoid having to
// enumerate the devices again.
- video_capture_devices_.clear();
if (!use_fake_device_) {
- media::VideoCaptureDevice::GetDeviceNames(&video_capture_devices_);
+ media::VideoCaptureDevice::GetDeviceNames(&result);
} else {
- media::FakeVideoCaptureDevice::GetDeviceNames(&video_capture_devices_);
+ media::FakeVideoCaptureDevice::GetDeviceNames(&result);
}
- *device_names = video_capture_devices_;
+
+ // TODO(nick): The correctness of device start depends on this cache being
+ // maintained, but it seems a little odd to keep a cache here. Can we
+ // eliminate it?
+ video_capture_devices_ = result;
break;
case MEDIA_DESKTOP_VIDEO_CAPTURE:
- device_names->clear();
+ // Do nothing.
break;
default:
NOTREACHED();
break;
}
+ return result;
}
-bool VideoCaptureManager::DeviceOpened(
- const media::VideoCaptureDevice::Name& device_name) {
- DCHECK(IsOnDeviceThread());
+VideoCaptureManager::DeviceEntry*
+VideoCaptureManager::GetDeviceEntryForMediaStreamDevice(
+ const MediaStreamDevice& device_info) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- for (VideoCaptureDevices::iterator it = devices_.begin();
+ for (DeviceEntries::iterator it = devices_.begin();
it != devices_.end(); ++it) {
- if (device_name.id() == it->second.capture_device->device_name().id()) {
- // We've found the device!
- return true;
- }
- }
- return false;
-}
-
-media::VideoCaptureDevice* VideoCaptureManager::GetOpenedDevice(
- const StreamDeviceInfo& device_info) {
- DCHECK(IsOnDeviceThread());
-
- for (VideoCaptureDevices::iterator it = devices_.begin();
- it != devices_.end(); it++) {
- if (device_info.device.id ==
- it->second.capture_device->device_name().id()) {
- return it->second.capture_device;
+ DeviceEntry* device = *it;
+ if (device_info.type == device->stream_type &&
+ device_info.id == device->id) {
+ return device;
}
}
return NULL;
}
-bool VideoCaptureManager::DeviceInUse(
- const media::VideoCaptureDevice* video_capture_device) {
- DCHECK(IsOnDeviceThread());
-
- for (VideoCaptureDevices::iterator it = devices_.begin();
+VideoCaptureManager::DeviceEntry*
+VideoCaptureManager::GetDeviceEntryForController(
+ const VideoCaptureController* controller) {
+ // Look up |controller| in |devices_|.
+ for (DeviceEntries::iterator it = devices_.begin();
it != devices_.end(); ++it) {
- if (video_capture_device == it->second.capture_device) {
- // We've found the device!
- return true;
+ if ((*it)->video_capture_controller.get() == controller) {
+ return *it;
}
}
- return false;
+ return NULL;
}
-void VideoCaptureManager::AddController(
+void VideoCaptureManager::OpenAndStartDefaultSession(
const media::VideoCaptureParams& capture_params,
- VideoCaptureControllerEventHandler* handler,
- base::Callback<void(VideoCaptureController*)> added_cb) {
- DCHECK(handler);
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::DoAddControllerOnDeviceThread,
- this, capture_params, handler, added_cb));
-}
-
-void VideoCaptureManager::DoAddControllerOnDeviceThread(
- const media::VideoCaptureParams capture_params,
- VideoCaptureControllerEventHandler* handler,
- base::Callback<void(VideoCaptureController*)> added_cb) {
- DCHECK(IsOnDeviceThread());
+ base::ProcessHandle client_render_process,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler,
+ base::Callback<void(VideoCaptureController*)> done_cb,
+ const media::VideoCaptureDevice::Names& device_names) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- media::VideoCaptureDevice* video_capture_device =
- GetDeviceInternal(capture_params.session_id);
- scoped_refptr<VideoCaptureController> controller;
- if (video_capture_device) {
- Controllers::iterator cit = controllers_.find(video_capture_device);
- if (cit == controllers_.end()) {
- controller = new VideoCaptureController(this);
- controllers_[video_capture_device] =
- new Controller(controller.get(), handler);
- } else {
- controllers_[video_capture_device]->handlers.push_front(handler);
- controller = controllers_[video_capture_device]->controller;
- }
+ // |device_names| is a value returned by GetAvailableDevicesOnDeviceThread().
+ // We'll mimic an Open() operation on the first element in that list.
+ DCHECK(capture_params.session_id == kStartOpenSessionId);
+ if (device_names.empty() ||
+ sessions_.count(capture_params.session_id) != 0) {
+ done_cb.Run(NULL);
+ return;
}
- added_cb.Run(controller.get());
-}
-void VideoCaptureManager::RemoveController(
- VideoCaptureController* controller,
- VideoCaptureControllerEventHandler* handler) {
- DCHECK(handler);
- device_loop_->PostTask(
- FROM_HERE,
- base::Bind(&VideoCaptureManager::DoRemoveControllerOnDeviceThread, this,
- make_scoped_refptr(controller), handler));
-}
+ // Open the device by creating a |sessions_| entry.
+ sessions_[capture_params.session_id] =
+ MediaStreamDevice(MEDIA_DEVICE_VIDEO_CAPTURE,
+ device_names.front().id(),
+ device_names.front().GetNameAndModel());
-void VideoCaptureManager::DoRemoveControllerOnDeviceThread(
- VideoCaptureController* controller,
- VideoCaptureControllerEventHandler* handler) {
- DCHECK(IsOnDeviceThread());
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+ base::Bind(&VideoCaptureManager::OnOpened, this,
+ MEDIA_DEVICE_VIDEO_CAPTURE, kStartOpenSessionId));
- for (Controllers::iterator cit = controllers_.begin();
- cit != controllers_.end(); ++cit) {
- if (controller == cit->second->controller.get()) {
- Handlers& handlers = cit->second->handlers;
- for (Handlers::iterator hit = handlers.begin();
- hit != handlers.end(); ++hit) {
- if ((*hit) == handler) {
- handlers.erase(hit);
- break;
- }
- }
- if (handlers.empty() && cit->second->ready_to_delete) {
- delete cit->second;
- controllers_.erase(cit);
- }
- return;
- }
- }
+ DoStartCaptureForClient(capture_params, client_render_process, client_id,
+ client_handler, done_cb);
}
-media::VideoCaptureDevice* VideoCaptureManager::GetDeviceInternal(
- int capture_session_id) {
- DCHECK(IsOnDeviceThread());
- VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
- if (dit != devices_.end()) {
- return dit->second.capture_device;
+void VideoCaptureManager::DestroyDeviceEntryIfNoClients(DeviceEntry* entry) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ // Removal of the last client stops the device.
+ if (entry->video_capture_controller->GetClientCount() == 0) {
+ DVLOG(1) << "VideoCaptureManager stopping device (type = "
+ << entry->stream_type << ", id = " << entry->id << ")";
+
+ // The DeviceEntry is removed from |devices_| immediately, but will be torn
+ // down asynchronously. After this point, subsequent request to open this
+ // same device ID will create a new DeviceEntry, VideoCaptureController,
+ // and VideoCaptureDevice.
+ devices_.erase(entry);
+ device_loop_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&VideoCaptureManager::DoStopDeviceOnDeviceThread, this,
+ base::Unretained(entry)),
+ base::Bind(&VideoCaptureManager::FreeDeviceEntryOnIOThread, this,
+ base::Passed(make_scoped_ptr(entry).Pass())));
}
+}
- // Solution for not using MediaStreamManager.
- // This session id won't be returned by Open().
- if (capture_session_id == kStartOpenSessionId) {
- media::VideoCaptureDevice::Names device_names;
- GetAvailableDevices(MEDIA_DEVICE_VIDEO_CAPTURE, &device_names);
- if (device_names.empty()) {
- // No devices available.
- return NULL;
- }
- StreamDeviceInfo device(MEDIA_DEVICE_VIDEO_CAPTURE,
- device_names.front().GetNameAndModel(),
- device_names.front().id(),
- false);
+VideoCaptureManager::DeviceEntry* VideoCaptureManager::GetOrCreateDeviceEntry(
+ int capture_session_id) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- // Call OnOpen to open using the first device in the list.
- OnOpen(capture_session_id, device);
+ std::map<int, MediaStreamDevice>::iterator session_it =
+ sessions_.find(capture_session_id);
+ if (session_it == sessions_.end()) {
+ return NULL;
+ }
+ const MediaStreamDevice& device_info = session_it->second;
- VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
- if (dit != devices_.end()) {
- return dit->second.capture_device;
- }
+ // Check if another session has already opened this device. If so, just
+ // use that opened device.
+ DeviceEntry* const existing_device =
+ GetDeviceEntryForMediaStreamDevice(device_info);
+ if (existing_device) {
+ DCHECK_EQ(device_info.type, existing_device->stream_type);
+ return existing_device;
}
- return NULL;
+
+ scoped_refptr<VideoCaptureController> video_capture_controller =
+ new VideoCaptureController();
+ DeviceEntry* new_device = new DeviceEntry(device_info.type,
+ device_info.id,
+ video_capture_controller);
+ devices_.insert(new_device);
+ return new_device;
}
} // namespace content
diff --git a/content/browser/renderer_host/media/video_capture_manager.h b/content/browser/renderer_host/media/video_capture_manager.h
index 34d6e62..fb0a574 100644
--- a/content/browser/renderer_host/media/video_capture_manager.h
+++ b/content/browser/renderer_host/media/video_capture_manager.h
@@ -4,18 +4,22 @@
// VideoCaptureManager is used to open/close, start/stop, enumerate available
// video capture devices, and manage VideoCaptureController's.
-// All functions are expected to be called from Browser::IO thread.
+// All functions are expected to be called from Browser::IO thread. Some helper
+// functions (*OnDeviceThread) will dispatch operations to the device thread.
// VideoCaptureManager will open OS dependent instances of VideoCaptureDevice.
// A device can only be opened once.
#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_MANAGER_H_
#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_MANAGER_H_
-#include <list>
#include <map>
+#include <set>
+#include <string>
#include "base/memory/ref_counted.h"
+#include "base/process/process_handle.h"
#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/common/content_export.h"
#include "content/common/media/media_stream_options.h"
#include "media/video/capture/video_capture_device.h"
@@ -48,118 +52,153 @@ class CONTENT_EXPORT VideoCaptureManager : public MediaStreamProvider {
virtual void Close(int capture_session_id) OVERRIDE;
- // Functions used to start and stop media flow.
- // Start allocates the device and no other application can use the device
- // before Stop is called. Captured video frames will be delivered to
- // video_capture_receiver.
- virtual void Start(const media::VideoCaptureParams& capture_params,
- media::VideoCaptureDevice::EventHandler* video_capture_receiver);
-
- // Stops capture device referenced by |capture_session_id|. No more frames
- // will be delivered to the frame receiver, and |stopped_cb| will be called.
- // |stopped_cb| can be NULL.
- virtual void Stop(const media::VideoCaptureSessionId& capture_session_id,
- base::Closure stopped_cb);
-
// Used by unit test to make sure a fake device is used instead of a real
// video capture device. Due to timing requirements, the function must be
// called before EnumerateDevices and Open.
void UseFakeDevice();
- // Called by VideoCaptureHost to get a controller for |capture_params|.
- // The controller is returned via calling |added_cb|.
- void AddController(
+ // Called by VideoCaptureHost to locate a capture device for |capture_params|,
+ // adding the Host as a client of the device's controller if successful. The
+ // value of |capture_params.session_id| controls which device is selected;
+ // this value should be a session id previously returned by Open().
+ //
+ // If the device is not already started (i.e., no other client is currently
+ // capturing from this device), this call will cause a VideoCaptureController
+ // and VideoCaptureDevice to be created, possibly asynchronously.
+ //
+ // On success, the controller is returned via calling |done_cb|, indicating
+ // that the client was successfully added. A NULL controller is passed to
+ // the callback on failure.
+ void StartCaptureForClient(
const media::VideoCaptureParams& capture_params,
- VideoCaptureControllerEventHandler* handler,
- base::Callback<void(VideoCaptureController*)> added_cb);
- // Called by VideoCaptureHost to remove the |controller|.
- void RemoveController(
- VideoCaptureController* controller,
- VideoCaptureControllerEventHandler* handler);
+ base::ProcessHandle client_render_process,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler,
+ base::Callback<void(VideoCaptureController*)> done_cb);
+
+ // Called by VideoCaptureHost to remove |client_handler|. If this is the last
+ // client of the device, the |controller| and its VideoCaptureDevice may be
+ // destroyed. The client must not access |controller| after calling this
+ // function.
+ void StopCaptureForClient(VideoCaptureController* controller,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler);
private:
- friend class MockVideoCaptureManager;
-
virtual ~VideoCaptureManager();
+ struct DeviceEntry;
- typedef std::list<VideoCaptureControllerEventHandler*> Handlers;
- struct Controller;
-
- // Called by the public functions, executed on device thread.
- void OnEnumerateDevices(MediaStreamType stream_type);
- void OnOpen(int capture_session_id, const StreamDeviceInfo& device);
- void OnClose(int capture_session_id);
- void OnStart(const media::VideoCaptureParams capture_params,
- media::VideoCaptureDevice::EventHandler* video_capture_receiver);
- void OnStop(const media::VideoCaptureSessionId capture_session_id,
- base::Closure stopped_cb);
- void DoAddControllerOnDeviceThread(
- const media::VideoCaptureParams capture_params,
- VideoCaptureControllerEventHandler* handler,
- base::Callback<void(VideoCaptureController*)> added_cb);
- void DoRemoveControllerOnDeviceThread(
- VideoCaptureController* controller,
- VideoCaptureControllerEventHandler* handler);
-
- // Executed on Browser::IO thread to call Listener.
+ // Helper for the kStartOpenSessionId case.
+ void OpenAndStartDefaultSession(
+ const media::VideoCaptureParams& capture_params,
+ base::ProcessHandle client_render_process,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler,
+ base::Callback<void(VideoCaptureController*)> done_cb,
+ const media::VideoCaptureDevice::Names& device_names);
+
+ // Helper routine implementing StartCaptureForClient().
+ void DoStartCaptureForClient(
+ const media::VideoCaptureParams& capture_params,
+ base::ProcessHandle client_render_process,
+ VideoCaptureControllerID client_id,
+ VideoCaptureControllerEventHandler* client_handler,
+ base::Callback<void(VideoCaptureController*)> done_cb);
+
+ // Check to see if |entry| has no clients left on its controller. If so,
+ // remove it from the list of devices, and delete it asynchronously. |entry|
+ // may be freed by this function.
+ void DestroyDeviceEntryIfNoClients(DeviceEntry* entry);
+
+ // Helpers to report an event to our Listener.
void OnOpened(MediaStreamType type, int capture_session_id);
void OnClosed(MediaStreamType type, int capture_session_id);
void OnDevicesEnumerated(MediaStreamType stream_type,
- scoped_ptr<StreamDeviceInfoArray> devices);
- void OnError(MediaStreamType type, int capture_session_id,
- MediaStreamProviderError error);
-
- // Executed on device thread to make sure Listener is called from
- // Browser::IO thread.
- void PostOnOpened(MediaStreamType type, int capture_session_id);
- void PostOnClosed(MediaStreamType type, int capture_session_id);
- void PostOnDevicesEnumerated(MediaStreamType stream_type,
- scoped_ptr<StreamDeviceInfoArray> devices);
- void PostOnError(int capture_session_id, MediaStreamProviderError error);
-
- // Helpers
- void GetAvailableDevices(MediaStreamType stream_type,
- media::VideoCaptureDevice::Names* device_names);
- bool DeviceOpened(const media::VideoCaptureDevice::Name& device_name);
- bool DeviceInUse(const media::VideoCaptureDevice* video_capture_device);
- media::VideoCaptureDevice* GetOpenedDevice(
- const StreamDeviceInfo& device_info);
+ const media::VideoCaptureDevice::Names& names);
+
+ // Find a DeviceEntry by its device ID and type, if it is already opened.
+ DeviceEntry* GetDeviceEntryForMediaStreamDevice(
+ const MediaStreamDevice& device_info);
+
+ // Find a DeviceEntry entry for the indicated session, creating a fresh one
+ // if necessary. Returns NULL if the session id is invalid.
+ DeviceEntry* GetOrCreateDeviceEntry(int capture_session_id);
+
+ // Find the DeviceEntry that owns a particular controller pointer.
+ DeviceEntry* GetDeviceEntryForController(
+ const VideoCaptureController* controller);
+
bool IsOnDeviceThread() const;
- media::VideoCaptureDevice* GetDeviceInternal(int capture_session_id);
- // The message loop of media stream device thread that this object runs on.
+ // Queries and returns the available device IDs.
+ media::VideoCaptureDevice::Names GetAvailableDevicesOnDeviceThread(
+ MediaStreamType stream_type);
+
+ // Create and Start a new VideoCaptureDevice, storing the result in
+ // |entry->video_capture_device|.
+ void DoStartDeviceOnDeviceThread(
+ DeviceEntry* entry,
+ const media::VideoCaptureCapability& capture_params,
+ media::VideoCaptureDevice::EventHandler* controller_as_handler);
+
+ // Stop and destroy the VideoCaptureDevice held in
+ // |entry->video_capture_device|.
+ void DoStopDeviceOnDeviceThread(DeviceEntry* entry);
+
+ // Helper to clean up the DeviceEntry* instance, and the
+ // VideoCaptureController, on the IO thread. Must happen after
+ // DoStopDeviceOnDeviceThread() destroys the VideoCaptureDevice. It is assumed
+ // that |dead_device| has already been removed from the |devices_| map.
+ void FreeDeviceEntryOnIOThread(scoped_ptr<DeviceEntry> dead_device);
+
+ // The message loop of media stream device thread, where VCD's live.
scoped_refptr<base::MessageLoopProxy> device_loop_;
// Only accessed on Browser::IO thread.
MediaStreamProviderListener* listener_;
int new_capture_session_id_;
- // Only accessed from device thread.
- // VideoCaptureManager owns all VideoCaptureDevices and is responsible for
- // deleting the instances when they are not used any longer.
+ // An entry is kept in this map for every session that has been created via
+ // the Open() entry point. The keys are session_id's. This map is used to
+ // determine which device to use when StartCaptureForClient() occurs. Used
+ // only on the IO thread.
+ std::map<int, MediaStreamDevice> sessions_;
+
+ // An entry, kept in a map, that owns a VideoCaptureDevice and its associated
+ // VideoCaptureController. VideoCaptureManager owns all VideoCaptureDevices
+ // and VideoCaptureControllers and is responsible for deleting the instances
+ // when they are not used any longer.
+ //
+ // The set of currently started VideoCaptureDevice and VideoCaptureController
+ // objects is only accessed from IO thread, though the DeviceEntry instances
+ // themselves may visit to the device thread for device creation and
+ // destruction.
struct DeviceEntry {
- MediaStreamType stream_type;
- media::VideoCaptureDevice* capture_device; // Maybe shared across sessions.
+ DeviceEntry(MediaStreamType stream_type,
+ const std::string& id,
+ scoped_refptr<VideoCaptureController> controller);
+ ~DeviceEntry();
+
+ const MediaStreamType stream_type;
+ const std::string id;
+
+ // The controller. Only used from the IO thread.
+ scoped_refptr<VideoCaptureController> video_capture_controller;
+
+ // The capture device. Only used from the device thread.
+ scoped_ptr<media::VideoCaptureDevice> video_capture_device;
};
- typedef std::map<int, DeviceEntry> VideoCaptureDevices;
- VideoCaptureDevices devices_; // Maps capture_session_id to DeviceEntry.
+ typedef std::set<DeviceEntry*> DeviceEntries;
+ DeviceEntries devices_;
- // Set to true if using fake video capture devices for testing,
- // false by default. This is only used for the MEDIA_DEVICE_VIDEO_CAPTURE
- // device type.
+ // Set to true if using fake video capture devices for testing, false by
+ // default. This is only used for the MEDIA_DEVICE_VIDEO_CAPTURE device type.
bool use_fake_device_;
- // Only accessed from device thread.
- // VideoCaptureManager owns all VideoCaptureController's and is responsible
- // for deleting the instances when they are not used any longer.
- // VideoCaptureDevice is one-to-one mapped to VideoCaptureController.
- typedef std::map<media::VideoCaptureDevice*, Controller*> Controllers;
- Controllers controllers_;
-
- // We cache the enumerated video capture devices in GetAvailableDevices
- // (e.g. called by OnEnumerateDevices) and then look up the requested ID when
- // a device is opened (see OnOpen).
- // Used only on the device thread.
+ // We cache the enumerated video capture devices in
+ // GetAvailableDevicesOnDeviceThread() and then later look up the requested ID
+ // when a device is created in DoStartDeviceOnDeviceThread(). Used only on the
+ // device thread.
media::VideoCaptureDevice::Names video_capture_devices_;
DISALLOW_COPY_AND_ASSIGN(VideoCaptureManager);
diff --git a/content/browser/renderer_host/media/video_capture_manager_unittest.cc b/content/browser/renderer_host/media/video_capture_manager_unittest.cc
index 29570a0..92a04da 100644
--- a/content/browser/renderer_host/media/video_capture_manager_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_manager_unittest.cc
@@ -10,8 +10,10 @@
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/renderer_host/media/media_stream_provider.h"
+#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/media/media_stream_options.h"
#include "media/video/capture/video_capture_device.h"
@@ -40,30 +42,33 @@ class MockMediaStreamProviderListener : public MediaStreamProviderListener {
MediaStreamProviderError));
}; // class MockMediaStreamProviderListener
-// Needed as an input argument to Start().
-class MockFrameObserver : public media::VideoCaptureDevice::EventHandler {
+// Needed as an input argument to StartCaptureForClient().
+class MockFrameObserver : public VideoCaptureControllerEventHandler {
public:
- virtual scoped_refptr<media::VideoFrame> ReserveOutputBuffer() OVERRIDE {
- return NULL;
- }
- virtual void OnError() OVERRIDE {}
+ MOCK_METHOD1(OnError, void(const VideoCaptureControllerID& id));
+
+ virtual void OnBufferCreated(const VideoCaptureControllerID& id,
+ base::SharedMemoryHandle handle,
+ int length, int buffer_id) OVERRIDE {};
+ virtual void OnBufferReady(const VideoCaptureControllerID& id,
+ int buffer_id,
+ base::Time timestamp) OVERRIDE {};
virtual void OnFrameInfo(
- const media::VideoCaptureCapability& info) OVERRIDE {}
- virtual void OnIncomingCapturedFrame(const uint8* data,
- int length,
- base::Time timestamp,
- int rotation,
- bool flip_vert,
- bool flip_horiz) OVERRIDE {}
- virtual void OnIncomingCapturedVideoFrame(
- const scoped_refptr<media::VideoFrame>& frame,
- base::Time timestamp) OVERRIDE {}
+ const VideoCaptureControllerID& id,
+ const media::VideoCaptureCapability& format) OVERRIDE {};
+ virtual void OnFrameInfoChanged(const VideoCaptureControllerID& id,
+ int width,
+ int height,
+ int frame_rate) OVERRIDE {};
+ virtual void OnEnded(const VideoCaptureControllerID& id) OVERRIDE {};
+
+ void OnGotControllerCallback(VideoCaptureControllerID) {}
};
// Test class
class VideoCaptureManagerTest : public testing::Test {
public:
- VideoCaptureManagerTest() {}
+ VideoCaptureManagerTest() : next_client_id_(1) {}
virtual ~VideoCaptureManagerTest() {}
protected:
@@ -80,6 +85,47 @@ class VideoCaptureManagerTest : public testing::Test {
virtual void TearDown() OVERRIDE {}
+ void OnGotControllerCallback(VideoCaptureControllerID id,
+ base::Closure quit_closure,
+ bool expect_success,
+ VideoCaptureController* controller) {
+ if (expect_success) {
+ ASSERT_TRUE(NULL != controller);
+ ASSERT_TRUE(0 == controllers_.count(id));
+ controllers_[id] = controller;
+ } else {
+ ASSERT_TRUE(NULL == controller);
+ }
+ quit_closure.Run();
+ }
+
+ VideoCaptureControllerID StartClient(int session_id, bool expect_success) {
+ media::VideoCaptureParams params;
+ params.session_id = session_id;
+ params.width = 320;
+ params.height = 240;
+ params.frame_rate = 30;
+
+ VideoCaptureControllerID client_id(next_client_id_++);
+ base::RunLoop run_loop;
+ vcm_->StartCaptureForClient(
+ params, base::kNullProcessHandle, client_id, frame_observer_.get(),
+ base::Bind(&VideoCaptureManagerTest::OnGotControllerCallback,
+ base::Unretained(this), client_id, run_loop.QuitClosure(),
+ expect_success));
+ run_loop.Run();
+ return client_id;
+ }
+
+ void StopClient(VideoCaptureControllerID client_id) {
+ ASSERT_TRUE(1 == controllers_.count(client_id));
+ vcm_->StopCaptureForClient(controllers_[client_id], client_id,
+ frame_observer_.get());
+ controllers_.erase(client_id);
+ }
+
+ int next_client_id_;
+ std::map<VideoCaptureControllerID, VideoCaptureController*> controllers_;
scoped_refptr<VideoCaptureManager> vcm_;
scoped_ptr<MockMediaStreamProviderListener> listener_;
scoped_ptr<base::MessageLoop> message_loop_;
@@ -108,15 +154,9 @@ TEST_F(VideoCaptureManagerTest, CreateAndClose) {
message_loop_->RunUntilIdle();
int video_session_id = vcm_->Open(devices.front());
+ VideoCaptureControllerID client_id = StartClient(video_session_id, true);
- media::VideoCaptureParams capture_params;
- capture_params.session_id = video_session_id;
- capture_params.width = 320;
- capture_params.height = 240;
- capture_params.frame_rate = 30;
- vcm_->Start(capture_params, frame_observer_.get());
-
- vcm_->Stop(video_session_id, base::Closure());
+ StopClient(client_id);
vcm_->Close(video_session_id);
// Wait to check callbacks before removing the listener.
@@ -190,9 +230,9 @@ TEST_F(VideoCaptureManagerTest, OpenNotExisting) {
InSequence s;
EXPECT_CALL(*listener_, DevicesEnumerated(MEDIA_DEVICE_VIDEO_CAPTURE, _))
.Times(1).WillOnce(SaveArg<1>(&devices));
- EXPECT_CALL(*listener_, Error(MEDIA_DEVICE_VIDEO_CAPTURE,
- _, kDeviceNotAvailable))
- .Times(1);
+ EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
+ EXPECT_CALL(*frame_observer_, OnError(_)).Times(1);
+ EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
vcm_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
@@ -204,11 +244,15 @@ TEST_F(VideoCaptureManagerTest, OpenNotExisting) {
std::string device_id("id_doesnt_exist");
StreamDeviceInfo dummy_device(stream_type, device_name, device_id, false);
- // This should fail with error code 'kDeviceNotAvailable'.
- vcm_->Open(dummy_device);
+ // This should fail with an error to the controller.
+ int session_id = vcm_->Open(dummy_device);
+ VideoCaptureControllerID client_id = StartClient(session_id, true);
+ message_loop_->RunUntilIdle();
- // Wait to check callbacks before removing the listener.
+ StopClient(client_id);
+ vcm_->Close(session_id);
message_loop_->RunUntilIdle();
+
vcm_->Unregister();
}
@@ -218,17 +262,21 @@ TEST_F(VideoCaptureManagerTest, StartUsingId) {
EXPECT_CALL(*listener_, Opened(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
EXPECT_CALL(*listener_, Closed(MEDIA_DEVICE_VIDEO_CAPTURE, _)).Times(1);
- media::VideoCaptureParams capture_params;
- capture_params.session_id = VideoCaptureManager::kStartOpenSessionId;
- capture_params.width = 320;
- capture_params.height = 240;
- capture_params.frame_rate = 30;
-
// Start shall trigger the Open callback.
- vcm_->Start(capture_params, frame_observer_.get());
+ VideoCaptureControllerID client_id = StartClient(
+ VideoCaptureManager::kStartOpenSessionId, true);
// Stop shall trigger the Close callback
- vcm_->Stop(VideoCaptureManager::kStartOpenSessionId, base::Closure());
+ StopClient(client_id);
+
+ // Wait to check callbacks before removing the listener.
+ message_loop_->RunUntilIdle();
+ vcm_->Unregister();
+}
+
+// Start a device without calling Open, using a non-magic ID.
+TEST_F(VideoCaptureManagerTest, StartInvalidSession) {
+ StartClient(22, false);
// Wait to check callbacks before removing the listener.
message_loop_->RunUntilIdle();
@@ -252,17 +300,12 @@ TEST_F(VideoCaptureManagerTest, CloseWithoutStop) {
int video_session_id = vcm_->Open(devices.front());
- media::VideoCaptureParams capture_params;
- capture_params.session_id = video_session_id;
- capture_params.width = 320;
- capture_params.height = 240;
- capture_params.frame_rate = 30;
- vcm_->Start(capture_params, frame_observer_.get());
+ VideoCaptureControllerID client_id = StartClient(video_session_id, true);
// Close will stop the running device, an assert will be triggered in
// VideoCaptureManager destructor otherwise.
vcm_->Close(video_session_id);
- vcm_->Stop(video_session_id, base::Closure());
+ StopClient(client_id);
// Wait to check callbacks before removing the listener
message_loop_->RunUntilIdle();