blob: faa29d06d99728fc51202b245590c65897997c37 [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/renderer/media/video_capture_impl.h"
#include "base/bind.h"
#include "base/stl_util.h"
#include "content/common/child_process.h"
#include "content/common/media/video_capture_messages.h"
struct VideoCaptureImpl::DIBBuffer {
public:
DIBBuffer(
base::SharedMemory* d,
media::VideoCapture::VideoFrameBuffer* ptr)
: dib(d),
mapped_memory(ptr),
references(0) {
}
~DIBBuffer() {}
scoped_ptr<base::SharedMemory> dib;
scoped_refptr<media::VideoCapture::VideoFrameBuffer> mapped_memory;
// Number of clients which hold this DIB.
int references;
};
bool VideoCaptureImpl::CaptureStarted() {
return state_ == video_capture::kStarted;
}
int VideoCaptureImpl::CaptureWidth() {
return current_params_.width;
}
int VideoCaptureImpl::CaptureHeight() {
return current_params_.height;
}
int VideoCaptureImpl::CaptureFrameRate() {
return current_params_.frame_per_second;
}
VideoCaptureImpl::VideoCaptureImpl(
const media::VideoCaptureSessionId id,
base::MessageLoopProxy* capture_message_loop_proxy,
VideoCaptureMessageFilter* filter)
: VideoCapture(),
message_filter_(filter),
capture_message_loop_proxy_(capture_message_loop_proxy),
io_message_loop_proxy_(ChildProcess::current()->io_message_loop_proxy()),
device_id_(0),
video_type_(media::VideoCaptureCapability::kI420),
device_info_available_(false),
state_(video_capture::kStopped) {
DCHECK(filter);
memset(&current_params_, 0, sizeof(current_params_));
memset(&device_info_, 0, sizeof(device_info_));
current_params_.session_id = id;
}
VideoCaptureImpl::~VideoCaptureImpl() {
STLDeleteValues(&cached_dibs_);
}
void VideoCaptureImpl::Init() {
if (!io_message_loop_proxy_->BelongsToCurrentThread()) {
io_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::AddDelegateOnIOThread,
base::Unretained(this)));
} else {
AddDelegateOnIOThread();
}
}
void VideoCaptureImpl::DeInit(base::Closure task) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoDeInitOnCaptureThread,
base::Unretained(this), task));
}
void VideoCaptureImpl::StartCapture(
media::VideoCapture::EventHandler* handler,
const media::VideoCaptureCapability& capability) {
DCHECK_EQ(capability.color, media::VideoCaptureCapability::kI420);
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoStartCaptureOnCaptureThread,
base::Unretained(this), handler, capability));
}
void VideoCaptureImpl::StopCapture(media::VideoCapture::EventHandler* handler) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoStopCaptureOnCaptureThread,
base::Unretained(this), handler));
}
void VideoCaptureImpl::FeedBuffer(scoped_refptr<VideoFrameBuffer> buffer) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoFeedBufferOnCaptureThread,
base::Unretained(this), buffer));
}
void VideoCaptureImpl::OnBufferCreated(
base::SharedMemoryHandle handle,
int length, int buffer_id) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoBufferCreatedOnCaptureThread,
base::Unretained(this), handle, length, buffer_id));
}
void VideoCaptureImpl::OnBufferReceived(int buffer_id, base::Time timestamp) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoBufferReceivedOnCaptureThread,
base::Unretained(this), buffer_id, timestamp));
}
void VideoCaptureImpl::OnStateChanged(video_capture::State state) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoStateChangedOnCaptureThread,
base::Unretained(this), state));
}
void VideoCaptureImpl::OnDeviceInfoReceived(
const media::VideoCaptureParams& device_info) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoDeviceInfoReceivedOnCaptureThread,
base::Unretained(this), device_info));
}
void VideoCaptureImpl::OnDelegateAdded(int32 device_id) {
capture_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::DoDelegateAddedOnCaptureThread,
base::Unretained(this), device_id));
}
void VideoCaptureImpl::DoDeInitOnCaptureThread(base::Closure task) {
if (state_ == video_capture::kStarted)
Send(new VideoCaptureHostMsg_Stop(device_id_));
io_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(&VideoCaptureImpl::RemoveDelegateOnIOThread,
base::Unretained(this), task));
}
void VideoCaptureImpl::DoStartCaptureOnCaptureThread(
media::VideoCapture::EventHandler* handler,
const media::VideoCaptureCapability& capability) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
if (state_ == video_capture::kError) {
handler->OnError(this, 1);
handler->OnRemoved(this);
} else if ((clients_pending_on_filter_.find(handler) !=
clients_pending_on_filter_.end()) ||
(clients_pending_on_restart_.find(handler) !=
clients_pending_on_restart_.end()) ||
clients_.find(handler) != clients_.end() ) {
// This client has started.
} else if (!device_id_) {
clients_pending_on_filter_[handler] = capability;
} else {
handler->OnStarted(this);
if (state_ == video_capture::kStarted) {
if (capability.width > current_params_.width ||
capability.height > current_params_.height) {
StopDevice();
DVLOG(1) << "StartCapture: Got client with higher resolution ("
<< capability.width << ", " << capability.height << ") "
<< "after started, try to restart.";
clients_pending_on_restart_[handler] = capability;
} else {
if (device_info_available_) {
handler->OnDeviceInfoReceived(this, device_info_);
}
clients_[handler] = capability;
}
} else if (state_ == video_capture::kStopping) {
clients_pending_on_restart_[handler] = capability;
DVLOG(1) << "StartCapture: Got new resolution ("
<< capability.width << ", " << capability.height << ") "
<< ", during stopping.";
} else {
clients_[handler] = capability;
DCHECK_EQ(1ul, clients_.size());
video_type_ = capability.color;
current_params_.width = capability.width;
current_params_.height = capability.height;
current_params_.frame_per_second = capability.frame_rate;
DVLOG(1) << "StartCapture: starting with first resolution ("
<< current_params_.width << "," << current_params_.height << ")";
StartCaptureInternal();
}
}
}
void VideoCaptureImpl::DoStopCaptureOnCaptureThread(
media::VideoCapture::EventHandler* handler) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
// A handler can be in only one client list.
// If this handler is in any client list, we can just remove it from
// that client list and don't have to run the other following RemoveClient().
RemoveClient(handler, &clients_pending_on_filter_) ||
RemoveClient(handler, &clients_pending_on_restart_) ||
RemoveClient(handler, &clients_);
if (clients_.empty()) {
DVLOG(1) << "StopCapture: No more client, stopping ...";
StopDevice();
}
}
void VideoCaptureImpl::DoFeedBufferOnCaptureThread(
scoped_refptr<VideoFrameBuffer> buffer) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
CachedDIB::iterator it;
for (it = cached_dibs_.begin(); it != cached_dibs_.end(); ++it) {
if (buffer == it->second->mapped_memory)
break;
}
if (it != cached_dibs_.end() && it->second) {
DCHECK_GT(it->second->references, 0);
--it->second->references;
if (it->second->references == 0) {
Send(new VideoCaptureHostMsg_BufferReady(device_id_, it->first));
}
}
}
void VideoCaptureImpl::DoBufferCreatedOnCaptureThread(
base::SharedMemoryHandle handle,
int length, int buffer_id) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
DCHECK(device_info_available_);
media::VideoCapture::VideoFrameBuffer* buffer;
DCHECK(cached_dibs_.find(buffer_id) == cached_dibs_.end());
base::SharedMemory* dib = new base::SharedMemory(handle, false);
dib->Map(length);
buffer = new VideoFrameBuffer();
buffer->memory_pointer = static_cast<uint8*>(dib->memory());
buffer->buffer_size = length;
buffer->width = device_info_.width;
buffer->height = device_info_.height;
buffer->stride = device_info_.width;
DIBBuffer* dib_buffer = new DIBBuffer(dib, buffer);
cached_dibs_[buffer_id] = dib_buffer;
}
void VideoCaptureImpl::DoBufferReceivedOnCaptureThread(
int buffer_id, base::Time timestamp) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
if (state_ != video_capture::kStarted) {
Send(new VideoCaptureHostMsg_BufferReady(device_id_, buffer_id));
return;
}
media::VideoCapture::VideoFrameBuffer* buffer;
DCHECK(cached_dibs_.find(buffer_id) != cached_dibs_.end());
buffer = cached_dibs_[buffer_id]->mapped_memory;
buffer->timestamp = timestamp;
for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) {
it->first->OnBufferReady(this, buffer);
}
cached_dibs_[buffer_id]->references = clients_.size();
}
void VideoCaptureImpl::DoStateChangedOnCaptureThread(
video_capture::State state) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
switch (state) {
case video_capture::kStarted:
break;
case video_capture::kStopped:
state_ = video_capture::kStopped;
DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_;
STLDeleteValues(&cached_dibs_);
if (!clients_.empty() || !clients_pending_on_restart_.empty())
RestartCapture();
break;
case video_capture::kPaused:
for (ClientInfo::iterator it = clients_.begin();
it != clients_.end(); ++it) {
it->first->OnPaused(this);
}
break;
case video_capture::kError:
DVLOG(1) << "OnStateChanged: error!, device_id = " << device_id_;
for (ClientInfo::iterator it = clients_.begin();
it != clients_.end(); ++it) {
// TODO(wjia): browser process would send error code.
it->first->OnError(this, 1);
it->first->OnRemoved(this);
}
clients_.clear();
state_ = video_capture::kError;
break;
default:
break;
}
}
void VideoCaptureImpl::DoDeviceInfoReceivedOnCaptureThread(
const media::VideoCaptureParams& device_info) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
DCHECK(!ClientHasDIB());
STLDeleteValues(&cached_dibs_);
device_info_ = device_info;
device_info_available_ = true;
for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) {
it->first->OnDeviceInfoReceived(this, device_info);
}
}
void VideoCaptureImpl::DoDelegateAddedOnCaptureThread(int32 device_id) {
DVLOG(1) << "DoDelegateAdded: device_id " << device_id;
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
device_id_ = device_id;
for (ClientInfo::iterator it = clients_pending_on_filter_.begin();
it != clients_pending_on_filter_.end(); ) {
media::VideoCapture::EventHandler* handler = it->first;
const media::VideoCaptureCapability capability = it->second;
clients_pending_on_filter_.erase(it++);
StartCapture(handler, capability);
}
}
void VideoCaptureImpl::StopDevice() {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
device_info_available_ = false;
if (state_ == video_capture::kStarted) {
state_ = video_capture::kStopping;
Send(new VideoCaptureHostMsg_Stop(device_id_));
current_params_.width = current_params_.height = 0;
}
}
void VideoCaptureImpl::RestartCapture() {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
DCHECK_EQ(state_, video_capture::kStopped);
int width = 0;
int height = 0;
for (ClientInfo::iterator it = clients_.begin();
it != clients_.end(); ++it) {
width = std::max(width, it->second.width);
height = std::max(height, it->second.height);
}
for (ClientInfo::iterator it = clients_pending_on_restart_.begin();
it != clients_pending_on_restart_.end(); ) {
width = std::max(width, it->second.width);
height = std::max(height, it->second.height);
clients_[it->first] = it->second;
clients_pending_on_restart_.erase(it++);
}
current_params_.width = width;
current_params_.height = height;
DVLOG(1) << "RestartCapture, " << current_params_.width << ", "
<< current_params_.height;
StartCaptureInternal();
}
void VideoCaptureImpl::StartCaptureInternal() {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
DCHECK(device_id_);
Send(new VideoCaptureHostMsg_Start(device_id_, current_params_));
state_ = video_capture::kStarted;
}
void VideoCaptureImpl::AddDelegateOnIOThread() {
DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
message_filter_->AddDelegate(this);
}
void VideoCaptureImpl::RemoveDelegateOnIOThread(base::Closure task) {
DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
message_filter_->RemoveDelegate(this);
capture_message_loop_proxy_->PostTask(FROM_HERE, task);
}
void VideoCaptureImpl::Send(IPC::Message* message) {
io_message_loop_proxy_->PostTask(FROM_HERE,
base::Bind(base::IgnoreResult(&VideoCaptureMessageFilter::Send),
message_filter_.get(), message));
}
bool VideoCaptureImpl::ClientHasDIB() const {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
for (CachedDIB::const_iterator it = cached_dibs_.begin();
it != cached_dibs_.end(); ++it) {
if (it->second->references > 0)
return true;
}
return false;
}
bool VideoCaptureImpl::RemoveClient(
media::VideoCapture::EventHandler* handler,
ClientInfo* clients) {
DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread());
bool found = false;
ClientInfo::iterator it = clients->find(handler);
if (it != clients->end()) {
handler->OnStopped(this);
handler->OnRemoved(this);
clients->erase(it);
found = true;
}
return found;
}