// 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/child/child_process.h" #include "content/common/media/video_capture_messages.h" #include "media/base/bind_to_loop.h" #include "media/base/limits.h" #include "media/base/video_frame.h" namespace content { class VideoCaptureImpl::ClientBuffer : public base::RefCountedThreadSafe { public: ClientBuffer(scoped_ptr buffer, size_t buffer_size) : buffer(buffer.Pass()), buffer_size(buffer_size) {} const scoped_ptr buffer; const size_t buffer_size; private: friend class base::RefCountedThreadSafe; virtual ~ClientBuffer() {} DISALLOW_COPY_AND_ASSIGN(ClientBuffer); }; bool VideoCaptureImpl::CaptureStarted() { return state_ == VIDEO_CAPTURE_STATE_STARTED; } int VideoCaptureImpl::CaptureFrameRate() { return last_frame_format_.frame_rate; } VideoCaptureImpl::VideoCaptureImpl( const media::VideoCaptureSessionId session_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), session_id_(session_id), suspended_(false), state_(VIDEO_CAPTURE_STATE_STOPPED), weak_this_factory_(this) { DCHECK(filter); } VideoCaptureImpl::~VideoCaptureImpl() {} 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::VideoCaptureParams& params) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoStartCaptureOnCaptureThread, base::Unretained(this), handler, params)); } void VideoCaptureImpl::StopCapture(media::VideoCapture::EventHandler* handler) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoStopCaptureOnCaptureThread, base::Unretained(this), handler)); } 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::OnBufferDestroyed(int buffer_id) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoBufferDestroyedOnCaptureThread, base::Unretained(this), buffer_id)); } void VideoCaptureImpl::OnBufferReceived( int buffer_id, base::Time timestamp, const media::VideoCaptureFormat& format) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoBufferReceivedOnCaptureThread, base::Unretained(this), buffer_id, timestamp, format)); } void VideoCaptureImpl::OnStateChanged(VideoCaptureState state) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoStateChangedOnCaptureThread, base::Unretained(this), state)); } void VideoCaptureImpl::OnDelegateAdded(int32 device_id) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoDelegateAddedOnCaptureThread, base::Unretained(this), device_id)); } void VideoCaptureImpl::SuspendCapture(bool suspend) { capture_message_loop_proxy_->PostTask(FROM_HERE, base::Bind(&VideoCaptureImpl::DoSuspendCaptureOnCaptureThread, base::Unretained(this), suspend)); } void VideoCaptureImpl::DoDeInitOnCaptureThread(base::Closure task) { if (state_ == VIDEO_CAPTURE_STATE_STARTED) 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::VideoCaptureParams& params) { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); if (state_ == VIDEO_CAPTURE_STATE_ERROR) { 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] = params; } else { handler->OnStarted(this); if (state_ == VIDEO_CAPTURE_STATE_STARTED) { clients_[handler] = params; } else if (state_ == VIDEO_CAPTURE_STATE_STOPPING) { clients_pending_on_restart_[handler] = params; DVLOG(1) << "StartCapture: Got new resolution " << params.requested_format.frame_size.ToString() << " during stopping."; } else { // TODO(sheu): Allowing resolution change will require that all // outstanding clients of a capture session support resolution change. DCHECK(!params.allow_resolution_change); clients_[handler] = params; DCHECK_EQ(1ul, clients_.size()); params_ = params; if (params_.requested_format.frame_rate > media::limits::kMaxFramesPerSecond) { params_.requested_format.frame_rate = media::limits::kMaxFramesPerSecond; } DVLOG(1) << "StartCapture: starting with first resolution " << params_.requested_format.frame_size.ToString(); 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(); client_buffers_.clear(); weak_this_factory_.InvalidateWeakPtrs(); } } void VideoCaptureImpl::DoBufferCreatedOnCaptureThread( base::SharedMemoryHandle handle, int length, int buffer_id) { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); // In case client calls StopCapture before the arrival of created buffer, // just close this buffer and return. if (state_ != VIDEO_CAPTURE_STATE_STARTED) { base::SharedMemory::CloseHandle(handle); return; } scoped_ptr shm(new base::SharedMemory(handle, false)); if (!shm->Map(length)) { DLOG(ERROR) << "DoBufferCreatedOnCaptureThread: Map() failed."; return; } bool inserted = client_buffers_.insert(std::make_pair( buffer_id, new ClientBuffer(shm.Pass(), length))).second; DCHECK(inserted); } void VideoCaptureImpl::DoBufferDestroyedOnCaptureThread(int buffer_id) { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); ClientBufferMap::iterator iter = client_buffers_.find(buffer_id); if (iter == client_buffers_.end()) return; DCHECK(!iter->second || iter->second->HasOneRef()) << "Instructed to delete buffer we are still using."; client_buffers_.erase(iter); } void VideoCaptureImpl::DoBufferReceivedOnCaptureThread( int buffer_id, base::Time timestamp, const media::VideoCaptureFormat& format) { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); if (state_ != VIDEO_CAPTURE_STATE_STARTED || suspended_) { Send(new VideoCaptureHostMsg_BufferReady(device_id_, buffer_id)); return; } last_frame_format_ = format; ClientBufferMap::iterator iter = client_buffers_.find(buffer_id); DCHECK(iter != client_buffers_.end()); scoped_refptr buffer = iter->second; scoped_refptr frame = media::VideoFrame::WrapExternalPackedMemory( media::VideoFrame::I420, last_frame_format_.frame_size, gfx::Rect(last_frame_format_.frame_size), last_frame_format_.frame_size, reinterpret_cast(buffer->buffer->memory()), buffer->buffer_size, buffer->buffer->handle(), // TODO(sheu): convert VideoCaptureMessageFilter::Delegate to use // base::TimeTicks instead of base::Time. http://crbug.com/249215 timestamp - base::Time::UnixEpoch(), media::BindToLoop( capture_message_loop_proxy_, base::Bind( &VideoCaptureImpl::DoClientBufferFinishedOnCaptureThread, weak_this_factory_.GetWeakPtr(), buffer_id, buffer))); for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) it->first->OnFrameReady(this, frame); } void VideoCaptureImpl::DoClientBufferFinishedOnCaptureThread( int buffer_id, const scoped_refptr& buffer) { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); Send(new VideoCaptureHostMsg_BufferReady(device_id_, buffer_id)); } void VideoCaptureImpl::DoStateChangedOnCaptureThread(VideoCaptureState state) { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); switch (state) { case VIDEO_CAPTURE_STATE_STARTED: break; case VIDEO_CAPTURE_STATE_STOPPED: state_ = VIDEO_CAPTURE_STATE_STOPPED; DVLOG(1) << "OnStateChanged: stopped!, device_id = " << device_id_; client_buffers_.clear(); weak_this_factory_.InvalidateWeakPtrs(); if (!clients_.empty() || !clients_pending_on_restart_.empty()) RestartCapture(); break; case VIDEO_CAPTURE_STATE_PAUSED: for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) { it->first->OnPaused(this); } break; case VIDEO_CAPTURE_STATE_ERROR: 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_STATE_ERROR; break; case VIDEO_CAPTURE_STATE_ENDED: DVLOG(1) << "OnStateChanged: ended!, device_id = " << device_id_; for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) { it->first->OnRemoved(this); } clients_.clear(); state_ = VIDEO_CAPTURE_STATE_ENDED; break; default: break; } } 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::VideoCaptureParams params = it->second; clients_pending_on_filter_.erase(it++); StartCapture(handler, params); } } void VideoCaptureImpl::DoSuspendCaptureOnCaptureThread(bool suspend) { DVLOG(1) << "DoSuspendCapture: suspend " << (suspend ? "yes" : "no"); DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); suspended_ = suspend; } void VideoCaptureImpl::StopDevice() { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); if (state_ == VIDEO_CAPTURE_STATE_STARTED) { state_ = VIDEO_CAPTURE_STATE_STOPPING; Send(new VideoCaptureHostMsg_Stop(device_id_)); params_.requested_format.frame_size.SetSize(0, 0); } } void VideoCaptureImpl::RestartCapture() { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); DCHECK_EQ(state_, VIDEO_CAPTURE_STATE_STOPPED); int width = 0; int height = 0; for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) { width = std::max(width, it->second.requested_format.frame_size.width()); height = std::max(height, it->second.requested_format.frame_size.height()); } for (ClientInfo::iterator it = clients_pending_on_restart_.begin(); it != clients_pending_on_restart_.end(); ) { width = std::max(width, it->second.requested_format.frame_size.width()); height = std::max(height, it->second.requested_format.frame_size.height()); clients_[it->first] = it->second; clients_pending_on_restart_.erase(it++); } params_.requested_format.frame_size.SetSize(width, height); DVLOG(1) << "RestartCapture, " << params_.requested_format.frame_size.ToString(); StartCaptureInternal(); } void VideoCaptureImpl::StartCaptureInternal() { DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); DCHECK(device_id_); Send(new VideoCaptureHostMsg_Start(device_id_, session_id_, params_)); state_ = VIDEO_CAPTURE_STATE_STARTED; } 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::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; } } // namespace content