blob: 6f053bdc5fe32607d43eb69eca112edfbd5cc1f0 [file] [log] [blame]
// Copyright 2015 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_encoder_host.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/numerics/safe_math.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/common/pepper_file_util.h"
#include "content/public/common/gpu_stream_constants.h"
#include "content/public/renderer/renderer_ppapi_host.h"
#include "content/renderer/pepper/gfx_conversion.h"
#include "content/renderer/pepper/host_globals.h"
#include "content/renderer/pepper/video_encoder_shim.h"
#include "content/renderer/render_thread_impl.h"
#include "gpu/command_buffer/common/context_creation_attribs.h"
#include "gpu/ipc/client/command_buffer_proxy_impl.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "ppapi/c/pp_codecs.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/pp_graphics_3d.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/media_stream_buffer.h"
using ppapi::proxy::SerializedHandle;
namespace content {
namespace {
const uint32_t kDefaultNumberOfBitstreamBuffers = 4;
int32_t PP_FromMediaEncodeAcceleratorError(
media::VideoEncodeAccelerator::Error error) {
switch (error) {
case media::VideoEncodeAccelerator::kInvalidArgumentError:
return PP_ERROR_MALFORMED_INPUT;
case media::VideoEncodeAccelerator::kIllegalStateError:
case media::VideoEncodeAccelerator::kPlatformFailureError:
return PP_ERROR_RESOURCE_FAILED;
// No default case, to catch unhandled enum values.
}
return PP_ERROR_FAILED;
}
// TODO(llandwerlin): move following to media_conversion.cc/h?
media::VideoCodecProfile PP_ToMediaVideoProfile(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;
}
PP_VideoProfile PP_FromMediaVideoProfile(media::VideoCodecProfile profile) {
switch (profile) {
case media::H264PROFILE_BASELINE:
return PP_VIDEOPROFILE_H264BASELINE;
case media::H264PROFILE_MAIN:
return PP_VIDEOPROFILE_H264MAIN;
case media::H264PROFILE_EXTENDED:
return PP_VIDEOPROFILE_H264EXTENDED;
case media::H264PROFILE_HIGH:
return PP_VIDEOPROFILE_H264HIGH;
case media::H264PROFILE_HIGH10PROFILE:
return PP_VIDEOPROFILE_H264HIGH10PROFILE;
case media::H264PROFILE_HIGH422PROFILE:
return PP_VIDEOPROFILE_H264HIGH422PROFILE;
case media::H264PROFILE_HIGH444PREDICTIVEPROFILE:
return PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE;
case media::H264PROFILE_SCALABLEBASELINE:
return PP_VIDEOPROFILE_H264SCALABLEBASELINE;
case media::H264PROFILE_SCALABLEHIGH:
return PP_VIDEOPROFILE_H264SCALABLEHIGH;
case media::H264PROFILE_STEREOHIGH:
return PP_VIDEOPROFILE_H264STEREOHIGH;
case media::H264PROFILE_MULTIVIEWHIGH:
return PP_VIDEOPROFILE_H264MULTIVIEWHIGH;
case media::VP8PROFILE_ANY:
return PP_VIDEOPROFILE_VP8_ANY;
case media::VP9PROFILE_PROFILE0:
return PP_VIDEOPROFILE_VP9_ANY;
default:
NOTREACHED();
return static_cast<PP_VideoProfile>(-1);
}
}
media::VideoPixelFormat PP_ToMediaVideoFormat(PP_VideoFrame_Format format) {
switch (format) {
case PP_VIDEOFRAME_FORMAT_UNKNOWN:
return media::PIXEL_FORMAT_UNKNOWN;
case PP_VIDEOFRAME_FORMAT_YV12:
return media::PIXEL_FORMAT_YV12;
case PP_VIDEOFRAME_FORMAT_I420:
return media::PIXEL_FORMAT_I420;
case PP_VIDEOFRAME_FORMAT_BGRA:
return media::PIXEL_FORMAT_UNKNOWN;
// No default case, to catch unhandled PP_VideoFrame_Format values.
}
return media::PIXEL_FORMAT_UNKNOWN;
}
PP_VideoFrame_Format PP_FromMediaVideoFormat(media::VideoPixelFormat format) {
switch (format) {
case media::PIXEL_FORMAT_UNKNOWN:
return PP_VIDEOFRAME_FORMAT_UNKNOWN;
case media::PIXEL_FORMAT_YV12:
return PP_VIDEOFRAME_FORMAT_YV12;
case media::PIXEL_FORMAT_I420:
return PP_VIDEOFRAME_FORMAT_I420;
default:
return PP_VIDEOFRAME_FORMAT_UNKNOWN;
}
}
PP_VideoProfileDescription PP_FromVideoEncodeAcceleratorSupportedProfile(
media::VideoEncodeAccelerator::SupportedProfile profile) {
PP_VideoProfileDescription pp_profile;
pp_profile.profile = PP_FromMediaVideoProfile(profile.profile);
pp_profile.max_resolution = PP_FromGfxSize(profile.max_resolution);
pp_profile.max_framerate_numerator = profile.max_framerate_numerator;
pp_profile.max_framerate_denominator = profile.max_framerate_denominator;
pp_profile.hardware_accelerated = PP_FALSE;
return pp_profile;
}
} // namespace
PepperVideoEncoderHost::ShmBuffer::ShmBuffer(
uint32_t id,
base::UnsafeSharedMemoryRegion shm_region)
: id(id), region(std::move(shm_region)), in_use(true) {
DCHECK(region.IsValid());
mapping = region.Map();
DCHECK(mapping.IsValid());
}
PepperVideoEncoderHost::ShmBuffer::~ShmBuffer() {}
media::BitstreamBuffer PepperVideoEncoderHost::ShmBuffer::ToBitstreamBuffer() {
DCHECK(region.IsValid());
DCHECK(mapping.IsValid());
return media::BitstreamBuffer(id, region.Duplicate(), mapping.size());
}
PepperVideoEncoderHost::PepperVideoEncoderHost(RendererPpapiHost* host,
PP_Instance instance,
PP_Resource resource)
: ResourceHost(host->GetPpapiHost(), instance, resource),
renderer_ppapi_host_(host),
buffer_manager_(this),
encoder_(new VideoEncoderShim(this)),
initialized_(false),
encoder_last_error_(PP_ERROR_FAILED),
frame_count_(0),
media_input_format_(media::PIXEL_FORMAT_UNKNOWN) {}
PepperVideoEncoderHost::~PepperVideoEncoderHost() {
Close();
}
int32_t PepperVideoEncoderHost::OnResourceMessageReceived(
const IPC::Message& msg,
ppapi::host::HostMessageContext* context) {
PPAPI_BEGIN_MESSAGE_MAP(PepperVideoEncoderHost, msg)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
PpapiHostMsg_VideoEncoder_GetSupportedProfiles,
OnHostMsgGetSupportedProfiles)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoEncoder_Initialize,
OnHostMsgInitialize)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
PpapiHostMsg_VideoEncoder_GetVideoFrames,
OnHostMsgGetVideoFrames)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoEncoder_Encode,
OnHostMsgEncode)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(
PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer,
OnHostMsgRecycleBitstreamBuffer)
PPAPI_DISPATCH_HOST_RESOURCE_CALL(
PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange,
OnHostMsgRequestEncodingParametersChange)
PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoEncoder_Close,
OnHostMsgClose)
PPAPI_END_MESSAGE_MAP()
return PP_ERROR_FAILED;
}
void PepperVideoEncoderHost::OnGpuControlLostContext() {
#if DCHECK_IS_ON()
// This should never occur more than once.
DCHECK(!lost_context_);
lost_context_ = true;
#endif
NotifyPepperError(PP_ERROR_RESOURCE_FAILED);
}
void PepperVideoEncoderHost::OnGpuControlLostContextMaybeReentrant() {
// No internal state to update on lost context.
}
void PepperVideoEncoderHost::OnGpuControlReturnData(
base::span<const uint8_t> data) {
NOTIMPLEMENTED();
}
int32_t PepperVideoEncoderHost::OnHostMsgGetSupportedProfiles(
ppapi::host::HostMessageContext* context) {
std::vector<PP_VideoProfileDescription> pp_profiles;
GetSupportedProfiles(&pp_profiles);
host()->SendReply(
context->MakeReplyMessageContext(),
PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply(pp_profiles));
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoEncoderHost::OnHostMsgInitialize(
ppapi::host::HostMessageContext* context,
PP_VideoFrame_Format input_format,
const PP_Size& input_visible_size,
PP_VideoProfile output_profile,
uint32_t initial_bitrate,
PP_HardwareAcceleration acceleration) {
if (initialized_)
return PP_ERROR_FAILED;
media_input_format_ = PP_ToMediaVideoFormat(input_format);
if (media_input_format_ == media::PIXEL_FORMAT_UNKNOWN)
return PP_ERROR_BADARGUMENT;
media::VideoCodecProfile media_profile =
PP_ToMediaVideoProfile(output_profile);
if (media_profile == media::VIDEO_CODEC_PROFILE_UNKNOWN)
return PP_ERROR_BADARGUMENT;
gfx::Size input_size(input_visible_size.width, input_visible_size.height);
if (input_size.IsEmpty())
return PP_ERROR_BADARGUMENT;
if (acceleration == PP_HARDWAREACCELERATION_ONLY)
return PP_ERROR_NOTSUPPORTED;
initialize_reply_context_ = context->MakeReplyMessageContext();
const media::VideoEncodeAccelerator::Config config(
media_input_format_, input_size, media_profile, initial_bitrate);
if (encoder_->Initialize(config, this))
return PP_OK_COMPLETIONPENDING;
initialize_reply_context_ = ppapi::host::ReplyMessageContext();
Close();
return PP_ERROR_FAILED;
}
int32_t PepperVideoEncoderHost::OnHostMsgGetVideoFrames(
ppapi::host::HostMessageContext* context) {
if (encoder_last_error_)
return encoder_last_error_;
get_video_frames_reply_context_ = context->MakeReplyMessageContext();
AllocateVideoFrames();
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoEncoderHost::OnHostMsgEncode(
ppapi::host::HostMessageContext* context,
uint32_t frame_id,
bool force_keyframe) {
if (encoder_last_error_)
return encoder_last_error_;
if (frame_id >= frame_count_)
return PP_ERROR_FAILED;
encoder_->Encode(
CreateVideoFrame(frame_id, context->MakeReplyMessageContext()),
force_keyframe);
return PP_OK_COMPLETIONPENDING;
}
int32_t PepperVideoEncoderHost::OnHostMsgRecycleBitstreamBuffer(
ppapi::host::HostMessageContext* context,
uint32_t buffer_id) {
if (encoder_last_error_)
return encoder_last_error_;
if (buffer_id >= shm_buffers_.size() || shm_buffers_[buffer_id]->in_use)
return PP_ERROR_FAILED;
shm_buffers_[buffer_id]->in_use = true;
encoder_->UseOutputBitstreamBuffer(
shm_buffers_[buffer_id]->ToBitstreamBuffer());
return PP_OK;
}
int32_t PepperVideoEncoderHost::OnHostMsgRequestEncodingParametersChange(
ppapi::host::HostMessageContext* context,
uint32_t bitrate,
uint32_t framerate) {
if (encoder_last_error_)
return encoder_last_error_;
encoder_->RequestEncodingParametersChange(bitrate, framerate);
return PP_OK;
}
int32_t PepperVideoEncoderHost::OnHostMsgClose(
ppapi::host::HostMessageContext* context) {
encoder_last_error_ = PP_ERROR_FAILED;
Close();
return PP_OK;
}
void PepperVideoEncoderHost::RequireBitstreamBuffers(
unsigned int frame_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) {
DCHECK(RenderThreadImpl::current());
// We assume RequireBitstreamBuffers is only called once.
DCHECK(!initialized_);
input_coded_size_ = input_coded_size;
frame_count_ = frame_count;
for (uint32_t i = 0; i < kDefaultNumberOfBitstreamBuffers; ++i) {
base::UnsafeSharedMemoryRegion region =
mojo::CreateUnsafeSharedMemoryRegion(output_buffer_size);
if (!region.IsValid()) {
shm_buffers_.clear();
break;
}
shm_buffers_.push_back(std::make_unique<ShmBuffer>(i, std::move(region)));
}
// Feed buffers to the encoder.
std::vector<SerializedHandle> handles;
for (const auto& buffer : shm_buffers_) {
encoder_->UseOutputBitstreamBuffer(buffer->ToBitstreamBuffer());
handles.push_back(SerializedHandle(
renderer_ppapi_host_->ShareUnsafeSharedMemoryRegionWithRemote(
buffer->region)));
}
host()->SendUnsolicitedReplyWithHandles(
pp_resource(),
PpapiPluginMsg_VideoEncoder_BitstreamBuffers(
static_cast<uint32_t>(output_buffer_size)),
&handles);
if (!initialized_) {
// Tell the plugin that initialization has been successful if we
// haven't already.
initialized_ = true;
encoder_last_error_ = PP_OK;
host()->SendReply(initialize_reply_context_,
PpapiPluginMsg_VideoEncoder_InitializeReply(
frame_count, PP_FromGfxSize(input_coded_size)));
}
if (shm_buffers_.empty()) {
NotifyPepperError(PP_ERROR_NOMEMORY);
return;
}
// If the plugin already requested video frames, we can now answer
// that request.
if (get_video_frames_reply_context_.is_valid())
AllocateVideoFrames();
}
void PepperVideoEncoderHost::BitstreamBufferReady(
int32_t buffer_id,
const media::BitstreamBufferMetadata& metadata) {
DCHECK(RenderThreadImpl::current());
DCHECK(shm_buffers_[buffer_id]->in_use);
shm_buffers_[buffer_id]->in_use = false;
// TODO: Pass timestamp. Tracked in crbug/613984.
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoEncoder_BitstreamBufferReady(
buffer_id, base::checked_cast<uint32_t>(metadata.payload_size_bytes),
metadata.key_frame));
}
void PepperVideoEncoderHost::NotifyError(
media::VideoEncodeAccelerator::Error error) {
DCHECK(RenderThreadImpl::current());
NotifyPepperError(PP_FromMediaEncodeAcceleratorError(error));
}
void PepperVideoEncoderHost::GetSupportedProfiles(
std::vector<PP_VideoProfileDescription>* pp_profiles) {
DCHECK(RenderThreadImpl::current());
DCHECK(encoder_);
const media::VideoEncodeAccelerator::SupportedProfiles media_profiles =
encoder_->GetSupportedProfiles();
for (const auto& media_profile : media_profiles) {
pp_profiles->push_back(
PP_FromVideoEncodeAcceleratorSupportedProfile(media_profile));
}
}
void PepperVideoEncoderHost::Close() {
DCHECK(RenderThreadImpl::current());
encoder_ = nullptr;
command_buffer_ = nullptr;
}
void PepperVideoEncoderHost::AllocateVideoFrames() {
DCHECK(RenderThreadImpl::current());
DCHECK(get_video_frames_reply_context_.is_valid());
// Frames have already been allocated.
if (buffer_manager_.number_of_buffers() > 0) {
SendGetFramesErrorReply(PP_ERROR_FAILED);
NOTREACHED();
return;
}
base::CheckedNumeric<uint32_t> size =
media::VideoFrame::AllocationSize(media_input_format_, input_coded_size_);
uint32_t frame_size = size.ValueOrDie();
size += sizeof(ppapi::MediaStreamBuffer::Video);
uint32_t buffer_size = size.ValueOrDie();
// Make each buffer 4 byte aligned.
size += (4 - buffer_size % 4);
uint32_t buffer_size_aligned = size.ValueOrDie();
size *= frame_count_;
uint32_t total_size = size.ValueOrDie();
base::UnsafeSharedMemoryRegion region =
mojo::CreateUnsafeSharedMemoryRegion(total_size);
if (!region.IsValid() ||
!buffer_manager_.SetBuffers(frame_count_, buffer_size_aligned,
std::move(region), true)) {
SendGetFramesErrorReply(PP_ERROR_NOMEMORY);
return;
}
VLOG(4) << " frame_count=" << frame_count_ << " frame_size=" << frame_size
<< " buffer_size=" << buffer_size_aligned;
for (int32_t i = 0; i < buffer_manager_.number_of_buffers(); ++i) {
ppapi::MediaStreamBuffer::Video* buffer =
&(buffer_manager_.GetBufferPointer(i)->video);
buffer->header.size = buffer_manager_.buffer_size();
buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
buffer->format = PP_FromMediaVideoFormat(media_input_format_);
buffer->size.width = input_coded_size_.width();
buffer->size.height = input_coded_size_.height();
buffer->data_size = frame_size;
}
DCHECK(get_video_frames_reply_context_.is_valid());
get_video_frames_reply_context_.params.AppendHandle(SerializedHandle(
renderer_ppapi_host_->ShareUnsafeSharedMemoryRegionWithRemote(
buffer_manager_.region())));
host()->SendReply(get_video_frames_reply_context_,
PpapiPluginMsg_VideoEncoder_GetVideoFramesReply(
frame_count_, buffer_size_aligned,
PP_FromGfxSize(input_coded_size_)));
get_video_frames_reply_context_ = ppapi::host::ReplyMessageContext();
}
void PepperVideoEncoderHost::SendGetFramesErrorReply(int32_t error) {
get_video_frames_reply_context_.params.set_result(error);
host()->SendReply(
get_video_frames_reply_context_,
PpapiPluginMsg_VideoEncoder_GetVideoFramesReply(0, 0, PP_MakeSize(0, 0)));
get_video_frames_reply_context_ = ppapi::host::ReplyMessageContext();
}
scoped_refptr<media::VideoFrame> PepperVideoEncoderHost::CreateVideoFrame(
uint32_t frame_id,
const ppapi::host::ReplyMessageContext& reply_context) {
DCHECK(RenderThreadImpl::current());
ppapi::MediaStreamBuffer* buffer = buffer_manager_.GetBufferPointer(frame_id);
DCHECK(buffer);
// The shared memory handle does not need to be given to the video frame as
// cross-process calls coordinate shared memory via a buffer index. See
// ppapi/shared_impl/media_stream_buffer_manager.h for details.
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
media_input_format_, input_coded_size_, gfx::Rect(input_coded_size_),
input_coded_size_, static_cast<uint8_t*>(buffer->video.data),
buffer->video.data_size, base::TimeDelta());
if (!frame) {
NotifyPepperError(PP_ERROR_FAILED);
return frame;
}
frame->AddDestructionObserver(
base::BindOnce(&PepperVideoEncoderHost::FrameReleased,
weak_ptr_factory_.GetWeakPtr(), reply_context, frame_id));
return frame;
}
void PepperVideoEncoderHost::FrameReleased(
const ppapi::host::ReplyMessageContext& reply_context,
uint32_t frame_id) {
DCHECK(RenderThreadImpl::current());
ppapi::host::ReplyMessageContext context = reply_context;
context.params.set_result(encoder_last_error_);
host()->SendReply(context, PpapiPluginMsg_VideoEncoder_EncodeReply(frame_id));
}
void PepperVideoEncoderHost::NotifyPepperError(int32_t error) {
DCHECK(RenderThreadImpl::current());
encoder_last_error_ = error;
Close();
host()->SendUnsolicitedReply(
pp_resource(),
PpapiPluginMsg_VideoEncoder_NotifyError(encoder_last_error_));
}
uint8_t* PepperVideoEncoderHost::ShmHandleToAddress(int32_t buffer_id) {
DCHECK(RenderThreadImpl::current());
DCHECK_GE(buffer_id, 0);
DCHECK_LT(buffer_id, static_cast<int32_t>(shm_buffers_.size()));
return shm_buffers_[buffer_id]->mapping.GetMemoryAsSpan<uint8_t>().data();
}
} // namespace content