blob: 9a00155ee0e72653d6a12f2628e3a8712153dff4 [file] [log] [blame]
// 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 <algorithm>
#include <string>
#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<const PPP_VideoCapture_Dev*>(
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<media::VideoCapture::VideoFrameBuffer> 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<size_t>(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 = {
static_cast<uint32_t>(device_info.width),
static_cast<uint32_t>(device_info.height),
static_cast<uint32_t>(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<PP_Resource> 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<PPB_Buffer_API> enter(resources[i], true);
DCHECK(enter.succeeded());
BufferInfo info;
info.buffer = static_cast<PPB_Buffer_Impl*>(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,
const 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,
const 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_.frame_rate = device_info.frames_per_second;
capability_.expected_capture_delay = 0; // Ignored.
capability_.color = media::VideoCaptureCapability::kI420;
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