// 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 #include "base/bind.h" #include "base/memory/ptr_util.h" #include "base/memory/shared_memory.h" #include "base/numerics/safe_math.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "content/common/gpu_stream_constants.h" #include "content/common/pepper_file_util.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 "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(-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, std::unique_ptr shm) : id(id), shm(std::move(shm)), in_use(true) { DCHECK(this->shm); } PepperVideoEncoderHost::ShmBuffer::~ShmBuffer() {} media::BitstreamBuffer PepperVideoEncoderHost::ShmBuffer::ToBitstreamBuffer() { return media::BitstreamBuffer(id, shm->handle(), shm->mapped_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), weak_ptr_factory_(this) {} 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. } int32_t PepperVideoEncoderHost::OnHostMsgGetSupportedProfiles( ppapi::host::HostMessageContext* context) { std::vector 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(); if (encoder_->Initialize(media_input_format_, input_size, media_profile, initial_bitrate, 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) { std::unique_ptr shm( RenderThread::Get()->HostAllocateSharedMemoryBuffer( output_buffer_size)); if (!shm || !shm->Map(output_buffer_size)) { shm_buffers_.clear(); break; } shm_buffers_.push_back(std::make_unique(i, std::move(shm))); } // Feed buffers to the encoder. std::vector handles; for (const auto& buffer : shm_buffers_) { encoder_->UseOutputBitstreamBuffer(buffer->ToBitstreamBuffer()); handles.push_back(SerializedHandle( renderer_ppapi_host_->ShareSharedMemoryHandleWithRemote( buffer->shm->handle()), output_buffer_size)); } host()->SendUnsolicitedReplyWithHandles( pp_resource(), PpapiPluginMsg_VideoEncoder_BitstreamBuffers( static_cast(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, size_t payload_size, bool key_frame, base::TimeDelta /* timestamp */) { 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, static_cast(payload_size), key_frame)); } void PepperVideoEncoderHost::NotifyError( media::VideoEncodeAccelerator::Error error) { DCHECK(RenderThreadImpl::current()); NotifyPepperError(PP_FromMediaEncodeAcceleratorError(error)); } void PepperVideoEncoderHost::GetSupportedProfiles( std::vector* 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 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(); std::unique_ptr shm( RenderThreadImpl::current()->HostAllocateSharedMemoryBuffer(total_size)); if (!shm || !buffer_manager_.SetBuffers(frame_count_, buffer_size_aligned, std::move(shm), 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_->ShareSharedMemoryHandleWithRemote( buffer_manager_.shm()->handle()), total_size)); 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 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); uint32_t shm_offset = static_cast(buffer->video.data) - static_cast(buffer_manager_.shm()->memory()); scoped_refptr frame = media::VideoFrame::WrapExternalSharedMemory( media_input_format_, input_coded_size_, gfx::Rect(input_coded_size_), input_coded_size_, static_cast(buffer->video.data), buffer->video.data_size, buffer_manager_.shm()->handle(), shm_offset, 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(shm_buffers_.size())); return static_cast(shm_buffers_[buffer_id]->shm->memory()); } } // namespace content