diff options
Diffstat (limited to 'chromium/content/renderer/media/video_capture_impl.cc')
-rw-r--r-- | chromium/content/renderer/media/video_capture_impl.cc | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/chromium/content/renderer/media/video_capture_impl.cc b/chromium/content/renderer/media/video_capture_impl.cc new file mode 100644 index 00000000000..032cd7fedbb --- /dev/null +++ b/chromium/content/renderer/media/video_capture_impl.cc @@ -0,0 +1,491 @@ +// 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/limits.h" + +namespace content { + +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_STATE_STARTED; +} + +int VideoCaptureImpl::CaptureWidth() { + return capture_format_.width; +} + +int VideoCaptureImpl::CaptureHeight() { + return capture_format_.height; +} + +int VideoCaptureImpl::CaptureFrameRate() { + return capture_format_.frame_rate; +} + +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), + suspended_(false), + state_(VIDEO_CAPTURE_STATE_STOPPED) { + DCHECK(filter); + capture_format_.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(VideoCaptureState 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::OnDeviceInfoChanged( + const media::VideoCaptureParams& device_info) { + capture_message_loop_proxy_->PostTask(FROM_HERE, + base::Bind(&VideoCaptureImpl::DoDeviceInfoChangedOnCaptureThread, + 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::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::VideoCaptureCapability& capability) { + 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] = capability; + } else { + handler->OnStarted(this); + if (state_ == VIDEO_CAPTURE_STATE_STARTED) { + // TODO(wjia): Temporarily disable restarting till client supports + // resampling. +#if 0 + if (capability.width > capture_format_.width || + capability.height > capture_format_.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 { +#endif + { + if (device_info_available_) { + handler->OnDeviceInfoReceived(this, device_info_); + } + + clients_[handler] = capability; + } + } else if (state_ == VIDEO_CAPTURE_STATE_STOPPING) { + 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; + int session_id = capture_format_.session_id; + DCHECK_EQ(capability.session_id, 0); + capture_format_ = capability; + capture_format_.session_id = session_id; + if (capture_format_.frame_rate > media::limits::kMaxFramesPerSecond) + capture_format_.frame_rate = media::limits::kMaxFramesPerSecond; + DVLOG(1) << "StartCapture: starting with first resolution (" + << capture_format_.width << "," << capture_format_.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.get() == it->second->mapped_memory.get()) + 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()); + + // 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; + } + + 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_STATE_STARTED || suspended_) { + 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.get(); + 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(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_; + STLDeleteValues(&cached_dibs_); + 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::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::DoDeviceInfoChangedOnCaptureThread( + const media::VideoCaptureParams& device_info) { + DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); + + for (ClientInfo::iterator it = clients_.begin(); it != clients_.end(); ++it) { + it->first->OnDeviceInfoChanged(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::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()); + + device_info_available_ = false; + if (state_ == VIDEO_CAPTURE_STATE_STARTED) { + state_ = VIDEO_CAPTURE_STATE_STOPPING; + Send(new VideoCaptureHostMsg_Stop(device_id_)); + capture_format_.width = capture_format_.height = 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.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++); + } + capture_format_.width = width; + capture_format_.height = height; + DVLOG(1) << "RestartCapture, " << capture_format_.width << ", " + << capture_format_.height; + StartCaptureInternal(); +} + +void VideoCaptureImpl::StartCaptureInternal() { + DCHECK(capture_message_loop_proxy_->BelongsToCurrentThread()); + DCHECK(device_id_); + + media::VideoCaptureParams capability_as_params_copy; + capability_as_params_copy.width = capture_format_.width; + capability_as_params_copy.height = capture_format_.height; + capability_as_params_copy.frame_per_second = capture_format_.frame_rate; + capability_as_params_copy.session_id = capture_format_.session_id; + capability_as_params_copy.frame_size_type = capture_format_.frame_size_type; + Send(new VideoCaptureHostMsg_Start(device_id_, capability_as_params_copy)); + 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::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; +} + +} // namespace content |