blob: fa05ef9961336741a5295c698e5a884fddb9b121 [file] [log] [blame]
// Copyright (c) 2014 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_decoder_host.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/memory/shared_memory.h"
#include "build/build_config.h"
#include "content/common/pepper_file_util.h"
#include "content/public/common/content_client.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/renderer_ppapi_host.h"
#include "content/renderer/pepper/gfx_conversion.h"
#include "content/renderer/pepper/ppb_graphics_3d_impl.h"
#include "content/renderer/pepper/video_decoder_shim.h"
#include "gpu/ipc/client/command_buffer_proxy_impl.h"
#include "media/base/limits.h"
#include "media/base/media_util.h"
#include "media/gpu/ipc/client/gpu_video_decode_accelerator_host.h"
#include "media/video/video_decode_accelerator.h"
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/video_decoder_constants.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/ppb_graphics_3d_api.h"
using ppapi::proxy::SerializedHandle;
using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_Graphics3D_API;
namespace content {
namespace {
media::VideoCodecProfile PepperToMediaVideoProfile(PP_VideoProfile profile) {
switch (profile) {
case PP_VIDEOPROFILE_H264BASELINE:
return media::H264PROFILE_BASELINE;
case PP_VIDEOPROFILE_H264MAIN:
return media::H264PROFILE_MAIN;
case PP_VIDEOPROFILE_H264EXTENDED:
return media::H264PROFILE_EXTENDED;
case PP_VIDEOPROFILE_H264HIGH:
return media::H264PROFILE_HIGH;
case PP_VIDEOPROFILE_H264HIGH10PROFILE:
return media::H264PROFILE_HIGH10PROFILE;
case PP_VIDEOPROFILE_H264HIGH422PROFILE:
return media::H264PROFILE_HIGH422PROFILE;
case PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE:
return media::H264PROFILE_HIGH444PREDICTIVEPROFILE;
case PP_VIDEOPROFILE_H264SCALABLEBASELINE:
return media::H264PROFILE_SCALABLEBASELINE;
case PP_VIDEOPROFILE_H264SCALABLEHIGH:
return media::H264PROFILE_SCALABLEHIGH;
case PP_VIDEOPROFILE_H264STEREOHIGH:
return media::H264PROFILE_STEREOHIGH;
case PP_VIDEOPROFILE_H264MULTIVIEWHIGH:
return media::H264PROFILE_MULTIVIEWHIGH;
case PP_VIDEOPROFILE_VP8_ANY:
return media::VP8PROFILE_ANY;
case PP_VIDEOPROFILE_VP9_ANY:
return media::VP9PROFILE_PROFILE0;
// No default case, to catch unhandled PP_VideoProfile values.
}
return media::VIDEO_CODEC_PROFILE_UNKNOWN;
}
} // namespace
PepperVideoDecoderHost::PendingDecode::PendingDecode(
int32_t decode_id,
uint32_t shm_id,
uint32_t size,
const ppapi::host::ReplyMessageContext& reply_context)
: decode_id(decode_id),
shm_id(shm_id),
size(size),
reply_context(reply_context) {}
PepperVideoDecoderHost::PendingDecode::~PendingDecode() {}
PepperVideoDecoderHost::PepperVideoDecoderHost(RendererPpapiHost* host,
PP_Instance instance,
PP_Resource resource)
: ResourceHost(host->GetPpapiHost(), instance, resource),
renderer_ppapi_host_(host) {}
PepperVideoDecoderHost::~PepperVideoDecoderHost() {}
int32_t PepperVideoDecoderHost::OnResourceMessageReceived(
const IPC::Message& msg,
ppapi::host::HostMessageContext* context) {
PPAPI_BEGIN_MESSAGE_MAP(PepperVideoDecoderHost, msg)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_Initialize,
OnHostMsgInitialize)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_GetShm,
OnHostMsgGetShm)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_Decode,
OnHostMsgDecode)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_AssignTextures,
OnHostMsgAssignTextures)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_RecyclePicture,
OnHostMsgRecyclePicture)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoDecoder_Flush,
OnHostMsgFlush)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoDecoder_Reset,
OnHostMsgReset)
PPAPI_END_MESSAGE_MAP()
return PP_ERROR_FAILED;
}
int32_t PepperVideoDecoderHost::OnHostMsgInitialize(
ppapi::host::HostMessageContext* context,
const ppapi::HostResource& graphics_context,
PP_VideoProfile profile,
PP_HardwareAcceleration acceleration,
uint32_t min_picture_count) {
if (initialized_)
return PP_ERROR_FAILED;
if (min_picture_count > ppapi::proxy::kMaximumPictureCount)
return PP_ERROR_BADARGUMENT;
EnterResourceNoLock<PPB_Graphics3D_API> enter_graphics(
graphics_context.host_resource(), true);
if (enter_graphics.failed())
return PP_ERROR_FAILED;
PPB_Graphics3D_Impl* graphics3d =
static_cast<PPB_Graphics3D_Impl*>(enter_graphics.object());
gpu::CommandBufferProxyImpl* command_buffer =
graphics3d->GetCommandBufferProxy();
if (!command_buffer)
return PP_ERROR_FAILED;
profile_ = PepperToMediaVideoProfile(profile);
software_fallback_allowed_ = (acceleration != PP_HARDWAREACCELERATION_ONLY);
min_picture_count_ = min_picture_count;
if (acceleration != PP_HARDWAREACCELERATION_NONE) {
// This is not synchronous, but subsequent IPC messages will be buffered, so
// it is okay to immediately send IPC messages.
if (command_buffer->channel()) {
decoder_.reset(new media::GpuVideoDecodeAcceleratorHost(command_buffer));
media::VideoDecodeAccelerator::Config vda_config(profile_);
vda_config.supported_output_formats.assign(
{media::PIXEL_FORMAT_XRGB, media::PIXEL_FORMAT_ARGB});
if (decoder_->Initialize(vda_config, this)) {
initialized_ = true;
return PP_OK;
}
}
decoder_.reset();
if (acceleration == PP_HARDWAREACCELERATION_ONLY)
return PP_ERROR_NOTSUPPORTED;
}
#if defined(OS_ANDROID)
return PP_ERROR_NOTSUPPORTED;
#else
if (!TryFallbackToSoftwareDecoder())
return PP_ERROR_FAILED;
initialized_ = true;
return PP_OK;
#endif
}
int32_t PepperVideoDecoderHost::OnHostMsgGetShm(
ppapi::host::HostMessageContext* context,
uint32_t shm_id,
uint32_t shm_size) {
if (!initialized_)
return PP_ERROR_FAILED;
// Make the buffers larger since we hope to reuse them.
shm_size = std::max(
shm_size,
static_cast<uint32_t>(ppapi::proxy::kMinimumBitstreamBufferSize));
if (shm_size > ppapi::proxy::kMaximumBitstreamBufferSize)
return PP_ERROR_FAILED;
if (shm_id >= ppapi::proxy::kMaximumPendingDecodes)
return PP_ERROR_FAILED;
// The shm_id must be inside or at the end of shm_buffers_.
if (shm_id > shm_buffers_.size())
return PP_ERROR_FAILED;
// Reject an attempt to reallocate a busy shm buffer.
if (shm_id < shm_buffers_.size() && shm_buffer_busy_[shm_id])
return PP_ERROR_FAILED;
content::RenderThread* render_thread = content::RenderThread::Get();
std::unique_ptr<base::SharedMemory> shm(
render_thread->HostAllocateSharedMemoryBuffer(shm_size));
if (!shm || !shm->Map(shm_size))
return PP_ERROR_FAILED;
base::SharedMemoryHandle shm_handle = shm->handle();
if (shm_id == shm_buffers_.size()) {
shm_buffers_.push_back(std::move(shm));
shm_buffer_busy_.push_back(false);
} else {
shm_buffers_[shm_id] = std::move(shm);
}
SerializedHandle handle(
renderer_ppapi_host_->ShareSharedMemoryHandleWithRemote(shm_handle),
shm_size);
ppapi::host::ReplyMessageContext reply_context =
context->MakeReplyMessageContext();
reply_context.params.AppendHandle(std::move(handle));
host()->SendReply(reply_context,
PpapiPluginMsg_VideoDecoder_GetShmReply(shm_size));
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoDecoderHost::OnHostMsgDecode(
ppapi::host::HostMessageContext* context,
uint32_t shm_id,
uint32_t size,
int32_t decode_id) {
if (!initialized_)
return PP_ERROR_FAILED;
DCHECK(decoder_);
// |shm_id| is just an index into shm_buffers_. Make sure it's in range.
if (static_cast<size_t>(shm_id) >= shm_buffers_.size())
return PP_ERROR_FAILED;
// Reject an attempt to pass a busy buffer to the decoder again.
if (shm_buffer_busy_[shm_id])
return PP_ERROR_FAILED;
// Reject non-unique decode_id values.
if (GetPendingDecodeById(decode_id) != pending_decodes_.end())
return PP_ERROR_FAILED;
if (flush_reply_context_.is_valid() || reset_reply_context_.is_valid())
return PP_ERROR_FAILED;
pending_decodes_.push_back(PendingDecode(decode_id, shm_id, size,
context->MakeReplyMessageContext()));
shm_buffer_busy_[shm_id] = true;
decoder_->Decode(
media::BitstreamBuffer(decode_id, shm_buffers_[shm_id]->handle(), size));
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoDecoderHost::OnHostMsgAssignTextures(
ppapi::host::HostMessageContext* context,
const PP_Size& size,
const std::vector<uint32_t>& texture_ids,
const std::vector<gpu::Mailbox>& mailboxes) {
if (!initialized_)
return PP_ERROR_FAILED;
if (texture_ids.size() != mailboxes.size())
return PP_ERROR_FAILED;
DCHECK(decoder_);
pending_texture_requests_--;
DCHECK_GE(pending_texture_requests_, 0);
// If |assign_textures_messages_to_dismiss_| is not 0 then decrement it and
// dismiss the textures. This is necessary to ensure that after SW decoder
// fallback the textures that were requested by the failed HW decoder are not
// passed to the SW decoder.
if (assign_textures_messages_to_dismiss_ > 0) {
assign_textures_messages_to_dismiss_--;
PictureBufferMap pictures_pending_dismission;
for (auto& texture_id : texture_ids) {
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoDecoder_DismissPicture(texture_id));
}
picture_buffer_map_.swap(pictures_pending_dismission);
return PP_OK;
}
// Verify that the new texture IDs are unique and store them in
// |new_textures|.
PictureBufferMap new_textures;
for (uint32_t i = 0; i < texture_ids.size(); i++) {
if (picture_buffer_map_.find(texture_ids[i]) != picture_buffer_map_.end() ||
new_textures.find(texture_ids[i]) != new_textures.end()) {
// Can't assign the same texture more than once.
return PP_ERROR_BADARGUMENT;
}
new_textures.insert(
std::make_pair(texture_ids[i], PictureBufferState::ASSIGNED));
}
picture_buffer_map_.insert(new_textures.begin(), new_textures.end());
std::vector<media::PictureBuffer> picture_buffers;
for (uint32_t i = 0; i < texture_ids.size(); i++) {
media::PictureBuffer::TextureIds ids;
ids.push_back(texture_ids[i]);
media::PictureBuffer buffer(
texture_ids[i], // Use the texture_id to identify the buffer.
gfx::Size(size.width, size.height), ids);
picture_buffers.push_back(buffer);
}
texture_mailboxes_ = mailboxes;
decoder_->AssignPictureBuffers(picture_buffers);
return PP_OK;
}
int32_t PepperVideoDecoderHost::OnHostMsgRecyclePicture(
ppapi::host::HostMessageContext* context,
uint32_t texture_id) {
if (!initialized_)
return PP_ERROR_FAILED;
DCHECK(decoder_);
auto it = picture_buffer_map_.find(texture_id);
if (it == picture_buffer_map_.end())
return PP_ERROR_BADARGUMENT;
switch (it->second) {
case PictureBufferState::ASSIGNED:
return PP_ERROR_BADARGUMENT;
case PictureBufferState::IN_USE:
it->second = PictureBufferState::ASSIGNED;
decoder_->ReusePictureBuffer(texture_id);
break;
case PictureBufferState::DISMISSED:
picture_buffer_map_.erase(it);
// The texture was already dismissed by the decoder. Notify the plugin.
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoDecoder_DismissPicture(texture_id));
break;
}
return PP_OK;
}
int32_t PepperVideoDecoderHost::OnHostMsgFlush(
ppapi::host::HostMessageContext* context) {
if (!initialized_)
return PP_ERROR_FAILED;
DCHECK(decoder_);
if (flush_reply_context_.is_valid() || reset_reply_context_.is_valid())
return PP_ERROR_FAILED;
flush_reply_context_ = context->MakeReplyMessageContext();
decoder_->Flush();
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoDecoderHost::OnHostMsgReset(
ppapi::host::HostMessageContext* context) {
if (!initialized_)
return PP_ERROR_FAILED;
DCHECK(decoder_);
if (flush_reply_context_.is_valid() || reset_reply_context_.is_valid())
return PP_ERROR_FAILED;
reset_reply_context_ = context->MakeReplyMessageContext();
decoder_->Reset();
return PP_OK_COMPLETIONPENDING;
}
void PepperVideoDecoderHost::ProvidePictureBuffers(
uint32_t requested_num_of_buffers,
media::VideoPixelFormat format,
uint32_t textures_per_buffer,
const gfx::Size& dimensions,
uint32_t texture_target) {
DCHECK_EQ(1u, textures_per_buffer);
coded_size_ = dimensions;
pending_texture_requests_++;
host()->SendUnsolicitedReply(
pp_resource(), PpapiPluginMsg_VideoDecoder_RequestTextures(
std::max(min_picture_count_, requested_num_of_buffers),
PP_MakeSize(dimensions.width(), dimensions.height()),
texture_target));
}
void PepperVideoDecoderHost::PictureReady(const media::Picture& picture) {
auto it = picture_buffer_map_.find(picture.picture_buffer_id());
DCHECK(it != picture_buffer_map_.end());
// VDA might send the same picture multiple times in VP9 video. However the
// Pepper client might not able to handle it. Therefore we just catch it here.
// https://crbug.com/755887
CHECK(it->second == PictureBufferState::ASSIGNED);
it->second = PictureBufferState::IN_USE;
if (software_fallback_used_) {
media::ReportPepperVideoDecoderOutputPictureCountSW(coded_size_.height());
} else {
media::ReportPepperVideoDecoderOutputPictureCountHW(coded_size_.height());
}
// Don't bother validating the visible rect, since the plugin process is less
// trusted than the gpu process.
PP_Rect visible_rect = PP_FromGfxRect(picture.visible_rect());
host()->SendUnsolicitedReply(pp_resource(),
PpapiPluginMsg_VideoDecoder_PictureReady(
picture.bitstream_buffer_id(),
picture.picture_buffer_id(), visible_rect));
}
void PepperVideoDecoderHost::DismissPictureBuffer(int32_t picture_buffer_id) {
auto it = picture_buffer_map_.find(picture_buffer_id);
DCHECK(it != picture_buffer_map_.end());
// If the texture is still used by the plugin keep it until the plugin
// recycles it.
if (it->second == PictureBufferState::IN_USE) {
it->second = PictureBufferState::DISMISSED;
return;
}
DCHECK(it->second == PictureBufferState::ASSIGNED);
picture_buffer_map_.erase(it);
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoDecoder_DismissPicture(picture_buffer_id));
}
void PepperVideoDecoderHost::NotifyEndOfBitstreamBuffer(
int32_t bitstream_buffer_id) {
auto it = GetPendingDecodeById(bitstream_buffer_id);
if (it == pending_decodes_.end()) {
NOTREACHED();
return;
}
host()->SendReply(it->reply_context,
PpapiPluginMsg_VideoDecoder_DecodeReply(it->shm_id));
shm_buffer_busy_[it->shm_id] = false;
pending_decodes_.erase(it);
}
void PepperVideoDecoderHost::NotifyFlushDone() {
DCHECK(pending_decodes_.empty());
host()->SendReply(flush_reply_context_,
PpapiPluginMsg_VideoDecoder_FlushReply());
flush_reply_context_ = ppapi::host::ReplyMessageContext();
}
void PepperVideoDecoderHost::NotifyResetDone() {
DCHECK(pending_decodes_.empty());
host()->SendReply(reset_reply_context_,
PpapiPluginMsg_VideoDecoder_ResetReply());
reset_reply_context_ = ppapi::host::ReplyMessageContext();
}
void PepperVideoDecoderHost::NotifyError(
media::VideoDecodeAccelerator::Error error) {
int32_t pp_error = PP_ERROR_FAILED;
switch (error) {
case media::VideoDecodeAccelerator::UNREADABLE_INPUT:
pp_error = PP_ERROR_MALFORMED_INPUT;
break;
case media::VideoDecodeAccelerator::ILLEGAL_STATE:
case media::VideoDecodeAccelerator::INVALID_ARGUMENT:
case media::VideoDecodeAccelerator::PLATFORM_FAILURE:
pp_error = PP_ERROR_RESOURCE_FAILED;
break;
// No default case, to catch unhandled enum values.
}
// Try to initialize software decoder and use it instead.
if (!software_fallback_used_ && software_fallback_allowed_) {
VLOG(0)
<< "Hardware decoder has returned an error. Trying Software decoder.";
if (TryFallbackToSoftwareDecoder())
return;
}
host()->SendUnsolicitedReply(
pp_resource(), PpapiPluginMsg_VideoDecoder_NotifyError(pp_error));
}
const uint8_t* PepperVideoDecoderHost::DecodeIdToAddress(uint32_t decode_id) {
PendingDecodeList::const_iterator it = GetPendingDecodeById(decode_id);
DCHECK(it != pending_decodes_.end());
uint32_t shm_id = it->shm_id;
return static_cast<uint8_t*>(shm_buffers_[shm_id]->memory());
}
bool PepperVideoDecoderHost::TryFallbackToSoftwareDecoder() {
#if defined(OS_ANDROID)
return false;
#else
DCHECK(!software_fallback_used_ && software_fallback_allowed_);
uint32_t shim_texture_pool_size = media::limits::kMaxVideoFrames + 1;
shim_texture_pool_size = std::max(shim_texture_pool_size,
min_picture_count_);
std::unique_ptr<VideoDecoderShim> new_decoder(
new VideoDecoderShim(this, shim_texture_pool_size));
if (!new_decoder->Initialize(media::VideoDecodeAccelerator::Config(profile_),
this)) {
return false;
}
software_fallback_used_ = true;
decoder_.reset(new_decoder.release());
// Dismiss all assigned pictures and mark all pictures in use as DISMISSED.
PictureBufferMap pictures_pending_dismission;
for (auto& picture : picture_buffer_map_) {
if (picture.second == PictureBufferState::ASSIGNED) {
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoDecoder_DismissPicture(picture.first));
} else {
pictures_pending_dismission.insert(
std::make_pair(picture.first, PictureBufferState::DISMISSED));
}
}
picture_buffer_map_.swap(pictures_pending_dismission);
// Dismiss all outstanding texture requests.
DCHECK_EQ(assign_textures_messages_to_dismiss_, 0);
assign_textures_messages_to_dismiss_ = pending_texture_requests_;
// If there was a pending Reset() it can be finished now.
if (reset_reply_context_.is_valid()) {
while (!pending_decodes_.empty()) {
const PendingDecode& decode = pending_decodes_.front();
host()->SendReply(decode.reply_context,
PpapiPluginMsg_VideoDecoder_DecodeReply(decode.shm_id));
DCHECK(shm_buffer_busy_[decode.shm_id]);
shm_buffer_busy_[decode.shm_id] = false;
pending_decodes_.pop_front();
}
NotifyResetDone();
}
// Resubmit all pending decodes.
for (const PendingDecode& decode : pending_decodes_) {
DCHECK(shm_buffer_busy_[decode.shm_id]);
decoder_->Decode(media::BitstreamBuffer(
decode.decode_id, shm_buffers_[decode.shm_id]->handle(), decode.size));
}
// Flush the new decoder if Flush() was pending.
if (flush_reply_context_.is_valid())
decoder_->Flush();
return true;
#endif
}
PepperVideoDecoderHost::PendingDecodeList::iterator
PepperVideoDecoderHost::GetPendingDecodeById(int32_t decode_id) {
return std::find_if(pending_decodes_.begin(), pending_decodes_.end(),
[decode_id](const PendingDecode& item) {
return item.decode_id == decode_id;
});
}
} // namespace content