// Copyright (c) 2011 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/logging.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/resource_tracker.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::PpapiGlobals; using ppapi::thunk::EnterResourceNoLock; using ppapi::thunk::PPB_Buffer_API; using ppapi::thunk::PPB_VideoCapture_API; 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) : Resource(instance), buffer_count_hint_(0), ppp_videocapture_(NULL), status_(PP_VIDEO_CAPTURE_STATUS_STOPPED), is_dead_(false) { } PPB_VideoCapture_Impl::~PPB_VideoCapture_Impl() { } bool PPB_VideoCapture_Impl::Init() { DCHECK(!is_dead_); 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; platform_video_capture_.reset( instance->delegate()->CreateVideoCapture(this)); return !!platform_video_capture_.get(); } PPB_VideoCapture_API* PPB_VideoCapture_Impl::AsPPB_VideoCapture_API() { return this; } void PPB_VideoCapture_Impl::LastPluginRefWasDeleted() { if (platform_video_capture_.get()) StopCapture(); DCHECK(buffers_.empty()); ppp_videocapture_ = NULL; is_dead_ = true; ::ppapi::Resource::LastPluginRefWasDeleted(); } int32_t PPB_VideoCapture_Impl::StartCapture( const PP_VideoCaptureDeviceInfo_Dev& requested_info, uint32_t buffer_count) { DCHECK(!is_dead_); switch (status_) { case PP_VIDEO_CAPTURE_STATUS_STARTING: case PP_VIDEO_CAPTURE_STATUS_STARTED: case PP_VIDEO_CAPTURE_STATUS_PAUSED: default: return PP_ERROR_FAILED; case PP_VIDEO_CAPTURE_STATUS_STOPPED: case PP_VIDEO_CAPTURE_STATUS_STOPPING: break; } DCHECK(buffers_.empty()); // Clamp the buffer count to between 1 and |kMaxBuffers|. buffer_count_hint_ = std::min(std::max(buffer_count, 1U), kMaxBuffers); media::VideoCapture::VideoCaptureCapability capability = { requested_info.width, requested_info.height, requested_info.frames_per_second, 0, // ignored. media::VideoFrame::I420, false, // ignored }; status_ = PP_VIDEO_CAPTURE_STATUS_STARTING; AddRef(); // Balanced in |OnRemoved()|. platform_video_capture_->StartCapture(this, capability); return PP_OK; } int32_t PPB_VideoCapture_Impl::ReuseBuffer(uint32_t buffer) { DCHECK(!is_dead_); 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::StopCapture() { DCHECK(!is_dead_); switch (status_) { case PP_VIDEO_CAPTURE_STATUS_STOPPED: case PP_VIDEO_CAPTURE_STATUS_STOPPING: default: return PP_ERROR_FAILED; case PP_VIDEO_CAPTURE_STATUS_STARTING: case PP_VIDEO_CAPTURE_STATUS_STARTED: case PP_VIDEO_CAPTURE_STATUS_PAUSED: break; } ReleaseBuffers(); status_ = PP_VIDEO_CAPTURE_STATUS_STOPPING; platform_video_capture_->StopCapture(this); return PP_OK; } void PPB_VideoCapture_Impl::OnStarted(media::VideoCapture* capture) { if (is_dead_) return; switch (status_) { case PP_VIDEO_CAPTURE_STATUS_STARTING: case PP_VIDEO_CAPTURE_STATUS_PAUSED: break; case PP_VIDEO_CAPTURE_STATUS_STOPPED: case PP_VIDEO_CAPTURE_STATUS_STOPPING: case PP_VIDEO_CAPTURE_STATUS_STARTED: default: return; } status_ = PP_VIDEO_CAPTURE_STATUS_STARTED; SendStatus(); } void PPB_VideoCapture_Impl::OnStopped(media::VideoCapture* capture) { if (is_dead_) return; switch (status_) { case PP_VIDEO_CAPTURE_STATUS_STOPPING: break; case PP_VIDEO_CAPTURE_STATUS_STARTING: case PP_VIDEO_CAPTURE_STATUS_PAUSED: case PP_VIDEO_CAPTURE_STATUS_STOPPED: case PP_VIDEO_CAPTURE_STATUS_STARTED: default: return; } status_ = PP_VIDEO_CAPTURE_STATUS_STOPPED; SendStatus(); } void PPB_VideoCapture_Impl::OnPaused(media::VideoCapture* capture) { if (is_dead_) return; switch (status_) { case PP_VIDEO_CAPTURE_STATUS_STARTING: case PP_VIDEO_CAPTURE_STATUS_STARTED: break; case PP_VIDEO_CAPTURE_STATUS_STOPPED: case PP_VIDEO_CAPTURE_STATUS_STOPPING: case PP_VIDEO_CAPTURE_STATUS_PAUSED: default: return; } status_ = PP_VIDEO_CAPTURE_STATUS_PAUSED; SendStatus(); } void PPB_VideoCapture_Impl::OnError(media::VideoCapture* capture, int error_code) { if (is_dead_) return; // 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. status_ = PP_VIDEO_CAPTURE_STATUS_STOPPED; ppp_videocapture_->OnError(pp_instance(), pp_resource(), PP_ERROR_FAILED); } void PPB_VideoCapture_Impl::OnRemoved(media::VideoCapture* capture) { Release(); } void PPB_VideoCapture_Impl::OnBufferReady( media::VideoCapture* capture, scoped_refptr buffer) { if (!is_dead_) { 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; } } } // Even after we have stopped and are dead we have to return buffers that // are in flight to us. Otherwise VideoCaptureController will not tear down. platform_video_capture_->FeedBuffer(buffer); } void PPB_VideoCapture_Impl::OnDeviceInfoReceived( media::VideoCapture* capture, const media::VideoCaptureParams& device_info) { // No need to call |ReleaseBuffers()|: if we're dead, |StopCapture()| should // already have been called. if (is_dead_) return; 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); status_ = PP_VIDEO_CAPTURE_STATUS_STOPPING; platform_video_capture_->StopCapture(this); return; } ppp_videocapture_->OnDeviceInfo(pp_instance(), pp_resource(), &info, buffers_.size(), resources.get()); } void PPB_VideoCapture_Impl::ReleaseBuffers() { DCHECK(!is_dead_); ::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() { DCHECK(!is_dead_); ppp_videocapture_->OnStatus(pp_instance(), pp_resource(), status_); } PPB_VideoCapture_Impl::BufferInfo::BufferInfo() : in_use(false), data(NULL), buffer() { } PPB_VideoCapture_Impl::BufferInfo::~BufferInfo() { } } // namespace ppapi } // namespace webkit