blob: b09ccdd8e3aba01db0a8f2d4826ef224b73a9173 [file] [log] [blame]
// 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.
#include "content/browser/renderer_host/media/video_capture_controller.h"
#include <set>
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/public/browser/browser_thread.h"
#include "media/base/yuv_convert.h"
using content::BrowserThread;
// The number of DIBs VideoCaptureController allocate.
static const size_t kNoOfDIBS = 3;
struct VideoCaptureController::ControllerClient {
ControllerClient(
const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* handler,
base::ProcessHandle render_process,
const media::VideoCaptureParams& params)
: controller_id(id),
event_handler(handler),
render_process_handle(render_process),
parameters(params),
session_closed(false) {
}
~ControllerClient() {}
// ID used for identifying this object.
VideoCaptureControllerID controller_id;
VideoCaptureControllerEventHandler* event_handler;
// Handle to the render process that will receive the DIBs.
base::ProcessHandle render_process_handle;
media::VideoCaptureParams parameters;
// Buffers used by this client.
std::set<int> buffers;
// State of capture session, controlled by VideoCaptureManager directly.
bool session_closed;
};
struct VideoCaptureController::SharedDIB {
SharedDIB(base::SharedMemory* ptr)
: shared_memory(ptr),
references(0) {
}
~SharedDIB() {}
// The memory created to be shared with renderer processes.
scoped_ptr<base::SharedMemory> shared_memory;
// Number of renderer processes which hold this shared memory.
// renderer process is represented by VidoeCaptureHost.
int references;
};
VideoCaptureController::VideoCaptureController(
media_stream::VideoCaptureManager* video_capture_manager)
: chopped_width_(0),
chopped_height_(0),
frame_info_available_(false),
video_capture_manager_(video_capture_manager),
device_in_use_(false),
state_(video_capture::kStopped) {
memset(&current_params_, 0, sizeof(current_params_));
}
void VideoCaptureController::StartCapture(
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
<< ", (" << params.width
<< ", " << params.height
<< ", " << params.frame_per_second
<< ", " << params.session_id
<< ")";
// Signal error in case device is already in error state.
if (state_ == video_capture::kError) {
event_handler->OnError(id);
return;
}
// Do nothing if this client has called StartCapture before.
if (FindClient(id, event_handler, controller_clients_) ||
FindClient(id, event_handler, pending_clients_))
return;
ControllerClient* client = new ControllerClient(id, event_handler,
render_process, params);
// In case capture has been started, need to check different condtions.
if (state_ == video_capture::kStarted) {
// 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::kStopping;
pending_clients_.push_back(client);
return;
}
// 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_) {
int buffer_size = (frame_info_.width * frame_info_.height * 3) / 2;
SendFrameInfoAndBuffers(client, buffer_size);
}
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::kStopping) {
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::kStarted;
device_in_use_ = true;
}
void VideoCaptureController::StopCapture(
const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* event_handler) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << "VideoCaptureController::StopCapture, 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_);
if (!client)
return;
// Take back all buffers held by the |client|.
for (std::set<int>::iterator buffer_it = client->buffers.begin();
buffer_it != client->buffers.end(); ++buffer_it) {
int buffer_id = *buffer_it;
DIBMap::iterator dib_it = owned_dibs_.find(buffer_id);
if (dib_it == owned_dibs_.end())
continue;
{
base::AutoLock lock(lock_);
DCHECK_GT(dib_it->second->references, 0)
<< "The buffer is not used by renderer.";
dib_it->second->references -= 1;
}
}
client->buffers.clear();
int session_id = client->parameters.session_id;
delete client;
controller_clients_.remove(client);
// No more clients. Stop device.
if (controller_clients_.empty() && state_ == video_capture::kStarted) {
video_capture_manager_->Stop(session_id,
base::Bind(&VideoCaptureController::OnDeviceStopped, this));
frame_info_available_ = false;
state_ = video_capture::kStopping;
}
}
void VideoCaptureController::StopSession(
int session_id) {
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_);
if (client) {
client->session_closed = true;
client->event_handler->OnPaused(client->controller_id);
}
}
void VideoCaptureController::ReturnBuffer(
const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* event_handler,
int buffer_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
ControllerClient* client = FindClient(id, event_handler,
controller_clients_);
DIBMap::iterator dib_it = owned_dibs_.find(buffer_id);
// 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() ||
dib_it == owned_dibs_.end())
return;
client->buffers.erase(buffer_id);
{
base::AutoLock lock(lock_);
DCHECK_GT(dib_it->second->references, 0)
<< "The buffer is not used by renderer.";
dib_it->second->references -= 1;
if (dib_it->second->references > 0)
return;
}
// 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 (!ClientHasDIB() && state_ == video_capture::kStopping) {
PostStopping();
}
}
///////////////////////////////////////////////////////////////////////////////
// Implements VideoCaptureDevice::EventHandler.
// OnIncomingCapturedFrame is called the thread running the capture device.
// I.e.- DirectShow thread on windows and v4l2_thread on Linux.
void VideoCaptureController::OnIncomingCapturedFrame(const uint8* data,
int length,
base::Time timestamp) {
int buffer_id = 0;
base::SharedMemory* dib = NULL;
{
base::AutoLock lock(lock_);
for (DIBMap::iterator dib_it = owned_dibs_.begin();
dib_it != owned_dibs_.end(); dib_it++) {
if (dib_it->second->references == 0) {
buffer_id = dib_it->first;
// Use special value "-1" in order to not be treated as buffer at
// renderer side.
dib_it->second->references = -1;
dib = dib_it->second->shared_memory.get();
break;
}
}
}
if (!dib) {
return;
}
uint8* target = static_cast<uint8*>(dib->memory());
CHECK(dib->created_size() >= static_cast<size_t> (frame_info_.width *
frame_info_.height * 3) /
2);
uint8* yplane = target;
uint8* uplane = target + frame_info_.width * frame_info_.height;
uint8* vplane = uplane + (frame_info_.width * frame_info_.height) / 4;
// Do color conversion from the camera format to I420.
switch (frame_info_.color) {
case media::VideoCaptureCapability::kColorUnknown: // Color format not set.
break;
case media::VideoCaptureCapability::kI420: {
DCHECK(!chopped_width_ && !chopped_height_);
memcpy(target, data, (frame_info_.width * frame_info_.height * 3) / 2);
break;
}
case media::VideoCaptureCapability::kYV12: {
DCHECK(!chopped_width_ && !chopped_height_);
const uint8* ptr = data;
memcpy(yplane, ptr, (frame_info_.width * frame_info_.height));
ptr += frame_info_.width * frame_info_.height;
memcpy(vplane, ptr, (frame_info_.width * frame_info_.height) >> 2);
ptr += (frame_info_.width * frame_info_.height) >> 2;
memcpy(uplane, ptr, (frame_info_.width * frame_info_.height) >> 2);
break;
}
case media::VideoCaptureCapability::kNV21: {
DCHECK(!chopped_width_ && !chopped_height_);
media::ConvertNV21ToYUV(data, yplane, uplane, vplane, frame_info_.width,
frame_info_.height);
break;
}
case media::VideoCaptureCapability::kYUY2: {
DCHECK(!chopped_width_ && !chopped_height_);
media::ConvertYUY2ToYUV(data, yplane, uplane, vplane, frame_info_.width,
frame_info_.height);
break;
}
case media::VideoCaptureCapability::kRGB24: {
int ystride = frame_info_.width;
int uvstride = frame_info_.width / 2;
#if defined(OS_WIN) // RGB on Windows start at the bottom line.
int rgb_stride = -3 * (frame_info_.width + chopped_width_);
const uint8* rgb_src = data + 3 * (frame_info_.width + chopped_width_) *
(frame_info_.height -1 + chopped_height_);
#else
int rgb_stride = 3 * (frame_info_.width + chopped_width_);
const uint8* rgb_src = data;
#endif
media::ConvertRGB24ToYUV(rgb_src, yplane, uplane, vplane,
frame_info_.width, frame_info_.height,
rgb_stride, ystride, uvstride);
break;
}
case media::VideoCaptureCapability::kARGB: {
media::ConvertRGB32ToYUV(data, yplane, uplane, vplane, frame_info_.width,
frame_info_.height,
(frame_info_.width + chopped_width_) * 4,
frame_info_.width, frame_info_.width / 2);
break;
}
default:
NOTREACHED();
}
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&VideoCaptureController::DoIncomingCapturedFrameOnIOThread,
this, buffer_id, timestamp));
}
void VideoCaptureController::OnError() {
video_capture_manager_->Error(current_params_.session_id);
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&VideoCaptureController::DoErrorOnIOThread, this));
}
void VideoCaptureController::OnFrameInfo(
const media::VideoCaptureCapability& info) {
frame_info_= info;
// Handle cases when |info| has odd numbers for width/height.
if (info.width & 1) {
--frame_info_.width;
chopped_width_ = 1;
} else {
chopped_width_ = 0;
}
if (info.height & 1) {
--frame_info_.height;
chopped_height_ = 1;
} else {
chopped_height_ = 0;
}
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&VideoCaptureController::DoFrameInfoOnIOThread, this));
}
VideoCaptureController::~VideoCaptureController() {
// Delete all DIBs.
STLDeleteContainerPairSecondPointers(owned_dibs_.begin(),
owned_dibs_.end());
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(
int buffer_id, base::Time timestamp) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
int count = 0;
if (state_ == video_capture::kStarted) {
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->OnBufferReady((*client_it)->controller_id,
buffer_id, timestamp);
(*client_it)->buffers.insert(buffer_id);
count++;
}
}
base::AutoLock lock(lock_);
if (owned_dibs_.find(buffer_id) != owned_dibs_.end()) {
DCHECK_EQ(owned_dibs_[buffer_id]->references, -1);
owned_dibs_[buffer_id]->references = count;
}
}
void VideoCaptureController::DoFrameInfoOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(owned_dibs_.empty())
<< "Device is restarted without releasing shared memory.";
// Allocate memory only when device has been started.
if (state_ != video_capture::kStarted)
return;
bool frames_created = true;
const size_t needed_size = (frame_info_.width * frame_info_.height * 3) / 2;
{
base::AutoLock lock(lock_);
for (size_t i = 1; i <= kNoOfDIBS; ++i) {
scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory());
if (!shared_memory->CreateAndMapAnonymous(needed_size)) {
frames_created = false;
break;
}
SharedDIB* dib = new SharedDIB(shared_memory.release());
owned_dibs_.insert(std::make_pair(i, dib));
}
}
// Check whether all DIBs were created successfully.
if (!frames_created) {
state_ = video_capture::kError;
for (ControllerClients::iterator client_it = controller_clients_.begin();
client_it != controller_clients_.end(); client_it++) {
(*client_it)->event_handler->OnError((*client_it)->controller_id);
}
return;
}
frame_info_available_ = true;
for (ControllerClients::iterator client_it = controller_clients_.begin();
client_it != controller_clients_.end(); client_it++) {
SendFrameInfoAndBuffers((*client_it), static_cast<int>(needed_size));
}
}
void VideoCaptureController::DoErrorOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
state_ = video_capture::kError;
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);
}
}
void VideoCaptureController::DoDeviceStoppedOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
device_in_use_ = false;
if (state_ == video_capture::kStopping) {
PostStopping();
}
}
void VideoCaptureController::SendFrameInfoAndBuffers(
ControllerClient* client, int buffer_size) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(frame_info_available_);
client->event_handler->OnFrameInfo(client->controller_id,
frame_info_.width, frame_info_.height,
frame_info_.frame_rate);
for (DIBMap::iterator dib_it = owned_dibs_.begin();
dib_it != owned_dibs_.end(); dib_it++) {
base::SharedMemory* shared_memory = dib_it->second->shared_memory.get();
int index = dib_it->first;
base::SharedMemoryHandle remote_handle;
shared_memory->ShareToProcess(client->render_process_handle,
&remote_handle);
client->event_handler->OnBufferCreated(client->controller_id,
remote_handle,
buffer_size,
index);
}
}
VideoCaptureController::ControllerClient*
VideoCaptureController::FindClient(
const VideoCaptureControllerID& id,
VideoCaptureControllerEventHandler* handler,
const ControllerClients& clients) {
for (ControllerClients::const_iterator client_it = clients.begin();
client_it != clients.end(); client_it++) {
if ((*client_it)->controller_id == id &&
(*client_it)->event_handler == handler) {
return *client_it;
}
}
return NULL;
}
VideoCaptureController::ControllerClient*
VideoCaptureController::FindClient(
int session_id,
const ControllerClients& clients) {
for (ControllerClients::const_iterator client_it = clients.begin();
client_it != clients.end(); client_it++) {
if ((*client_it)->parameters.session_id == session_id) {
return *client_it;
}
}
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() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(state_, video_capture::kStopping);
// When clients still have some buffers, or device has not been stopped yet,
// do nothing.
if (ClientHasDIB() || device_in_use_)
return;
{
base::AutoLock lock(lock_);
STLDeleteValues(&owned_dibs_);
}
// No more client. Therefore the controller is stopped.
if (controller_clients_.empty() && pending_clients_.empty()) {
state_ = video_capture::kStopped;
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::kStarted;
device_in_use_ = true;
}
bool VideoCaptureController::ClientHasDIB() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
base::AutoLock lock(lock_);
for (DIBMap::iterator dib_it = owned_dibs_.begin();
dib_it != owned_dibs_.end(); dib_it++) {
if (dib_it->second->references > 0)
return true;
}
return false;
}