| // 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(¤t_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; |
| } |