blob: 2d671fbfaebfa053dc43a245a2cadf2e77936c27 [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 "content/renderer/pepper/pepper_video_capture_host.h"
#include "content/renderer/media/stream/media_stream_video_source.h"
#include "content/renderer/pepper/host_globals.h"
#include "content/renderer/pepper/pepper_media_device_manager.h"
#include "content/renderer/pepper/pepper_platform_video_capture.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/renderer_ppapi_host_impl.h"
#include "content/renderer/render_frame_impl.h"
#include "media/base/limits.h"
#include "media/base/video_frame.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/host_dispatcher.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/host_resource.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/ppb_buffer_api.h"
using ppapi::HostResource;
using ppapi::TrackedCallback;
using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_Buffer_API;
namespace {
// Maximum number of buffers to actually allocate.
const uint32_t kMaxBuffers = 20;
} // namespace
namespace content {
PepperVideoCaptureHost::PepperVideoCaptureHost(RendererPpapiHostImpl* host,
PP_Instance instance,
PP_Resource resource)
: ResourceHost(host->GetPpapiHost(), instance, resource),
renderer_ppapi_host_(host),
buffer_count_hint_(0),
status_(PP_VIDEO_CAPTURE_STATUS_STOPPED),
enumeration_helper_(this,
PepperMediaDeviceManager::GetForRenderFrame(
host->GetRenderFrameForInstance(pp_instance())),
PP_DEVICETYPE_DEV_VIDEOCAPTURE,
host->GetDocumentURL(instance)) {
}
PepperVideoCaptureHost::~PepperVideoCaptureHost() {
Close();
}
bool PepperVideoCaptureHost::Init() {
return !!renderer_ppapi_host_->GetPluginInstance(pp_instance());
}
int32_t PepperVideoCaptureHost::OnResourceMessageReceived(
const IPC::Message& msg,
ppapi::host::HostMessageContext* context) {
int32_t result = PP_ERROR_FAILED;
if (enumeration_helper_.HandleResourceMessage(msg, context, &result))
return result;
PPAPI_BEGIN_MESSAGE_MAP(PepperVideoCaptureHost, msg)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoCapture_Open, OnOpen)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoCapture_StartCapture,
OnStartCapture)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoCapture_ReuseBuffer,
OnReuseBuffer)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoCapture_StopCapture,
OnStopCapture)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoCapture_Close,
OnClose)
PPAPI_END_MESSAGE_MAP()
return PP_ERROR_FAILED;
}
void PepperVideoCaptureHost::OnInitialized(bool succeeded) {
if (succeeded) {
open_reply_context_.params.set_result(PP_OK);
} else {
DetachPlatformVideoCapture();
open_reply_context_.params.set_result(PP_ERROR_FAILED);
}
host()->SendReply(open_reply_context_,
PpapiPluginMsg_VideoCapture_OpenReply());
}
void PepperVideoCaptureHost::OnStarted() {
if (SetStatus(PP_VIDEO_CAPTURE_STATUS_STARTED, false))
SendStatus();
}
void PepperVideoCaptureHost::OnStopped() {
if (SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPED, false))
SendStatus();
}
void PepperVideoCaptureHost::OnPaused() {
if (SetStatus(PP_VIDEO_CAPTURE_STATUS_PAUSED, false))
SendStatus();
}
void PepperVideoCaptureHost::OnError() {
PostErrorReply();
}
void PepperVideoCaptureHost::PostErrorReply() {
// 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);
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoCapture_OnError(
static_cast<uint32_t>(PP_ERROR_FAILED)));
}
void PepperVideoCaptureHost::OnFrameReady(
const scoped_refptr<media::VideoFrame>& frame) {
DCHECK(frame.get());
if (alloc_size_ != frame->visible_rect().size() || buffers_.empty()) {
alloc_size_ = frame->visible_rect().size();
double frame_rate;
int rounded_frame_rate;
if (frame->metadata()->GetDouble(media::VideoFrameMetadata::FRAME_RATE,
&frame_rate))
rounded_frame_rate = static_cast<int>(frame_rate + 0.5 /* round */);
else
rounded_frame_rate = MediaStreamVideoSource::kUnknownFrameRate;
AllocBuffers(alloc_size_, rounded_frame_rate);
}
for (uint32_t i = 0; i < buffers_.size(); ++i) {
if (!buffers_[i].in_use) {
DCHECK_EQ(frame->format(), media::PIXEL_FORMAT_I420);
if (buffers_[i].buffer->size() <
media::VideoFrame::AllocationSize(frame->format(), alloc_size_)) {
// TODO(ihf): handle size mismatches gracefully here.
return;
}
uint8_t* dst = reinterpret_cast<uint8_t*>(buffers_[i].data);
static_assert(media::VideoFrame::kYPlane == 0, "y plane should be 0");
static_assert(media::VideoFrame::kUPlane == 1, "u plane should be 1");
static_assert(media::VideoFrame::kVPlane == 2, "v plane should be 2");
for (size_t j = 0; j < media::VideoFrame::NumPlanes(frame->format());
++j) {
const uint8_t* src = frame->visible_data(j);
const size_t row_bytes = frame->row_bytes(j);
const size_t src_stride = frame->stride(j);
for (int k = 0; k < frame->rows(j); ++k) {
memcpy(dst, src, row_bytes);
dst += row_bytes;
src += src_stride;
}
}
buffers_[i].in_use = true;
host()->SendUnsolicitedReply(
pp_resource(), PpapiPluginMsg_VideoCapture_OnBufferReady(i));
return;
}
}
}
void PepperVideoCaptureHost::AllocBuffers(const gfx::Size& resolution,
int frame_rate) {
PP_VideoCaptureDeviceInfo_Dev info = {
static_cast<uint32_t>(resolution.width()),
static_cast<uint32_t>(resolution.height()),
static_cast<uint32_t>(frame_rate)};
ReleaseBuffers();
const size_t size = media::VideoFrame::AllocationSize(
media::PIXEL_FORMAT_I420, gfx::Size(info.width, info.height));
ppapi::proxy::ResourceMessageReplyParams params(pp_resource(), 0);
// Allocate buffers. We keep a reference to them, that is released in
// ReleaseBuffers. In the mean time, we prepare the resource and handle here
// for sending below.
std::vector<HostResource> buffer_host_resources;
buffers_.reserve(buffer_count_hint_);
ppapi::ResourceTracker* tracker = HostGlobals::Get()->GetResourceTracker();
ppapi::proxy::HostDispatcher* dispatcher =
ppapi::proxy::HostDispatcher::GetForInstance(pp_instance());
for (size_t i = 0; i < buffer_count_hint_; ++i) {
PP_Resource res = PPB_Buffer_Impl::Create(pp_instance(), size);
if (!res)
break;
EnterResourceNoLock<PPB_Buffer_API> enter(res, true);
DCHECK(enter.succeeded());
BufferInfo buf;
buf.buffer = static_cast<PPB_Buffer_Impl*>(enter.object());
buf.data = buf.buffer->Map();
if (!buf.data) {
tracker->ReleaseResource(res);
break;
}
buffers_.push_back(buf);
// Add to HostResource array to be sent.
{
HostResource host_resource;
host_resource.SetHostResource(pp_instance(), res);
buffer_host_resources.push_back(host_resource);
// Add a reference for the plugin, which is resposible for releasing it.
tracker->AddRefResource(res);
}
// Add the serialized shared memory handle to params. FileDescriptor is
// treated in special case.
{
EnterResourceNoLock<PPB_Buffer_API> enter2(res, true);
DCHECK(enter2.succeeded());
base::SharedMemory* shm;
int32_t result = enter2.object()->GetSharedMemory(&shm);
DCHECK(result == PP_OK);
params.AppendHandle(ppapi::proxy::SerializedHandle(
dispatcher->ShareSharedMemoryHandleWithRemote(shm->handle()), size));
}
}
if (buffers_.empty()) {
// We couldn't allocate/map buffers at all. Send an error and stop the
// capture.
SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPING, true);
platform_video_capture_->StopCapture();
PostErrorReply();
return;
}
host()->Send(
new PpapiPluginMsg_ResourceReply(params,
PpapiPluginMsg_VideoCapture_OnDeviceInfo(
info, buffer_host_resources, size)));
}
int32_t PepperVideoCaptureHost::OnOpen(
ppapi::host::HostMessageContext* context,
const std::string& device_id,
const PP_VideoCaptureDeviceInfo_Dev& requested_info,
uint32_t buffer_count) {
if (platform_video_capture_.get())
return PP_ERROR_FAILED;
SetRequestedInfo(requested_info, buffer_count);
GURL document_url = renderer_ppapi_host_->GetDocumentURL(pp_instance());
if (!document_url.is_valid())
return PP_ERROR_FAILED;
platform_video_capture_.reset(new PepperPlatformVideoCapture(
renderer_ppapi_host_->GetRenderFrameForInstance(pp_instance())->
GetRoutingID(),
device_id,
this));
open_reply_context_ = context->MakeReplyMessageContext();
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoCaptureHost::OnStartCapture(
ppapi::host::HostMessageContext* context) {
if (!SetStatus(PP_VIDEO_CAPTURE_STATUS_STARTING, false) ||
!platform_video_capture_.get())
return PP_ERROR_FAILED;
DCHECK(buffers_.empty());
// It's safe to call this regardless it's capturing or not, because
// PepperPlatformVideoCapture maintains the state.
platform_video_capture_->StartCapture(video_capture_params_);
return PP_OK;
}
int32_t PepperVideoCaptureHost::OnReuseBuffer(
ppapi::host::HostMessageContext* context,
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 PepperVideoCaptureHost::OnStopCapture(
ppapi::host::HostMessageContext* context) {
return StopCapture();
}
int32_t PepperVideoCaptureHost::OnClose(
ppapi::host::HostMessageContext* context) {
return Close();
}
int32_t PepperVideoCaptureHost::StopCapture() {
if (!SetStatus(PP_VIDEO_CAPTURE_STATUS_STOPPING, false))
return PP_ERROR_FAILED;
DCHECK(platform_video_capture_.get());
ReleaseBuffers();
// It's safe to call this regardless it's capturing or not, because
// PepperPlatformVideoCapture maintains the state.
platform_video_capture_->StopCapture();
return PP_OK;
}
int32_t PepperVideoCaptureHost::Close() {
if (!platform_video_capture_.get())
return PP_OK;
StopCapture();
DCHECK(buffers_.empty());
DetachPlatformVideoCapture();
return PP_OK;
}
void PepperVideoCaptureHost::ReleaseBuffers() {
ppapi::ResourceTracker* tracker = HostGlobals::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 PepperVideoCaptureHost::SendStatus() {
host()->SendUnsolicitedReply(pp_resource(),
PpapiPluginMsg_VideoCapture_OnStatus(status_));
}
void PepperVideoCaptureHost::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);
// Clamp the frame rate to between 1 and |kMaxFramesPerSecond - 1|.
int frames_per_second =
std::min(std::max(device_info.frames_per_second, 1U),
static_cast<uint32_t>(media::limits::kMaxFramesPerSecond - 1));
video_capture_params_.requested_format = media::VideoCaptureFormat(
gfx::Size(device_info.width, device_info.height), frames_per_second,
media::PIXEL_FORMAT_I420);
}
void PepperVideoCaptureHost::DetachPlatformVideoCapture() {
if (platform_video_capture_) {
platform_video_capture_->DetachEventHandler();
platform_video_capture_.reset();
}
}
bool PepperVideoCaptureHost::SetStatus(PP_VideoCaptureStatus_Dev status,
bool forced) {
if (!forced) {
switch (status) {
case PP_VIDEO_CAPTURE_STATUS_STOPPED:
if (status_ != PP_VIDEO_CAPTURE_STATUS_STOPPING)
return false;
break;
case PP_VIDEO_CAPTURE_STATUS_STARTING:
if (status_ != PP_VIDEO_CAPTURE_STATUS_STOPPED)
return false;
break;
case PP_VIDEO_CAPTURE_STATUS_STARTED:
switch (status_) {
case PP_VIDEO_CAPTURE_STATUS_STARTING:
case PP_VIDEO_CAPTURE_STATUS_PAUSED:
break;
default:
return false;
}
break;
case PP_VIDEO_CAPTURE_STATUS_PAUSED:
switch (status_) {
case PP_VIDEO_CAPTURE_STATUS_STARTING:
case PP_VIDEO_CAPTURE_STATUS_STARTED:
break;
default:
return false;
}
break;
case PP_VIDEO_CAPTURE_STATUS_STOPPING:
switch (status_) {
case PP_VIDEO_CAPTURE_STATUS_STARTING:
case PP_VIDEO_CAPTURE_STATUS_STARTED:
case PP_VIDEO_CAPTURE_STATUS_PAUSED:
break;
default:
return false;
}
break;
}
}
status_ = status;
return true;
}
PepperVideoCaptureHost::BufferInfo::BufferInfo()
: in_use(false), data(nullptr), buffer() {}
PepperVideoCaptureHost::BufferInfo::BufferInfo(const BufferInfo& other) =
default;
PepperVideoCaptureHost::BufferInfo::~BufferInfo() {
}
} // namespace content