// 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 "webkit/plugins/ppapi/ppb_video_capture_impl.h" #include #include #include "base/bind.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "ppapi/c/dev/pp_video_capture_dev.h" #include "ppapi/c/dev/ppb_video_capture_dev.h" #include "ppapi/c/pp_completion_callback.h" #include "ppapi/c/pp_errors.h" #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/ppb_device_ref_shared.h" #include "ppapi/shared_impl/resource_tracker.h" #include "ppapi/shared_impl/tracked_callback.h" #include "ppapi/thunk/enter.h" #include "webkit/plugins/ppapi/common.h" #include "webkit/plugins/ppapi/plugin_module.h" #include "webkit/plugins/ppapi/ppapi_plugin_instance.h" #include "webkit/plugins/ppapi/ppb_buffer_impl.h" #include "webkit/plugins/ppapi/resource_helper.h" using ppapi::DeviceRefData; using ppapi::PpapiGlobals; using ppapi::thunk::EnterResourceNoLock; using ppapi::thunk::PPB_Buffer_API; using ppapi::thunk::PPB_VideoCapture_API; using ppapi::TrackedCallback; namespace { // Maximum number of buffers to actually allocate. const uint32_t kMaxBuffers = 20; } // namespace namespace webkit { namespace ppapi { PPB_VideoCapture_Impl::PPB_VideoCapture_Impl(PP_Instance instance) : PPB_VideoCapture_Shared(instance), buffer_count_hint_(0), ppp_videocapture_(NULL), capability_() { } PPB_VideoCapture_Impl::~PPB_VideoCapture_Impl() { Close(); } bool PPB_VideoCapture_Impl::Init() { PluginInstance* instance = ResourceHelper::GetPluginInstance(this); if (!instance) return false; ppp_videocapture_ = static_cast( instance->module()->GetPluginInterface(PPP_VIDEO_CAPTURE_DEV_INTERFACE)); if (!ppp_videocapture_) return false; return true; } void PPB_VideoCapture_Impl::OnStarted(media::VideoCapture* capture) { if (SetStatus(PP_VIDEO_CAPTURE_STATUS_STARTED, false)) SendStatus(); } void PPB_VideoCapture_Impl::OnStopped(media::VideoCapture* capture) { if (SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPED, false)) SendStatus(); } void PPB_VideoCapture_Impl::OnPaused(media::VideoCapture* capture) { if (SetStatus(PP_VIDEO_CAPTURE_STATUS_PAUSED, false)) SendStatus(); } void PPB_VideoCapture_Impl::OnError(media::VideoCapture* capture, int error_code) { // Today, the media layer only sends "1" as an error. DCHECK(error_code == 1); // It either comes because some error was detected while starting (e.g. 2 // conflicting "master" resolution), or because the browser failed to start // the capture. SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPED, true); ppp_videocapture_->OnError(pp_instance(), pp_resource(), PP_ERROR_FAILED); } void PPB_VideoCapture_Impl::OnRemoved(media::VideoCapture* capture) { } void PPB_VideoCapture_Impl::OnBufferReady( media::VideoCapture* capture, scoped_refptr buffer) { DCHECK(buffer.get()); for (uint32_t i = 0; i < buffers_.size(); ++i) { if (!buffers_[i].in_use) { // TODO(ihf): Switch to a size calculation based on stride. // Stride is filled out now but not more meaningful than size // until wjia unifies VideoFrameBuffer and media::VideoFrame. size_t size = std::min(static_cast(buffers_[i].buffer->size()), buffer->buffer_size); memcpy(buffers_[i].data, buffer->memory_pointer, size); buffers_[i].in_use = true; platform_video_capture_->FeedBuffer(buffer); ppp_videocapture_->OnBufferReady(pp_instance(), pp_resource(), i); return; } } } void PPB_VideoCapture_Impl::OnDeviceInfoReceived( media::VideoCapture* capture, const media::VideoCaptureParams& device_info) { PP_VideoCaptureDeviceInfo_Dev info = { device_info.width, device_info.height, device_info.frame_per_second }; ReleaseBuffers(); // Allocate buffers. We keep a reference to them, that is released in // ReleaseBuffers. // YUV 4:2:0 int uv_width = info.width / 2; int uv_height = info.height / 2; size_t size = info.width * info.height + 2 * uv_width * uv_height; scoped_array resources(new PP_Resource[buffer_count_hint_]); buffers_.reserve(buffer_count_hint_); for (size_t i = 0; i < buffer_count_hint_; ++i) { resources[i] = PPB_Buffer_Impl::Create(pp_instance(), size); if (!resources[i]) break; EnterResourceNoLock enter(resources[i], true); DCHECK(enter.succeeded()); BufferInfo info; info.buffer = static_cast(enter.object()); info.data = info.buffer->Map(); if (!info.data) { PpapiGlobals::Get()->GetResourceTracker()->ReleaseResource(resources[i]); break; } buffers_.push_back(info); } if (buffers_.empty()) { // We couldn't allocate/map buffers at all. Send an error and stop the // capture. ppp_videocapture_->OnError(pp_instance(), pp_resource(), PP_ERROR_NOMEMORY); SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPING, true); platform_video_capture_->StopCapture(this); return; } ppp_videocapture_->OnDeviceInfo(pp_instance(), pp_resource(), &info, buffers_.size(), resources.get()); } void PPB_VideoCapture_Impl::OnInitialized(media::VideoCapture* capture, bool succeeded) { DCHECK(capture == platform_video_capture_.get()); OnOpenComplete(succeeded ? PP_OK : PP_ERROR_FAILED); } int32_t PPB_VideoCapture_Impl::InternalEnumerateDevices( PP_Resource* devices, PP_CompletionCallback callback) { PluginInstance* instance = ResourceHelper::GetPluginInstance(this); if (!instance) return PP_ERROR_FAILED; devices_ = devices; enumerate_devices_callback_ = new TrackedCallback(this, callback); instance->delegate()->EnumerateDevices( PP_DEVICETYPE_DEV_VIDEOCAPTURE, base::Bind(&PPB_VideoCapture_Impl::EnumerateDevicesCallbackFunc, AsWeakPtr())); return PP_OK_COMPLETIONPENDING; } int32_t PPB_VideoCapture_Impl::InternalOpen( const std::string& device_id, const PP_VideoCaptureDeviceInfo_Dev& requested_info, uint32_t buffer_count, PP_CompletionCallback callback) { // It is able to complete synchronously if the default device is used. bool sync_completion = device_id.empty(); if (!callback.func && !sync_completion) return PP_ERROR_BLOCKS_MAIN_THREAD; PluginInstance* instance = ResourceHelper::GetPluginInstance(this); if (!instance) return PP_ERROR_FAILED; SetRequestedInfo(requested_info, buffer_count); DCHECK(!platform_video_capture_.get()); platform_video_capture_ = instance->delegate()->CreateVideoCapture(device_id, this); if (sync_completion) { OnInitialized(platform_video_capture_.get(), true); return PP_OK; } else { open_callback_ = new TrackedCallback(this, callback); return PP_OK_COMPLETIONPENDING; } } int32_t PPB_VideoCapture_Impl::InternalStartCapture() { DCHECK(buffers_.empty()); platform_video_capture_->StartCapture(this, capability_); return PP_OK; } int32_t PPB_VideoCapture_Impl::InternalReuseBuffer(uint32_t buffer) { if (buffer >= buffers_.size() || !buffers_[buffer].in_use) return PP_ERROR_BADARGUMENT; buffers_[buffer].in_use = false; return PP_OK; } int32_t PPB_VideoCapture_Impl::InternalStopCapture() { ReleaseBuffers(); platform_video_capture_->StopCapture(this); return PP_OK; } void PPB_VideoCapture_Impl::InternalClose() { StopCapture(); DCHECK(buffers_.empty()); DetachPlatformVideoCapture(); } int32_t PPB_VideoCapture_Impl::InternalStartCapture0_1( const PP_VideoCaptureDeviceInfo_Dev& requested_info, uint32_t buffer_count) { PluginInstance* instance = ResourceHelper::GetPluginInstance(this); if (!instance) { SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPED, true); return PP_ERROR_FAILED; } DCHECK(buffers_.empty()); SetRequestedInfo(requested_info, buffer_count); DetachPlatformVideoCapture(); platform_video_capture_ = instance->delegate()->CreateVideoCapture("", this); platform_video_capture_->StartCapture(this, capability_); return PP_OK; } const PPB_VideoCapture_Impl::DeviceRefDataVector& PPB_VideoCapture_Impl::InternalGetDeviceRefData() const { return devices_data_; } void PPB_VideoCapture_Impl::ReleaseBuffers() { ::ppapi::ResourceTracker* tracker = PpapiGlobals::Get()->GetResourceTracker(); for (size_t i = 0; i < buffers_.size(); ++i) { buffers_[i].buffer->Unmap(); tracker->ReleaseResource(buffers_[i].buffer->pp_resource()); } buffers_.clear(); } void PPB_VideoCapture_Impl::SendStatus() { ppp_videocapture_->OnStatus(pp_instance(), pp_resource(), status_); } void PPB_VideoCapture_Impl::SetRequestedInfo( const PP_VideoCaptureDeviceInfo_Dev& device_info, uint32_t buffer_count) { // Clamp the buffer count to between 1 and |kMaxBuffers|. buffer_count_hint_ = std::min(std::max(buffer_count, 1U), kMaxBuffers); capability_.width = device_info.width; capability_.height = device_info.height; capability_.max_fps = device_info.frames_per_second; capability_.expected_capture_delay = 0; // Ignored. capability_.raw_type = media::VideoFrame::I420; capability_.interlaced = false; // Ignored. } void PPB_VideoCapture_Impl::DetachPlatformVideoCapture() { if (platform_video_capture_.get()) { platform_video_capture_->DetachEventHandler(); platform_video_capture_ = NULL; } } void PPB_VideoCapture_Impl::EnumerateDevicesCallbackFunc( int request_id, bool succeeded, const DeviceRefDataVector& devices) { devices_data_.clear(); if (succeeded) devices_data_ = devices; OnEnumerateDevicesComplete(succeeded ? PP_OK : PP_ERROR_FAILED, devices); } PPB_VideoCapture_Impl::BufferInfo::BufferInfo() : in_use(false), data(NULL), buffer() { } PPB_VideoCapture_Impl::BufferInfo::~BufferInfo() { } } // namespace ppapi } // namespace webkit