diff options
-rw-r--r-- | chrome/test/ppapi/ppapi_browsertest.cc | 2 | ||||
-rw-r--r-- | content/common/gpu/gpu_process_launch_causes.h | 1 | ||||
-rw-r--r-- | content/content_renderer.gypi | 2 | ||||
-rw-r--r-- | content/renderer/pepper/content_renderer_pepper_host_factory.cc | 4 | ||||
-rw-r--r-- | content/renderer/pepper/pepper_video_encoder_host.cc | 652 | ||||
-rw-r--r-- | content/renderer/pepper/pepper_video_encoder_host.h | 158 | ||||
-rw-r--r-- | content/test/ppapi/ppapi_browsertest.cc | 2 | ||||
-rw-r--r-- | ppapi/ppapi_sources.gypi | 2 | ||||
-rw-r--r-- | ppapi/ppapi_tests.gypi | 1 | ||||
-rw-r--r-- | ppapi/proxy/ppapi_messages.h | 49 | ||||
-rw-r--r-- | ppapi/proxy/video_encoder_resource.cc | 378 | ||||
-rw-r--r-- | ppapi/proxy/video_encoder_resource.h | 101 | ||||
-rw-r--r-- | ppapi/proxy/video_encoder_resource_unittest.cc | 1085 | ||||
-rw-r--r-- | ppapi/tests/test_video_encoder.cc | 41 | ||||
-rw-r--r-- | ppapi/tests/test_video_encoder.h | 29 |
15 files changed, 2491 insertions, 16 deletions
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc index 8aa2522..37f6a8c 100644 --- a/chrome/test/ppapi/ppapi_browsertest.cc +++ b/chrome/test/ppapi/ppapi_browsertest.cc @@ -1239,6 +1239,8 @@ TEST_PPAPI_NACL(TrueTypeFont) TEST_PPAPI_NACL(VideoDecoder) +TEST_PPAPI_NACL(VideoEncoder) + // VideoDestination doesn't work in content_browsertests. TEST_PPAPI_OUT_OF_PROCESS(VideoDestination) TEST_PPAPI_NACL(VideoDestination) diff --git a/content/common/gpu/gpu_process_launch_causes.h b/content/common/gpu/gpu_process_launch_causes.h index c6416ae..b4dfc0a 100644 --- a/content/common/gpu/gpu_process_launch_causes.h +++ b/content/common/gpu/gpu_process_launch_causes.h @@ -19,6 +19,7 @@ enum CauseForGpuLaunch { CAUSE_FOR_GPU_LAUNCH_PEPPERPLATFORMCONTEXT3DIMPL_INITIALIZE, CAUSE_FOR_GPU_LAUNCH_BROWSER_STARTUP, CAUSE_FOR_GPU_LAUNCH_CANVAS_2D, + CAUSE_FOR_GPU_LAUNCH_PEPPERVIDEOENCODERACCELERATOR_INITIALIZE, // All new values should be inserted above this point so that // existing values continue to match up with those in histograms.xml. diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi index d3e4979..9b1875b 100644 --- a/content/content_renderer.gypi +++ b/content/content_renderer.gypi @@ -520,6 +520,8 @@ 'renderer/pepper/pepper_video_capture_host.h', 'renderer/pepper/pepper_video_decoder_host.cc', 'renderer/pepper/pepper_video_decoder_host.h', + 'renderer/pepper/pepper_video_encoder_host.cc', + 'renderer/pepper/pepper_video_encoder_host.h', 'renderer/pepper/pepper_webplugin_impl.cc', 'renderer/pepper/pepper_webplugin_impl.h', 'renderer/pepper/pepper_websocket_host.cc', diff --git a/content/renderer/pepper/content_renderer_pepper_host_factory.cc b/content/renderer/pepper/content_renderer_pepper_host_factory.cc index 143fb18..2df65f7 100644 --- a/content/renderer/pepper/content_renderer_pepper_host_factory.cc +++ b/content/renderer/pepper/content_renderer_pepper_host_factory.cc @@ -22,6 +22,7 @@ #include "content/renderer/pepper/pepper_video_capture_host.h" #include "content/renderer/pepper/pepper_video_decoder_host.h" #include "content/renderer/pepper/pepper_video_destination_host.h" +#include "content/renderer/pepper/pepper_video_encoder_host.h" #include "content/renderer/pepper/pepper_video_source_host.h" #include "content/renderer/pepper/pepper_websocket_host.h" #include "content/renderer/pepper/ppb_image_data_impl.h" @@ -159,6 +160,9 @@ scoped_ptr<ResourceHost> ContentRendererPepperHostFactory::CreateResourceHost( case PpapiHostMsg_VideoDecoder_Create::ID: return scoped_ptr<ResourceHost>( new PepperVideoDecoderHost(host_, instance, resource)); + case PpapiHostMsg_VideoEncoder_Create::ID: + return scoped_ptr<ResourceHost>( + new PepperVideoEncoderHost(host_, instance, resource)); case PpapiHostMsg_WebSocket_Create::ID: return scoped_ptr<ResourceHost>( new PepperWebSocketHost(host_, instance, resource)); diff --git a/content/renderer/pepper/pepper_video_encoder_host.cc b/content/renderer/pepper/pepper_video_encoder_host.cc new file mode 100644 index 0000000..bacbfe0 --- /dev/null +++ b/content/renderer/pepper/pepper_video_encoder_host.cc @@ -0,0 +1,652 @@ +// 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 "base/bind.h" +#include "base/memory/shared_memory.h" +#include "base/numerics/safe_math.h" +#include "content/common/gpu/client/command_buffer_proxy_impl.h" +#include "content/common/gpu/client/gpu_video_encode_accelerator_host.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/pepper_video_encoder_host.h" +#include "content/renderer/render_thread_impl.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/video_frame.h" +#include "media/filters/gpu_video_accelerator_factories.h" +#include "media/video/video_encode_accelerator.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; + +base::PlatformFile ConvertSharedMemoryHandle( + const base::SharedMemory& shared_memory) { +#if defined(OS_POSIX) + return shared_memory.handle().fd; +#elif defined(OS_WIN) + return shared_memory.handle(); +#else +#error "Platform not supported." +#endif +} + +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_ANY; + // 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_ANY: + return PP_VIDEOPROFILE_VP9_ANY; + default: + NOTREACHED(); + return static_cast<PP_VideoProfile>(-1); + } +} + +media::VideoFrame::Format PP_ToMediaVideoFormat(PP_VideoFrame_Format format) { + switch (format) { + case PP_VIDEOFRAME_FORMAT_UNKNOWN: + return media::VideoFrame::UNKNOWN; + case PP_VIDEOFRAME_FORMAT_YV12: + return media::VideoFrame::YV12; + case PP_VIDEOFRAME_FORMAT_I420: + return media::VideoFrame::I420; + case PP_VIDEOFRAME_FORMAT_BGRA: + return media::VideoFrame::UNKNOWN; + // No default case, to catch unhandled PP_VideoFrame_Format values. + } + return media::VideoFrame::UNKNOWN; +} + +PP_VideoFrame_Format PP_FromMediaVideoFormat(media::VideoFrame::Format format) { + switch (format) { + case media::VideoFrame::UNKNOWN: + return PP_VIDEOFRAME_FORMAT_UNKNOWN; + case media::VideoFrame::YV12: + return PP_VIDEOFRAME_FORMAT_YV12; + case media::VideoFrame::I420: + return PP_VIDEOFRAME_FORMAT_I420; + default: + return PP_VIDEOFRAME_FORMAT_UNKNOWN; + } +} + +PP_VideoProfileDescription PP_FromVideoEncodeAcceleratorSupportedProfile( + media::VideoEncodeAccelerator::SupportedProfile profile, + PP_HardwareAcceleration acceleration) { + 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.acceleration = acceleration; + return pp_profile; +} + +bool PP_HardwareAccelerationCompatible(PP_HardwareAcceleration supply, + PP_HardwareAcceleration demand) { + switch (supply) { + case PP_HARDWAREACCELERATION_ONLY: + return (demand == PP_HARDWAREACCELERATION_ONLY || + demand == PP_HARDWAREACCELERATION_WITHFALLBACK); + case PP_HARDWAREACCELERATION_WITHFALLBACK: + return true; + case PP_HARDWAREACCELERATION_NONE: + return (demand == PP_HARDWAREACCELERATION_WITHFALLBACK || + demand == PP_HARDWAREACCELERATION_NONE); + // No default case, to catch unhandled PP_HardwareAcceleration values. + } + return false; +} + +} // namespace + +PepperVideoEncoderHost::ShmBuffer::ShmBuffer(uint32_t id, + scoped_ptr<base::SharedMemory> shm) + : id(id), shm(shm.Pass()), 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), + command_buffer_(nullptr), + initialized_(false), + encoder_last_error_(PP_ERROR_FAILED), + frame_count_(0), + media_input_format_(media::VideoFrame::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; +} + +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::VideoFrame::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 (!IsInitializationValid(input_visible_size, output_profile, acceleration)) + return PP_ERROR_NOTSUPPORTED; + + int32_t error = PP_ERROR_NOTSUPPORTED; + initialize_reply_context_ = context->MakeReplyMessageContext(); + + if (acceleration == PP_HARDWAREACCELERATION_ONLY || + acceleration == PP_HARDWAREACCELERATION_WITHFALLBACK) { + if (InitializeHardware(media_input_format_, input_size, media_profile, + initial_bitrate)) + return PP_OK_COMPLETIONPENDING; + + if (acceleration == PP_HARDWAREACCELERATION_ONLY) + error = PP_ERROR_FAILED; + } + + // TODO(llandwerlin): Software encoder. + initialize_reply_context_ = ppapi::host::ReplyMessageContext(); + Close(); + return error; +} + +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) { + scoped_ptr<base::SharedMemory> shm( + RenderThread::Get() + ->HostAllocateSharedMemoryBuffer(output_buffer_size) + .Pass()); + + if (!shm || !shm->Map(output_buffer_size)) { + shm_buffers_.clear(); + break; + } + + shm_buffers_.push_back(new ShmBuffer(i, shm.Pass())); + } + + // Feed buffers to the encoder. + std::vector<SerializedHandle> handles; + for (size_t i = 0; i < shm_buffers_.size(); ++i) { + encoder_->UseOutputBitstreamBuffer(shm_buffers_[i]->ToBitstreamBuffer()); + handles.push_back(SerializedHandle( + renderer_ppapi_host_->ShareHandleWithRemote( + ConvertSharedMemoryHandle(*shm_buffers_[i]->shm), false), + output_buffer_size)); + } + + 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 buffer_id, + size_t payload_size, + bool key_frame) { + DCHECK(RenderThreadImpl::current()); + DCHECK(shm_buffers_[buffer_id]->in_use); + + shm_buffers_[buffer_id]->in_use = false; + host()->SendUnsolicitedReply( + pp_resource(), + PpapiPluginMsg_VideoEncoder_BitstreamBufferReady( + buffer_id, static_cast<uint32_t>(payload_size), 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()); + + if (!EnsureGpuChannel()) + return; + + std::vector<media::VideoEncodeAccelerator::SupportedProfile> profiles = + GpuVideoEncodeAcceleratorHost::ConvertGpuToMediaProfiles( + channel_->gpu_info().video_encode_accelerator_supported_profiles); + for (media::VideoEncodeAccelerator::SupportedProfile profile : profiles) + pp_profiles->push_back(PP_FromVideoEncodeAcceleratorSupportedProfile( + profile, PP_HARDWAREACCELERATION_ONLY)); + + // TODO(llandwerlin): add software supported profiles. +} + +bool PepperVideoEncoderHost::IsInitializationValid( + const PP_Size& input_size, + PP_VideoProfile output_profile, + PP_HardwareAcceleration acceleration) { + DCHECK(RenderThreadImpl::current()); + + std::vector<PP_VideoProfileDescription> profiles; + GetSupportedProfiles(&profiles); + + for (const PP_VideoProfileDescription& profile : profiles) { + if (output_profile == profile.profile && + input_size.width <= profile.max_resolution.width && + input_size.height <= profile.max_resolution.height && + PP_HardwareAccelerationCompatible(profile.acceleration, acceleration)) + return true; + } + + return false; +} + +bool PepperVideoEncoderHost::EnsureGpuChannel() { + DCHECK(RenderThreadImpl::current()); + + if (command_buffer_) + return true; + + // There is no guarantee that we have a 3D context to work with. So + // we create a dummy command buffer to communicate with the gpu process. + channel_ = RenderThreadImpl::current()->EstablishGpuChannelSync( + CAUSE_FOR_GPU_LAUNCH_PEPPERVIDEOENCODERACCELERATOR_INITIALIZE); + if (!channel_) + return false; + + std::vector<int32> attribs(1, PP_GRAPHICS3DATTRIB_NONE); + command_buffer_ = channel_->CreateOffscreenCommandBuffer( + gfx::Size(), nullptr, attribs, GURL::EmptyGURL(), + gfx::PreferIntegratedGpu); + if (!command_buffer_) { + Close(); + return false; + } + + command_buffer_->SetChannelErrorCallback(media::BindToCurrentLoop( + base::Bind(&PepperVideoEncoderHost::NotifyPepperError, + weak_ptr_factory_.GetWeakPtr(), PP_ERROR_RESOURCE_FAILED))); + if (!command_buffer_->Initialize()) { + Close(); + return false; + } + + return true; +} + +bool PepperVideoEncoderHost::InitializeHardware( + media::VideoFrame::Format input_format, + const gfx::Size& input_visible_size, + media::VideoCodecProfile output_profile, + uint32_t initial_bitrate) { + DCHECK(RenderThreadImpl::current()); + + if (!EnsureGpuChannel()) + return false; + + encoder_ = command_buffer_->CreateVideoEncoder(); + if (!encoder_ || + !encoder_->Initialize(input_format, input_visible_size, output_profile, + initial_bitrate, this)) + return false; + + return true; +} + +void PepperVideoEncoderHost::Close() { + DCHECK(RenderThreadImpl::current()); + + encoder_ = nullptr; + if (command_buffer_) { + DCHECK(channel_); + channel_->DestroyCommandBuffer(command_buffer_); + 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(); + + scoped_ptr<base::SharedMemory> shm( + RenderThreadImpl::current() + ->HostAllocateSharedMemoryBuffer(total_size) + .Pass()); + if (!shm || + !buffer_manager_.SetBuffers(frame_count_, + buffer_size_aligned, + shm.Pass(), + 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_->ShareHandleWithRemote( + ConvertSharedMemoryHandle(*buffer_manager_.shm()), false), + 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<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); + uint32_t shm_offset = static_cast<uint8*>(buffer->video.data) - + static_cast<uint8*>(buffer_manager_.shm()->memory()); + + return media::VideoFrame::WrapExternalPackedMemory( + media_input_format_, input_coded_size_, gfx::Rect(input_coded_size_), + input_coded_size_, static_cast<uint8*>(buffer->video.data), + buffer->video.data_size, buffer_manager_.shm()->handle(), shm_offset, + base::TimeDelta(), + base::Bind(&PepperVideoEncoderHost::FrameReleased, + weak_ptr_factory_.GetWeakPtr(), reply_context, frame_id)); +} + +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_)); +} + +} // namespace content diff --git a/content/renderer/pepper/pepper_video_encoder_host.h b/content/renderer/pepper/pepper_video_encoder_host.h new file mode 100644 index 0000000..18c9a8e --- /dev/null +++ b/content/renderer/pepper/pepper_video_encoder_host.h @@ -0,0 +1,158 @@ +// 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. + +#ifndef CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_ENCODER_HOST_H_ +#define CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_ENCODER_HOST_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "content/common/content_export.h" +#include "media/video/video_encode_accelerator.h" +#include "ppapi/c/pp_codecs.h" +#include "ppapi/c/ppb_video_frame.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/resource_host.h" +#include "ppapi/proxy/resource_message_params.h" +#include "ppapi/shared_impl/media_stream_buffer_manager.h" + +namespace media { +class GpuVideoAcceleratorFactories; +} + +namespace content { + +class CommandBufferProxyImpl; +class GpuChannelHost; +class RendererPpapiHost; + +class CONTENT_EXPORT PepperVideoEncoderHost + : public ppapi::host::ResourceHost, + public media::VideoEncodeAccelerator::Client, + public ppapi::MediaStreamBufferManager::Delegate { + public: + PepperVideoEncoderHost(RendererPpapiHost* host, + PP_Instance instance, + PP_Resource resource); + ~PepperVideoEncoderHost() override; + + private: + // Shared memory buffers. + struct ShmBuffer { + ShmBuffer(uint32_t id, scoped_ptr<base::SharedMemory> shm); + ~ShmBuffer(); + + media::BitstreamBuffer ToBitstreamBuffer(); + + // Index of the buffer in the ScopedVector. Buffers have the same id in + // the plugin and the host. + uint32_t id; + scoped_ptr<base::SharedMemory> shm; + bool in_use; + }; + + // media::VideoEncodeAccelerator implementation. + void RequireBitstreamBuffers(unsigned int input_count, + const gfx::Size& input_coded_size, + size_t output_buffer_size) override; + void BitstreamBufferReady(int32 bitstream_buffer_id, + size_t payload_size, + bool key_frame) override; + void NotifyError(media::VideoEncodeAccelerator::Error error) override; + + // ResourceHost implementation. + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + int32_t OnHostMsgGetSupportedProfiles( + ppapi::host::HostMessageContext* context); + int32_t 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); + int32_t OnHostMsgGetVideoFrames(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgEncode(ppapi::host::HostMessageContext* context, + uint32_t frame_id, + bool force_keyframe); + int32_t OnHostMsgRecycleBitstreamBuffer( + ppapi::host::HostMessageContext* context, + uint32_t buffer_id); + int32_t OnHostMsgRequestEncodingParametersChange( + ppapi::host::HostMessageContext* context, + uint32_t bitrate, + uint32_t framerate); + int32_t OnHostMsgClose(ppapi::host::HostMessageContext* context); + + // Internal methods. + void GetSupportedProfiles( + std::vector<PP_VideoProfileDescription>* pp_profiles); + bool IsInitializationValid(const PP_Size& input_size, + PP_VideoProfile ouput_profile, + PP_HardwareAcceleration acceleration); + bool EnsureGpuChannel(); + bool InitializeHardware(media::VideoFrame::Format input_format, + const gfx::Size& input_visible_size, + media::VideoCodecProfile output_profile, + uint32_t initial_bitrate); + void Close(); + void AllocateVideoFrames(); + void SendGetFramesErrorReply(int32_t error); + scoped_refptr<media::VideoFrame> CreateVideoFrame( + uint32_t frame_id, + const ppapi::host::ReplyMessageContext& reply_context); + void FrameReleased(const ppapi::host::ReplyMessageContext& reply_context, + uint32_t frame_id); + void NotifyPepperError(int32_t error); + + // Non-owning pointer. + RendererPpapiHost* renderer_ppapi_host_; + + ScopedVector<ShmBuffer> shm_buffers_; + + // Buffer manager for shared memory that holds video frames. + ppapi::MediaStreamBufferManager buffer_manager_; + + scoped_refptr<GpuChannelHost> channel_; + CommandBufferProxyImpl* command_buffer_; + + scoped_ptr<media::VideoEncodeAccelerator> encoder_; + + // Whether the encoder has been successfully initialized. + bool initialized_; + + // Saved context to answer an Initialize message from the plugin. + ppapi::host::ReplyMessageContext initialize_reply_context_; + + // Saved context to answer a GetVideoFrames message from the plugin. + ppapi::host::ReplyMessageContext get_video_frames_reply_context_; + + // This represents the current error state of the encoder, i.e. PP_OK + // normally, or a Pepper error code if the encoder is uninitialized, + // has been notified of an encoder error, has encountered some + // other unrecoverable error, or has been closed by the plugin. + // This field is checked in most message handlers to decide whether + // operations should proceed or fail. + int32_t encoder_last_error_; + + // Size of the frames allocated for the encoder (matching hardware + // constraints). + gfx::Size input_coded_size_; + + // Number of frames the encoder needs. + uint32_t frame_count_; + + // Format of the frames to give to the encoder. + media::VideoFrame::Format media_input_format_; + + base::WeakPtrFactory<PepperVideoEncoderHost> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(PepperVideoEncoderHost); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_ENCODER_HOST_H_ diff --git a/content/test/ppapi/ppapi_browsertest.cc b/content/test/ppapi/ppapi_browsertest.cc index 1a43355..be0764b 100644 --- a/content/test/ppapi/ppapi_browsertest.cc +++ b/content/test/ppapi/ppapi_browsertest.cc @@ -162,5 +162,7 @@ TEST_PPAPI_OUT_OF_PROCESS(VideoDecoder) TEST_PPAPI_IN_PROCESS(VideoDecoderDev) TEST_PPAPI_OUT_OF_PROCESS(VideoDecoderDev) +TEST_PPAPI_OUT_OF_PROCESS(VideoEncoder) + } // namespace } // namespace content diff --git a/ppapi/ppapi_sources.gypi b/ppapi/ppapi_sources.gypi index e182b05..3536453 100644 --- a/ppapi/ppapi_sources.gypi +++ b/ppapi/ppapi_sources.gypi @@ -513,6 +513,8 @@ 'tests/test_video_decoder.h', 'tests/test_video_destination.cc', 'tests/test_video_destination.h', + 'tests/test_video_encoder.cc', + 'tests/test_video_encoder.h', 'tests/test_video_source.cc', 'tests/test_video_source.h', 'tests/test_view.cc', diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi index 0e5de16..562903d 100644 --- a/ppapi/ppapi_tests.gypi +++ b/ppapi/ppapi_tests.gypi @@ -175,6 +175,7 @@ 'proxy/serialized_var_unittest.cc', 'proxy/talk_resource_unittest.cc', 'proxy/video_decoder_resource_unittest.cc', + 'proxy/video_encoder_resource_unittest.cc', 'proxy/websocket_resource_unittest.cc', 'shared_impl/media_stream_audio_track_shared_unittest.cc', 'shared_impl/media_stream_buffer_manager_unittest.cc', diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index 243e783..643d8f9 100644 --- a/ppapi/proxy/ppapi_messages.h +++ b/ppapi/proxy/ppapi_messages.h @@ -41,6 +41,7 @@ #include "ppapi/c/ppb_tcp_socket.h" #include "ppapi/c/ppb_text_input_controller.h" #include "ppapi/c/ppb_udp_socket.h" +#include "ppapi/c/ppb_video_encoder.h" #include "ppapi/c/private/pp_content_decryptor.h" #include "ppapi/c/private/pp_private_font_charset.h" #include "ppapi/c/private/pp_video_capture_format.h" @@ -443,6 +444,14 @@ IPC_STRUCT_TRAITS_BEGIN(ppapi::PpapiNaClPluginArgs) IPC_STRUCT_TRAITS_MEMBER(switch_values) IPC_STRUCT_TRAITS_END() +IPC_STRUCT_TRAITS_BEGIN(PP_VideoProfileDescription) +IPC_STRUCT_TRAITS_MEMBER(profile) +IPC_STRUCT_TRAITS_MEMBER(max_resolution) +IPC_STRUCT_TRAITS_MEMBER(max_framerate_numerator) +IPC_STRUCT_TRAITS_MEMBER(max_framerate_denominator) +IPC_STRUCT_TRAITS_MEMBER(acceleration) +IPC_STRUCT_TRAITS_END() + #if !defined(OS_NACL) && !defined(NACL_WIN64) IPC_STRUCT_TRAITS_BEGIN(ppapi::proxy::PPPDecryptor_Buffer) @@ -1992,6 +2001,46 @@ IPC_MESSAGE_CONTROL0(PpapiPluginMsg_VideoDecoder_ResetReply) IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoDecoder_NotifyError, int32_t /* error */) +// VideoEncoder ------------------------------------------------------ + +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_Create) +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_GetSupportedProfiles) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply, + std::vector<PP_VideoProfileDescription> /* results */) +IPC_MESSAGE_CONTROL5(PpapiHostMsg_VideoEncoder_Initialize, + PP_VideoFrame_Format /* input_format */, + PP_Size /* input_visible_size */, + PP_VideoProfile /* output_profile */, + uint32_t /* initial_bitrate */, + PP_HardwareAcceleration /* acceleration */) +IPC_MESSAGE_CONTROL2(PpapiPluginMsg_VideoEncoder_InitializeReply, + uint32_t /* input_frame_count */, + PP_Size /* input_coded_size */) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_BitstreamBuffers, + uint32_t /* buffer_length */) +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_GetVideoFrames) +IPC_MESSAGE_CONTROL3(PpapiPluginMsg_VideoEncoder_GetVideoFramesReply, + uint32_t /* frame_count */, + uint32_t /* frame_length */, + PP_Size /* frame_size */) +IPC_MESSAGE_CONTROL2(PpapiHostMsg_VideoEncoder_Encode, + uint32_t /* frame_id */, + bool /* force_keyframe */) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_EncodeReply, + uint32_t /* frame_id */) +IPC_MESSAGE_CONTROL3(PpapiPluginMsg_VideoEncoder_BitstreamBufferReady, + uint32_t /* buffer_id */, + uint32_t /* buffer_size */, + bool /* key_frame */) +IPC_MESSAGE_CONTROL1(PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer, + uint32_t /* buffer_id */) +IPC_MESSAGE_CONTROL2(PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange, + uint32_t /* bitrate */, + uint32_t /* framerate */) +IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_NotifyError, + int32_t /* error */) +IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_Close) + #if !defined(OS_NACL) && !defined(NACL_WIN64) // Audio input. diff --git a/ppapi/proxy/video_encoder_resource.cc b/ppapi/proxy/video_encoder_resource.cc index 5d7913d..3c6436b 100644 --- a/ppapi/proxy/video_encoder_resource.cc +++ b/ppapi/proxy/video_encoder_resource.cc @@ -2,19 +2,73 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/shared_memory.h" +#include "base/numerics/safe_conversions.h" +#include "ppapi/c/pp_array_output.h" +#include "ppapi/proxy/ppapi_messages.h" #include "ppapi/proxy/video_encoder_resource.h" +#include "ppapi/proxy/video_frame_resource.h" +#include "ppapi/shared_impl/media_stream_buffer.h" +#include "ppapi/shared_impl/media_stream_buffer_manager.h" +#include "ppapi/thunk/enter.h" +using ppapi::proxy::SerializedHandle; +using ppapi::thunk::EnterResourceNoLock; using ppapi::thunk::PPB_VideoEncoder_API; namespace ppapi { namespace proxy { +namespace { + +void RunCallback(scoped_refptr<TrackedCallback>* callback, int32_t error) { + if (!TrackedCallback::IsPending(*callback)) + return; + + scoped_refptr<TrackedCallback> temp; + callback->swap(temp); + temp->Run(error); +} + +} // namespace + +VideoEncoderResource::ShmBuffer::ShmBuffer(uint32_t id, + scoped_ptr<base::SharedMemory> shm) + : id(id), shm(shm.Pass()) { +} + +VideoEncoderResource::ShmBuffer::~ShmBuffer() { +} + +VideoEncoderResource::BitstreamBuffer::BitstreamBuffer(uint32_t id, + uint32_t size, + bool key_frame) + : id(id), size(size), key_frame(key_frame) { +} + +VideoEncoderResource::BitstreamBuffer::~BitstreamBuffer() { +} + VideoEncoderResource::VideoEncoderResource(Connection connection, PP_Instance instance) - : PluginResource(connection, instance) { + : PluginResource(connection, instance), + initialized_(false), + closed_(false), + // Set |encoder_last_error_| to PP_OK after successful initialization. + // This makes error checking a little more concise, since we can check + // that the encoder has been initialized and hasn't returned an error by + // just testing |encoder_last_error_|. + encoder_last_error_(PP_ERROR_FAILED), + input_frame_count_(0), + input_coded_size_(PP_MakeSize(0, 0)), + buffer_manager_(this), + get_video_frame_data_(nullptr), + get_bitstream_buffer_data_(nullptr) { + SendCreate(RENDERER, PpapiHostMsg_VideoEncoder_Create()); } VideoEncoderResource::~VideoEncoderResource() { + Close(); } PPB_VideoEncoder_API* VideoEncoderResource::AsPPB_VideoEncoder_API() { @@ -24,7 +78,28 @@ PPB_VideoEncoder_API* VideoEncoderResource::AsPPB_VideoEncoder_API() { int32_t VideoEncoderResource::GetSupportedProfiles( const PP_ArrayOutput& output, const scoped_refptr<TrackedCallback>& callback) { - return PP_ERROR_FAILED; + if (TrackedCallback::IsPending(get_supported_profiles_callback_)) + return PP_ERROR_INPROGRESS; + + get_supported_profiles_callback_ = callback; + Call<PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply>( + RENDERER, PpapiHostMsg_VideoEncoder_GetSupportedProfiles(), + base::Bind(&VideoEncoderResource::OnPluginMsgGetSupportedProfilesReply, + this, output)); + return PP_OK_COMPLETIONPENDING; +} + +int32_t VideoEncoderResource::GetFramesRequired() { + if (encoder_last_error_) + return encoder_last_error_; + return input_frame_count_; +} + +int32_t VideoEncoderResource::GetFrameCodedSize(PP_Size* size) { + if (encoder_last_error_) + return encoder_last_error_; + *size = input_coded_size_; + return PP_OK; } int32_t VideoEncoderResource::Initialize( @@ -34,45 +109,318 @@ int32_t VideoEncoderResource::Initialize( uint32_t initial_bitrate, PP_HardwareAcceleration acceleration, const scoped_refptr<TrackedCallback>& callback) { - return PP_ERROR_FAILED; -} + if (initialized_) + return PP_ERROR_FAILED; + if (TrackedCallback::IsPending(initialize_callback_)) + return PP_ERROR_INPROGRESS; -int32_t VideoEncoderResource::GetFramesRequired() { - return PP_ERROR_FAILED; -} - -int32_t VideoEncoderResource::GetFrameCodedSize(PP_Size* size) { - return PP_ERROR_FAILED; + initialize_callback_ = callback; + Call<PpapiPluginMsg_VideoEncoder_InitializeReply>( + RENDERER, PpapiHostMsg_VideoEncoder_Initialize( + input_format, *input_visible_size, output_profile, + initial_bitrate, acceleration), + base::Bind(&VideoEncoderResource::OnPluginMsgInitializeReply, this)); + return PP_OK_COMPLETIONPENDING; } int32_t VideoEncoderResource::GetVideoFrame( PP_Resource* video_frame, const scoped_refptr<TrackedCallback>& callback) { - return PP_ERROR_FAILED; + if (encoder_last_error_) + return encoder_last_error_; + + if (TrackedCallback::IsPending(get_video_frame_callback_)) + return PP_ERROR_INPROGRESS; + + get_video_frame_data_ = video_frame; + get_video_frame_callback_ = callback; + + // Lazily ask for a shared memory buffer in which video frames are allocated. + if (buffer_manager_.number_of_buffers() == 0) { + Call<PpapiPluginMsg_VideoEncoder_GetVideoFramesReply>( + RENDERER, PpapiHostMsg_VideoEncoder_GetVideoFrames(), + base::Bind(&VideoEncoderResource::OnPluginMsgGetVideoFramesReply, + this)); + } else { + TryWriteVideoFrame(); + } + + return PP_OK_COMPLETIONPENDING; } int32_t VideoEncoderResource::Encode( PP_Resource video_frame, PP_Bool force_keyframe, const scoped_refptr<TrackedCallback>& callback) { - return PP_ERROR_FAILED; + if (encoder_last_error_) + return encoder_last_error_; + + VideoFrameMap::iterator it = video_frames_.find(video_frame); + if (it == video_frames_.end()) + // TODO(llandwerlin): accept MediaStreamVideoTrack's video frames. + return PP_ERROR_BADRESOURCE; + + scoped_refptr<VideoFrameResource> frame_resource = it->second; + + encode_callbacks_.insert(std::make_pair(video_frame, callback)); + + Call<PpapiPluginMsg_VideoEncoder_EncodeReply>( + RENDERER, + PpapiHostMsg_VideoEncoder_Encode(frame_resource->GetBufferIndex(), + PP_ToBool(force_keyframe)), + base::Bind(&VideoEncoderResource::OnPluginMsgEncodeReply, this, + video_frame)); + + // Invalidate the frame to prevent the plugin from modifying it. + it->second->Invalidate(); + video_frames_.erase(it); + + return PP_OK_COMPLETIONPENDING; } int32_t VideoEncoderResource::GetBitstreamBuffer( - PP_BitstreamBuffer* picture, + PP_BitstreamBuffer* bitstream_buffer, const scoped_refptr<TrackedCallback>& callback) { - return PP_ERROR_FAILED; + if (encoder_last_error_) + return encoder_last_error_; + if (TrackedCallback::IsPending(get_bitstream_buffer_callback_)) + return PP_ERROR_INPROGRESS; + + get_bitstream_buffer_callback_ = callback; + get_bitstream_buffer_data_ = bitstream_buffer; + + if (!available_bitstream_buffers_.empty()) { + BitstreamBuffer buffer(available_bitstream_buffers_.front()); + available_bitstream_buffers_.pop_front(); + WriteBitstreamBuffer(buffer); + } + + return PP_OK_COMPLETIONPENDING; } void VideoEncoderResource::RecycleBitstreamBuffer( - const PP_BitstreamBuffer* picture) { + const PP_BitstreamBuffer* bitstream_buffer) { + if (encoder_last_error_) + return; + BitstreamBufferMap::const_iterator iter = + bitstream_buffer_map_.find(bitstream_buffer->buffer); + if (iter != bitstream_buffer_map_.end()) { + Post(RENDERER, + PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer(iter->second)); + } } void VideoEncoderResource::RequestEncodingParametersChange(uint32_t bitrate, uint32_t framerate) { + if (encoder_last_error_) + return; + Post(RENDERER, PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange( + bitrate, framerate)); } void VideoEncoderResource::Close() { + if (closed_) + return; + Post(RENDERER, PpapiHostMsg_VideoEncoder_Close()); + closed_ = true; + if (!encoder_last_error_ || !initialized_) + NotifyError(PP_ERROR_ABORTED); + ReleaseFrames(); +} + +void VideoEncoderResource::OnReplyReceived( + const ResourceMessageReplyParams& params, + const IPC::Message& msg) { + PPAPI_BEGIN_MESSAGE_MAP(VideoEncoderResource, msg) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( + PpapiPluginMsg_VideoEncoder_BitstreamBuffers, + OnPluginMsgBitstreamBuffers) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL( + PpapiPluginMsg_VideoEncoder_BitstreamBufferReady, + OnPluginMsgBitstreamBufferReady) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(PpapiPluginMsg_VideoEncoder_NotifyError, + OnPluginMsgNotifyError) + PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED( + PluginResource::OnReplyReceived(params, msg)) + PPAPI_END_MESSAGE_MAP() +} + +void VideoEncoderResource::OnPluginMsgGetSupportedProfilesReply( + const PP_ArrayOutput& output, + const ResourceMessageReplyParams& params, + const std::vector<PP_VideoProfileDescription>& profiles) { + void* ptr = output.GetDataBuffer( + output.user_data, + base::checked_cast<uint32_t>(profiles.size()), + base::checked_cast<uint32_t>(sizeof(PP_VideoProfileDescription))); + + if (!ptr) { + RunCallback(&get_supported_profiles_callback_, PP_ERROR_FAILED); + return; + } + + if (profiles.size() > 0) + memcpy(ptr, &profiles[0], + profiles.size() * sizeof(PP_VideoProfileDescription)); + RunCallback(&get_supported_profiles_callback_, PP_OK); +} + +void VideoEncoderResource::OnPluginMsgInitializeReply( + const ResourceMessageReplyParams& params, + uint32_t input_frame_count, + const PP_Size& input_coded_size) { + DCHECK(!initialized_); + + encoder_last_error_ = params.result(); + if (!encoder_last_error_) + initialized_ = true; + + input_frame_count_ = input_frame_count; + input_coded_size_ = input_coded_size; + + RunCallback(&initialize_callback_, encoder_last_error_); +} + +void VideoEncoderResource::OnPluginMsgGetVideoFramesReply( + const ResourceMessageReplyParams& params, + uint32_t frame_count, + uint32_t frame_length, + const PP_Size& frame_size) { + int32_t error = params.result(); + if (error) { + NotifyError(error); + return; + } + + base::SharedMemoryHandle buffer_handle; + params.TakeSharedMemoryHandleAtIndex(0, &buffer_handle); + + if (!buffer_manager_.SetBuffers( + frame_count, frame_length, + make_scoped_ptr(new base::SharedMemory(buffer_handle, false)), + true)) { + NotifyError(PP_ERROR_FAILED); + return; + } + + if (TrackedCallback::IsPending(get_video_frame_callback_)) + TryWriteVideoFrame(); +} + +void VideoEncoderResource::OnPluginMsgEncodeReply( + PP_Resource video_frame, + const ResourceMessageReplyParams& params, + uint32_t frame_id) { + DCHECK_NE(encode_callbacks_.size(), 0U); + encoder_last_error_ = params.result(); + + EncodeMap::iterator it = encode_callbacks_.find(video_frame); + DCHECK(encode_callbacks_.end() != it); + + scoped_refptr<TrackedCallback> callback = it->second; + encode_callbacks_.erase(it); + RunCallback(&callback, encoder_last_error_); + + buffer_manager_.EnqueueBuffer(frame_id); + // If the plugin is waiting for a video frame, we can give the one + // that just became available again. + if (TrackedCallback::IsPending(get_video_frame_callback_)) + TryWriteVideoFrame(); +} + +void VideoEncoderResource::OnPluginMsgBitstreamBuffers( + const ResourceMessageReplyParams& params, + uint32_t buffer_length) { + std::vector<base::SharedMemoryHandle> shm_handles; + params.TakeAllSharedMemoryHandles(&shm_handles); + if (shm_handles.size() == 0) { + NotifyError(PP_ERROR_FAILED); + return; + } + + for (uint32_t i = 0; i < shm_handles.size(); ++i) { + scoped_ptr<base::SharedMemory> shm( + new base::SharedMemory(shm_handles[i], true)); + CHECK(shm->Map(buffer_length)); + + ShmBuffer* buffer = new ShmBuffer(i, shm.Pass()); + shm_buffers_.push_back(buffer); + bitstream_buffer_map_.insert( + std::make_pair(buffer->shm->memory(), buffer->id)); + } +} + +void VideoEncoderResource::OnPluginMsgBitstreamBufferReady( + const ResourceMessageReplyParams& params, + uint32_t buffer_id, + uint32_t buffer_size, + bool key_frame) { + available_bitstream_buffers_.push_back( + BitstreamBuffer(buffer_id, buffer_size, key_frame)); + + if (TrackedCallback::IsPending(get_bitstream_buffer_callback_)) { + BitstreamBuffer buffer(available_bitstream_buffers_.front()); + available_bitstream_buffers_.pop_front(); + WriteBitstreamBuffer(buffer); + } +} + +void VideoEncoderResource::OnPluginMsgNotifyError( + const ResourceMessageReplyParams& params, + int32_t error) { + NotifyError(error); +} + +void VideoEncoderResource::NotifyError(int32_t error) { + encoder_last_error_ = error; + RunCallback(&get_supported_profiles_callback_, error); + RunCallback(&initialize_callback_, error); + RunCallback(&get_video_frame_callback_, error); + get_video_frame_data_ = nullptr; + RunCallback(&get_bitstream_buffer_callback_, error); + get_bitstream_buffer_data_ = nullptr; + for (EncodeMap::iterator it = encode_callbacks_.begin(); + it != encode_callbacks_.end(); ++it) { + scoped_refptr<TrackedCallback> callback = it->second; + RunCallback(&callback, error); + } + encode_callbacks_.clear(); +} + +void VideoEncoderResource::TryWriteVideoFrame() { + DCHECK(TrackedCallback::IsPending(get_video_frame_callback_)); + + int32_t frame_id = buffer_manager_.DequeueBuffer(); + if (frame_id < 0) + return; + + scoped_refptr<VideoFrameResource> resource = new VideoFrameResource( + pp_instance(), frame_id, buffer_manager_.GetBufferPointer(frame_id)); + video_frames_.insert( + VideoFrameMap::value_type(resource->pp_resource(), resource)); + + *get_video_frame_data_ = resource->GetReference(); + get_video_frame_data_ = nullptr; + RunCallback(&get_video_frame_callback_, PP_OK); +} + +void VideoEncoderResource::WriteBitstreamBuffer(const BitstreamBuffer& buffer) { + DCHECK_LT(buffer.id, shm_buffers_.size()); + + get_bitstream_buffer_data_->size = buffer.size; + get_bitstream_buffer_data_->buffer = shm_buffers_[buffer.id]->shm->memory(); + get_bitstream_buffer_data_->key_frame = PP_FromBool(buffer.key_frame); + get_bitstream_buffer_data_ = nullptr; + RunCallback(&get_bitstream_buffer_callback_, PP_OK); +} + +void VideoEncoderResource::ReleaseFrames() { + for (VideoFrameMap::iterator it = video_frames_.begin(); + it != video_frames_.end(); ++it) { + it->second->Invalidate(); + it->second = nullptr; + } + video_frames_.clear(); } } // namespace proxy diff --git a/ppapi/proxy/video_encoder_resource.h b/ppapi/proxy/video_encoder_resource.h index 585d5c1..3b46759 100644 --- a/ppapi/proxy/video_encoder_resource.h +++ b/ppapi/proxy/video_encoder_resource.h @@ -5,21 +5,34 @@ #ifndef PPAPI_PROXY_VIDEO_ENCODER_RESOURCE_H_ #define PPAPI_PROXY_VIDEO_ENCODER_RESOURCE_H_ +#include <deque> + #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "ppapi/proxy/connection.h" #include "ppapi/proxy/plugin_resource.h" +#include "ppapi/shared_impl/media_stream_buffer_manager.h" #include "ppapi/shared_impl/resource.h" #include "ppapi/thunk/ppb_video_encoder_api.h" +namespace base { +class SharedMemory; +} + namespace ppapi { class TrackedCallback; namespace proxy { +class SerializedHandle; +class VideoFrameResource; + class PPAPI_PROXY_EXPORT VideoEncoderResource : public PluginResource, - public thunk::PPB_VideoEncoder_API { + public thunk::PPB_VideoEncoder_API, + public ppapi::MediaStreamBufferManager::Delegate { public: VideoEncoderResource(Connection connection, PP_Instance instance); ~VideoEncoderResource() override; @@ -27,6 +40,26 @@ class PPAPI_PROXY_EXPORT VideoEncoderResource thunk::PPB_VideoEncoder_API* AsPPB_VideoEncoder_API() override; private: + struct ShmBuffer { + ShmBuffer(uint32_t id, scoped_ptr<base::SharedMemory> shm); + ~ShmBuffer(); + + // Index of the buffer in the ScopedVector. Buffers have the same id in + // the plugin and the host. + uint32_t id; + scoped_ptr<base::SharedMemory> shm; + }; + + struct BitstreamBuffer { + BitstreamBuffer(uint32_t id, uint32_t size, bool key_frame); + ~BitstreamBuffer(); + + // Index of the buffer in the ScopedVector. Same as ShmBuffer::id. + uint32_t id; + uint32_t size; + bool key_frame; + }; + // PPB_VideoEncoder_API implementation. int32_t GetSupportedProfiles( const PP_ArrayOutput& output, @@ -53,6 +86,72 @@ class PPAPI_PROXY_EXPORT VideoEncoderResource uint32_t framerate) override; void Close() override; + // PluginResource implementation. + void OnReplyReceived(const ResourceMessageReplyParams& params, + const IPC::Message& msg) override; + + // Reply message handlers for operations that are done in the host. + void OnPluginMsgGetSupportedProfilesReply( + const PP_ArrayOutput& output, + const ResourceMessageReplyParams& params, + const std::vector<PP_VideoProfileDescription>& profiles); + void OnPluginMsgInitializeReply(const ResourceMessageReplyParams& params, + uint32_t input_frame_count, + const PP_Size& input_coded_size); + void OnPluginMsgGetVideoFramesReply(const ResourceMessageReplyParams& params, + uint32_t frame_count, + uint32_t frame_length, + const PP_Size& frame_size); + void OnPluginMsgEncodeReply(PP_Resource video_frame, + const ResourceMessageReplyParams& params, + uint32_t frame_id); + + // Unsolicited reply message handlers. + void OnPluginMsgBitstreamBuffers(const ResourceMessageReplyParams& params, + uint32_t buffer_length); + void OnPluginMsgBitstreamBufferReady(const ResourceMessageReplyParams& params, + uint32_t buffer_id, + uint32_t buffer_size, + bool key_frame); + void OnPluginMsgNotifyError(const ResourceMessageReplyParams& params, + int32_t error); + + // Internal utility functions. + void NotifyError(int32_t error); + void TryWriteVideoFrame(); + void WriteBitstreamBuffer(const BitstreamBuffer& buffer); + void ReleaseFrames(); + + bool initialized_; + bool closed_; + int32_t encoder_last_error_; + + int32_t input_frame_count_; + PP_Size input_coded_size_; + + MediaStreamBufferManager buffer_manager_; + + typedef std::map<PP_Resource, scoped_refptr<VideoFrameResource> > + VideoFrameMap; + VideoFrameMap video_frames_; + + ScopedVector<ShmBuffer> shm_buffers_; + + std::deque<BitstreamBuffer> available_bitstream_buffers_; + typedef std::map<void*, uint32_t> BitstreamBufferMap; + BitstreamBufferMap bitstream_buffer_map_; + + scoped_refptr<TrackedCallback> get_supported_profiles_callback_; + scoped_refptr<TrackedCallback> initialize_callback_; + scoped_refptr<TrackedCallback> get_video_frame_callback_; + PP_Resource* get_video_frame_data_; + + typedef std::map<PP_Resource, scoped_refptr<TrackedCallback> > EncodeMap; + EncodeMap encode_callbacks_; + + scoped_refptr<TrackedCallback> get_bitstream_buffer_callback_; + PP_BitstreamBuffer* get_bitstream_buffer_data_; + DISALLOW_COPY_AND_ASSIGN(VideoEncoderResource); }; diff --git a/ppapi/proxy/video_encoder_resource_unittest.cc b/ppapi/proxy/video_encoder_resource_unittest.cc new file mode 100644 index 0000000..0adc1ea --- /dev/null +++ b/ppapi/proxy/video_encoder_resource_unittest.cc @@ -0,0 +1,1085 @@ +// 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 "base/memory/shared_memory.h" +#include "base/process/process.h" +#include "base/synchronization/waitable_event.h" +#include "ppapi/c/pp_codecs.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/ppb_video_encoder.h" +#include "ppapi/c/ppb_video_frame.h" +#include "ppapi/proxy/locking_resource_releaser.h" +#include "ppapi/proxy/plugin_message_filter.h" +#include "ppapi/proxy/ppapi_message_utils.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/ppapi_proxy_test.h" +#include "ppapi/proxy/video_encoder_resource.h" +#include "ppapi/shared_impl/media_stream_buffer.h" +#include "ppapi/shared_impl/proxy_lock.h" +#include "ppapi/thunk/thunk.h" + +using ppapi::proxy::ResourceMessageTestSink; + +namespace ppapi { +namespace proxy { + +namespace { + +class MockCompletionCallback { + public: + MockCompletionCallback() : called_(false), call_event_(false, false) {} + + bool called() { return called_; } + int32_t result() { return result_; } + + void WaitUntilCalled() { call_event_.Wait(); } + + void Reset() { + called_ = false; + call_event_.Reset(); + } + + static void Callback(void* user_data, int32_t result) { + MockCompletionCallback* that = + reinterpret_cast<MockCompletionCallback*>(user_data); + that->call_event_.Signal(); + that->called_ = true; + that->result_ = result; + } + + private: + bool called_; + int32_t result_; + base::WaitableEvent call_event_; +}; + +class VideoEncoderResourceTest : public PluginProxyTest, + public MediaStreamBufferManager::Delegate { + public: + VideoEncoderResourceTest() + : encoder_iface_(thunk::GetPPB_VideoEncoder_0_1_Thunk()), + video_frames_manager_(this) {} + ~VideoEncoderResourceTest() override {} + + const PPB_VideoEncoder_0_1* encoder_iface() const { return encoder_iface_; } + + const uint32_t kBitstreamBufferSize = 4000; + const uint32_t kBitstreamBufferCount = 5; + const uint32_t kVideoFrameCount = 3; + const uint32_t kBitrate = 200000; + + const PP_Size kFrameSize = PP_MakeSize(640, 480); + + void SendReply(const ResourceMessageCallParams& params, + int32_t result, + const IPC::Message& nested_message) { + ResourceMessageReplyParams reply_params(params.pp_resource(), + params.sequence()); + reply_params.set_result(result); + PluginMessageFilter::DispatchResourceReplyForTest(reply_params, + nested_message); + } + + void SendReplyWithHandle(const ResourceMessageCallParams& params, + int32_t result, + const IPC::Message& nested_message, + const SerializedHandle& handle) { + ResourceMessageReplyParams reply_params(params.pp_resource(), + params.sequence()); + reply_params.set_result(result); + reply_params.AppendHandle(handle); + PluginMessageFilter::DispatchResourceReplyForTest(reply_params, + nested_message); + } + + void SendReplyWithHandles(const ResourceMessageCallParams& params, + int32_t result, + const IPC::Message& nested_message, + const std::vector<SerializedHandle>& handles) { + ResourceMessageReplyParams reply_params(params.pp_resource(), + params.sequence()); + reply_params.set_result(result); + for (SerializedHandle handle : handles) + reply_params.AppendHandle(handle); + PluginMessageFilter::DispatchResourceReplyForTest(reply_params, + nested_message); + } + + PP_Resource CreateEncoder() { + PP_Resource result = encoder_iface()->Create(pp_instance()); + return result; + } + + void CreateBitstreamSharedMemory(uint32_t buffer_size, uint32_t nb_buffers) { + shared_memory_bitstreams_.clear(); + for (uint32_t i = 0; i < nb_buffers; ++i) { + scoped_ptr<base::SharedMemory> mem(new base::SharedMemory()); + ASSERT_TRUE(mem->CreateAnonymous(buffer_size)); + shared_memory_bitstreams_.push_back(mem.Pass()); + } + } + + void CreateVideoFramesSharedMemory(uint32_t frame_length, + uint32_t frame_count) { + scoped_ptr<base::SharedMemory> shared_memory_frames( + new base::SharedMemory()); + uint32_t buffer_length = + frame_length + sizeof(ppapi::MediaStreamBuffer::Video); + ASSERT_TRUE(shared_memory_frames->CreateAnonymous(buffer_length * + frame_count)); + ASSERT_TRUE(video_frames_manager_.SetBuffers(frame_count, + buffer_length, + shared_memory_frames.Pass(), + true)); + for (int32_t i = 0; i < video_frames_manager_.number_of_buffers(); ++i) { + ppapi::MediaStreamBuffer::Video* buffer = + &(video_frames_manager_.GetBufferPointer(i)->video); + buffer->header.size = buffer_length; + buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO; + buffer->format = PP_VIDEOFRAME_FORMAT_I420; + buffer->size = kFrameSize; + buffer->data_size = frame_length; + } + } + + PP_Resource CreateAndInitializeEncoder() { + PP_Resource encoder = CreateEncoder(); + PP_Size size = kFrameSize; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder, PP_VIDEOFRAME_FORMAT_I420, &size, PP_VIDEOPROFILE_H264MAIN, + kBitrate, PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + if (result != PP_OK_COMPLETIONPENDING) + return 0; + ResourceMessageCallParams params; + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_Initialize::ID, ¶ms, &msg)) + return 0; + sink().ClearMessages(); + + SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize); + CreateBitstreamSharedMemory(kBitstreamBufferSize, kBitstreamBufferCount); + SendBitstreamBuffers(params, kBitstreamBufferSize); + + if (!cb.called() || cb.result() != PP_OK) + return 0; + + return encoder; + } + + int32_t CallGetFramesRequired(PP_Resource pp_encoder) { + return encoder_iface()->GetFramesRequired(pp_encoder); + } + + int32_t CallGetFrameCodedSize(PP_Resource pp_encoder, PP_Size* coded_size) { + return encoder_iface()->GetFrameCodedSize(pp_encoder, coded_size); + } + + int32_t CallGetVideoFrame(PP_Resource pp_encoder, + PP_Resource* video_frame, + MockCompletionCallback* cb) { + return encoder_iface()->GetVideoFrame( + pp_encoder, video_frame, PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + } + + int32_t CallFirstGetVideoFrame(PP_Resource pp_encoder, + PP_Resource* video_frame, + MockCompletionCallback* cb) { + int32_t result = encoder_iface()->GetVideoFrame( + pp_encoder, video_frame, PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + if (result != PP_OK_COMPLETIONPENDING) + return result; + + ResourceMessageCallParams params; + CheckGetVideoFramesMsg(¶ms); + + uint32_t frame_length = kFrameSize.width * kFrameSize.height * 2; + CreateVideoFramesSharedMemory(frame_length, kVideoFrameCount); + SendGetVideoFramesReply(params, kVideoFrameCount, frame_length, kFrameSize); + + return result; + } + + int32_t CallEncode(PP_Resource pp_encoder, + PP_Resource video_frame, + PP_Bool force_keyframe, + MockCompletionCallback* cb) { + return encoder_iface()->Encode(pp_encoder, video_frame, force_keyframe, + PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + } + + int32_t CallCompleteEncode(PP_Resource pp_encoder, + PP_Resource video_frame, + PP_Bool force_keyframe, + MockCompletionCallback* cb) { + int32_t result = + encoder_iface()->Encode(pp_encoder, video_frame, force_keyframe, + PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, cb)); + if (result != PP_OK_COMPLETIONPENDING) + return result; + + ResourceMessageCallParams params; + uint32_t frame_id; + bool forced_keyframe; + if (!CheckEncodeMsg(¶ms, &frame_id, &forced_keyframe)) + return PP_ERROR_FAILED; + + SendEncodeReply(params, frame_id); + + return result; + } + + int32_t CallGetBitstreamBuffer(PP_Resource pp_encoder, + PP_BitstreamBuffer* bitstream_buffer, + MockCompletionCallback* cb) { + return encoder_iface()->GetBitstreamBuffer( + pp_encoder, bitstream_buffer, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + cb)); + } + + void CallRecycleBitstreamBuffer(PP_Resource pp_encoder, + const PP_BitstreamBuffer& buffer) { + encoder_iface()->RecycleBitstreamBuffer(pp_encoder, &buffer); + } + + void CallRequestEncodingParametersChange(PP_Resource pp_encoder, + uint32_t bitrate, + uint32_t framerate) { + encoder_iface()->RequestEncodingParametersChange(pp_encoder, bitrate, + framerate); + } + + void CallClose(PP_Resource pp_encoder) { + encoder_iface()->Close(pp_encoder); + } + + void SendGetSupportedProfilesReply( + const ResourceMessageCallParams& params, + const std::vector<PP_VideoProfileDescription>& profiles) { + SendReply(params, PP_OK, + PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply(profiles)); + } + + void SendInitializeReply(const ResourceMessageCallParams& params, + int32_t success, + uint32_t input_frame_count, + const PP_Size& input_coded_size) { + SendReply(params, success, PpapiPluginMsg_VideoEncoder_InitializeReply( + input_frame_count, input_coded_size)); + } + + void SendBitstreamBuffers(const ResourceMessageCallParams& params, + uint32_t buffer_length) { + std::vector<SerializedHandle> handles; + for (base::SharedMemory* mem : shared_memory_bitstreams_) { + ASSERT_EQ(mem->requested_size(), buffer_length); + base::SharedMemoryHandle handle; + + ASSERT_TRUE( + mem->ShareToProcess(base::Process::Current().Handle(), &handle)); + handles.push_back(SerializedHandle(handle, buffer_length)); + } + SendReplyWithHandles( + params, PP_OK, + PpapiPluginMsg_VideoEncoder_BitstreamBuffers(buffer_length), handles); + } + + void SendGetVideoFramesReply(const ResourceMessageCallParams& params, + uint32_t frame_count, + uint32_t frame_length, + const PP_Size& size) { + base::SharedMemoryHandle handle; + ASSERT_TRUE(video_frames_manager_.shm()->ShareToProcess( + base::Process::Current().Handle(), &handle)); + SendReplyWithHandle( + params, PP_OK, PpapiPluginMsg_VideoEncoder_GetVideoFramesReply( + frame_count, + frame_length + sizeof(MediaStreamBuffer::Video), + size), + SerializedHandle( + handle, + static_cast<uint32_t>( + video_frames_manager_.shm()->requested_size()))); + } + + void SendEncodeReply(const ResourceMessageCallParams& params, + uint32_t frame_id) { + SendReply(params, PP_OK, PpapiPluginMsg_VideoEncoder_EncodeReply(frame_id)); + } + + void SendBitstreamBufferReady(const ResourceMessageCallParams& params, + uint32_t buffer_id, + uint32_t buffer_size, + bool keyframe) { + SendReply(params, PP_OK, + PpapiPluginMsg_VideoEncoder_BitstreamBufferReady( + buffer_id, buffer_size, PP_FromBool(keyframe))); + } + + void SendNotifyError(const ResourceMessageCallParams& params, int32_t error) { + SendReply(params, PP_OK, PpapiPluginMsg_VideoEncoder_NotifyError(error)); + } + + bool CheckGetSupportedProfilesMsg(ResourceMessageCallParams* params) { + IPC::Message msg; + return sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_GetSupportedProfiles::ID, params, &msg); + } + + bool CheckInitializeMsg(ResourceMessageCallParams* params, + PP_VideoFrame_Format* input_format, + struct PP_Size* input_visible_size, + PP_VideoProfile* output_profile, + uint32_t* bitrate, + PP_HardwareAcceleration* acceleration) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_Initialize::ID, params, &msg)) + return false; + sink().ClearMessages(); + return UnpackMessage<PpapiHostMsg_VideoEncoder_Initialize>( + msg, input_format, input_visible_size, output_profile, bitrate, + acceleration); + } + + bool CheckGetVideoFramesMsg(ResourceMessageCallParams* params) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_GetVideoFrames::ID, params, &msg)) + return false; + sink().ClearMessages(); + return true; + } + + bool CheckEncodeMsg(ResourceMessageCallParams* params, + uint32_t* frame_id, + bool* keyframe) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_Encode::ID, params, &msg)) + return false; + sink().ClearMessages(); + return UnpackMessage<PpapiHostMsg_VideoEncoder_Encode>(msg, frame_id, + keyframe); + } + + bool CheckRecycleBitstreamBufferMsg(ResourceMessageCallParams* params, + uint32_t* buffer_id) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer::ID, params, &msg)) + return false; + sink().ClearMessages(); + return UnpackMessage<PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer>( + msg, buffer_id); + } + + bool CheckRequestEncodingParametersChangeMsg( + ResourceMessageCallParams* params, + uint32_t* bitrate, + uint32_t* framerate) { + IPC::Message msg; + if (!sink().GetFirstResourceCallMatching( + PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange::ID, + params, &msg)) + return false; + sink().ClearMessages(); + return UnpackMessage< + PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange>(msg, bitrate, + framerate); + } + + bool CheckIsVideoFrame(PP_Resource video_frame) { + return thunk::GetPPB_VideoFrame_0_1_Thunk()->IsVideoFrame(video_frame); + } + + bool CheckIsVideoFrameValid(PP_Resource video_frame) { + PP_Size frame_size; + return thunk::GetPPB_VideoFrame_0_1_Thunk()->GetSize( + video_frame, &frame_size) == PP_TRUE; + } + + private: + // MediaStreamBufferManager::Delegate: + void OnNewBufferEnqueued() override {} + + const PPB_VideoEncoder_0_1* encoder_iface_; + + ScopedVector<base::SharedMemory> shared_memory_bitstreams_; + + MediaStreamBufferManager video_frames_manager_; +}; + +void* ForwardUserData(void* user_data, + uint32_t element_count, + uint32_t element_size) { + return user_data; +} + +} // namespace + +TEST_F(VideoEncoderResourceTest, GetSupportedProfiles) { + // Verifies that GetSupportedProfiles calls into the renderer and + // the we get the right results back. + { + LockingResourceReleaser encoder(CreateEncoder()); + PP_VideoProfileDescription profiles[2]; + PP_ArrayOutput output; + output.user_data = &profiles[0]; + output.GetDataBuffer = ForwardUserData; + ResourceMessageCallParams params; + MockCompletionCallback cb; + int32_t result = encoder_iface()->GetSupportedProfiles( + encoder.get(), output, PP_MakeOptionalCompletionCallback( + &MockCompletionCallback::Callback, &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + ASSERT_TRUE(CheckGetSupportedProfilesMsg(¶ms)); + + std::vector<PP_VideoProfileDescription> profiles_response; + PP_VideoProfileDescription profile; + profile.profile = PP_VIDEOPROFILE_H264MAIN; + profile.max_resolution.width = 1920; + profile.max_resolution.height = 1080; + profile.max_framerate_numerator = 30; + profile.max_framerate_denominator = 1; + profile.acceleration = PP_HARDWAREACCELERATION_ONLY; + profiles_response.push_back(profile); + profile.profile = PP_VIDEOPROFILE_VP8_ANY; + profile.max_resolution.width = 1920; + profile.max_resolution.height = 1080; + profile.max_framerate_numerator = 30; + profile.max_framerate_denominator = 1; + profile.acceleration = PP_HARDWAREACCELERATION_NONE; + profiles_response.push_back(profile); + + SendGetSupportedProfilesReply(params, profiles_response); + ASSERT_EQ(PP_OK, cb.result()); + + ASSERT_EQ(2U, profiles_response.size()); + ASSERT_EQ(0, memcmp(&profiles[0], &profiles_response[0], + sizeof(PP_VideoProfileDescription) * 2)); + } +} + +TEST_F(VideoEncoderResourceTest, InitializeFailure) { + { + // Verify the initialize callback is called in case of failure. + LockingResourceReleaser encoder(CreateEncoder()); + ResourceMessageCallParams params; + PP_Size size = kFrameSize; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + PP_VideoFrame_Format input_format; + PP_Size input_visible_size; + PP_VideoProfile output_profile; + uint32_t bitrate; + PP_HardwareAcceleration acceleration; + ASSERT_TRUE(CheckInitializeMsg(¶ms, &input_format, &input_visible_size, + &output_profile, &bitrate, &acceleration)); + ASSERT_EQ(PP_VIDEOFRAME_FORMAT_BGRA, input_format); + ASSERT_EQ(size.width, input_visible_size.width); + ASSERT_EQ(size.height, input_visible_size.height); + ASSERT_EQ(kBitrate, bitrate); + ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile); + ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration); + + SendInitializeReply(params, PP_ERROR_NOTSUPPORTED, kVideoFrameCount, + kFrameSize); + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_ERROR_NOTSUPPORTED, cb.result()); + } + { + // Verify the initialize callback is called in case of error + // notification. + LockingResourceReleaser encoder(CreateEncoder()); + ResourceMessageCallParams params; + PP_Size size = kFrameSize; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + PP_VideoFrame_Format input_format; + PP_Size input_visible_size; + PP_VideoProfile output_profile; + uint32_t bitrate; + PP_HardwareAcceleration acceleration; + ASSERT_TRUE(CheckInitializeMsg(¶ms, &input_format, &input_visible_size, + &output_profile, &bitrate, &acceleration)); + ASSERT_EQ(PP_VIDEOFRAME_FORMAT_BGRA, input_format); + ASSERT_EQ(kFrameSize.width, input_visible_size.width); + ASSERT_EQ(kFrameSize.height, input_visible_size.height); + ASSERT_EQ(kBitrate, bitrate); + ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile); + ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration); + + ResourceMessageCallParams error_params(encoder.get(), 0); + SendNotifyError(error_params, PP_ERROR_FAILED); + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_ERROR_FAILED, cb.result()); + } + { + // Verify that calling initialize twice fails the second time if + // we haven't received a response yet. + LockingResourceReleaser encoder(CreateEncoder()); + ResourceMessageCallParams params; + PP_Size size = kFrameSize; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + PP_VideoFrame_Format input_format; + PP_Size input_visible_size; + PP_VideoProfile output_profile; + uint32_t bitrate; + PP_HardwareAcceleration acceleration; + ASSERT_TRUE(CheckInitializeMsg(¶ms, &input_format, &input_visible_size, + &output_profile, &bitrate, &acceleration)); + ASSERT_EQ(PP_VIDEOFRAME_FORMAT_BGRA, input_format); + ASSERT_EQ(size.width, input_visible_size.width); + ASSERT_EQ(size.height, input_visible_size.height); + ASSERT_EQ(kBitrate, bitrate); + ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile); + ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration); + + result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_ERROR_INPROGRESS, result); + + ResourceMessageCallParams error_params(encoder.get(), 0); + SendNotifyError(error_params, PP_ERROR_FAILED); + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_ERROR_FAILED, cb.result()); + } +} + +TEST_F(VideoEncoderResourceTest, InitializeSuccess) { + { + // Verify the initialize callback is called when initialization is + // successfull. + LockingResourceReleaser encoder(CreateEncoder()); + ResourceMessageCallParams params; + PP_Size size = kFrameSize; + const uint32_t kBitrate = 420000; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + PP_VideoFrame_Format input_format; + PP_Size input_visible_size; + PP_VideoProfile output_profile; + uint32_t bitrate; + PP_HardwareAcceleration acceleration; + ASSERT_TRUE(CheckInitializeMsg(¶ms, &input_format, &input_visible_size, + &output_profile, &bitrate, &acceleration)); + ASSERT_EQ(PP_VIDEOFRAME_FORMAT_I420, input_format); + ASSERT_EQ(kFrameSize.width, input_visible_size.width); + ASSERT_EQ(kFrameSize.height, input_visible_size.height); + ASSERT_EQ(kBitrate, bitrate); + ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile); + ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration); + + SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize); + + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_OK, cb.result()); + } + { + // Verify that calling initialize a second time, after it already + // succeeded, fails. + LockingResourceReleaser encoder(CreateEncoder()); + ResourceMessageCallParams params; + PP_Size size = kFrameSize; + const uint32_t kBitrate = 420000; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + PP_VideoFrame_Format input_format; + PP_Size input_visible_size; + PP_VideoProfile output_profile; + uint32_t bitrate; + PP_HardwareAcceleration acceleration; + ASSERT_TRUE(CheckInitializeMsg(¶ms, &input_format, &input_visible_size, + &output_profile, &bitrate, &acceleration)); + ASSERT_EQ(PP_VIDEOFRAME_FORMAT_I420, input_format); + ASSERT_EQ(kFrameSize.width, input_visible_size.width); + ASSERT_EQ(kFrameSize.height, input_visible_size.height); + ASSERT_EQ(kBitrate, bitrate); + ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile); + ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration); + + SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize); + + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_OK, cb.result()); + + result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_ERROR_FAILED, result); + } + { + // Verify the sending the bitstream buffers details makes them + // available through the API. the right values. + LockingResourceReleaser encoder(CreateEncoder()); + ResourceMessageCallParams params; + PP_Size size = kFrameSize; + const uint32_t kBitrate = 420000; + MockCompletionCallback cb; + int32_t result = encoder_iface()->Initialize( + encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size, + PP_VIDEOPROFILE_H264MAIN, kBitrate, + PP_HARDWAREACCELERATION_WITHFALLBACK, + PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback, + &cb)); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); + + PP_VideoFrame_Format input_format; + PP_Size input_visible_size; + PP_VideoProfile output_profile; + uint32_t bitrate; + PP_HardwareAcceleration acceleration; + ASSERT_TRUE(CheckInitializeMsg(¶ms, &input_format, &input_visible_size, + &output_profile, &bitrate, &acceleration)); + ASSERT_EQ(PP_VIDEOFRAME_FORMAT_I420, input_format); + ASSERT_EQ(kFrameSize.width, input_visible_size.width); + ASSERT_EQ(kFrameSize.height, input_visible_size.height); + ASSERT_EQ(kBitrate, bitrate); + ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile); + ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration); + + SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize); + + ASSERT_TRUE(cb.called()); + ASSERT_EQ(PP_OK, cb.result()); + + PP_Size coded_size; + ASSERT_EQ(PP_OK, CallGetFrameCodedSize(encoder.get(), &coded_size)); + ASSERT_EQ(kFrameSize.width, coded_size.width); + ASSERT_EQ(kFrameSize.height, coded_size.height); + ASSERT_EQ(static_cast<int32_t>(kVideoFrameCount), + CallGetFramesRequired(encoder.get())); + } +} + +TEST_F(VideoEncoderResourceTest, Uninitialized) { + // Operations on uninitialized encoders should fail. + LockingResourceReleaser encoder(CreateEncoder()); + + ASSERT_EQ(PP_ERROR_FAILED, CallGetFramesRequired(encoder.get())); + + PP_Size size; + ASSERT_EQ(PP_ERROR_FAILED, CallGetFrameCodedSize(encoder.get(), &size)); + + MockCompletionCallback uncalled_cb; + PP_Resource video_frame = 0; + ASSERT_EQ(PP_ERROR_FAILED, + CallGetVideoFrame(encoder.get(), &video_frame, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + ASSERT_EQ(0, video_frame); + + ASSERT_EQ(PP_ERROR_FAILED, + CallEncode(encoder.get(), video_frame, PP_FALSE, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ( + PP_ERROR_FAILED, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, &uncalled_cb)); + ASSERT_FALSE(uncalled_cb.called()); + + ResourceMessageCallParams params; + uint32_t buffer_id; + CallRecycleBitstreamBuffer(encoder.get(), bitstream_buffer); + ASSERT_FALSE(CheckRecycleBitstreamBufferMsg(¶ms, &buffer_id)); + + uint32_t bitrate, framerate; + CallRequestEncodingParametersChange(encoder.get(), 0, 0); + ASSERT_FALSE( + CheckRequestEncodingParametersChangeMsg(¶ms, &bitrate, &framerate)); +} + +TEST_F(VideoEncoderResourceTest, InitializeAndGetVideoFrame) { + // Verify that we can pull the right number of video frames before + // the proxy makes us wait. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + ResourceMessageCallParams params; + std::vector<PP_Resource> video_frames; + MockCompletionCallback get_frame_cb; + + video_frames.resize(kVideoFrameCount + 1); + + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetVideoFrame(encoder.get(), &video_frames[0], &get_frame_cb)); + ASSERT_FALSE(get_frame_cb.called()); + ASSERT_TRUE(CheckGetVideoFramesMsg(¶ms)); + + uint32_t frame_length = kFrameSize.width * kFrameSize.height * 2; + CreateVideoFramesSharedMemory(frame_length, kVideoFrameCount); + SendGetVideoFramesReply(params, kVideoFrameCount, frame_length, kFrameSize); + + for (uint32_t i = 1; i < kVideoFrameCount; ++i) { + get_frame_cb.Reset(); + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallGetVideoFrame(encoder.get(), &video_frames[i], &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + ASSERT_TRUE(CheckIsVideoFrame(video_frames[i])); + } + + get_frame_cb.Reset(); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetVideoFrame(encoder.get(), &video_frames[kVideoFrameCount], + &get_frame_cb)); + ASSERT_FALSE(get_frame_cb.called()); + + MockCompletionCallback get_frame_fail_cb; + ASSERT_EQ(PP_ERROR_INPROGRESS, + CallGetVideoFrame(encoder.get(), &video_frames[kVideoFrameCount], + &get_frame_fail_cb)); + ASSERT_FALSE(get_frame_fail_cb.called()); + + // Unblock the GetVideoFrame callback by freeing up a frame. + MockCompletionCallback encode_cb; + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallCompleteEncode(encoder.get(), video_frames[0], PP_FALSE, &encode_cb)); + ASSERT_TRUE(encode_cb.called()); + ASSERT_EQ(PP_OK, encode_cb.result()); + ASSERT_TRUE(get_frame_cb.called()); + + CallClose(encoder.get()); +} + +TEST_F(VideoEncoderResourceTest, Encode) { + // Check Encode() calls into the renderer. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + PP_Resource video_frame; + MockCompletionCallback get_frame_cb; + + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallFirstGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + + MockCompletionCallback encode_cb; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallEncode(encoder.get(), video_frame, PP_TRUE, &encode_cb)); + ASSERT_FALSE(encode_cb.called()); + ASSERT_FALSE(CheckIsVideoFrameValid(video_frame)); + + ResourceMessageCallParams params; + uint32_t frame_id; + bool force_frame; + ASSERT_TRUE(CheckEncodeMsg(¶ms, &frame_id, &force_frame)); + + SendEncodeReply(params, frame_id); + + ASSERT_TRUE(encode_cb.called()); + ASSERT_EQ(PP_OK, encode_cb.result()); +} + +TEST_F(VideoEncoderResourceTest, EncodeAndGetVideoFrame) { + // Check the encoding loop works well. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + ResourceMessageCallParams params; + PP_Resource video_frame; + MockCompletionCallback get_frame_cb, encode_cb; + + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallFirstGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + + for (uint32_t i = 1; i < 20 * kVideoFrameCount; ++i) { + encode_cb.Reset(); + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallCompleteEncode(encoder.get(), video_frame, PP_FALSE, &encode_cb)); + ASSERT_TRUE(encode_cb.called()); + ASSERT_EQ(PP_OK, encode_cb.result()); + + get_frame_cb.Reset(); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + ASSERT_TRUE(CheckIsVideoFrame(video_frame)); + } + + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallCompleteEncode(encoder.get(), video_frame, PP_FALSE, &encode_cb)); + ASSERT_TRUE(encode_cb.called()); + ASSERT_EQ(PP_OK, encode_cb.result()); +} + +TEST_F(VideoEncoderResourceTest, GetBitstreamBuffer) { + { + // Verify that the GetBitstreamBuffer callback is fired whenever the + // renderer signals a buffer is available. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + ASSERT_FALSE(get_bitstream_buffer_cb.called()); + + ResourceMessageCallParams buffer_params(encoder.get(), 0); + SendBitstreamBufferReady(buffer_params, 0, 10, true); + + ASSERT_TRUE(get_bitstream_buffer_cb.called()); + ASSERT_EQ(PP_OK, get_bitstream_buffer_cb.result()); + ASSERT_EQ(10U, bitstream_buffer.size); + ASSERT_EQ(PP_TRUE, bitstream_buffer.key_frame); + } + { + // Verify that calling GetBitstreamBuffer a second time, while the + // first callback hasn't been fired fails. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + ASSERT_FALSE(get_bitstream_buffer_cb.called()); + + ASSERT_EQ(PP_ERROR_INPROGRESS, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + ASSERT_FALSE(get_bitstream_buffer_cb.called()); + + ResourceMessageCallParams buffer_params(encoder.get(), 0); + SendBitstreamBufferReady(buffer_params, 0, 10, true); + } +} + +TEST_F(VideoEncoderResourceTest, RecycleBitstreamBuffer) { + // Verify that we signal the renderer that a bitstream buffer has been + // recycled. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + ASSERT_FALSE(get_bitstream_buffer_cb.called()); + + ResourceMessageCallParams buffer_params(encoder.get(), 0); + SendBitstreamBufferReady(buffer_params, kBitstreamBufferCount - 1, 10, true); + + ASSERT_TRUE(get_bitstream_buffer_cb.called()); + ASSERT_EQ(PP_OK, get_bitstream_buffer_cb.result()); + + CallRecycleBitstreamBuffer(encoder.get(), bitstream_buffer); + + ResourceMessageCallParams recycle_params; + uint32_t buffer_id; + ASSERT_TRUE(CheckRecycleBitstreamBufferMsg(&recycle_params, &buffer_id)); + ASSERT_EQ(kBitstreamBufferCount - 1, buffer_id); +} + +TEST_F(VideoEncoderResourceTest, RequestEncodingParametersChange) { + // Check encoding parameter changes are correctly sent to the + // renderer. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + CallRequestEncodingParametersChange(encoder.get(), 1, 2); + ResourceMessageCallParams params; + uint32_t bitrate, framerate; + ASSERT_TRUE( + CheckRequestEncodingParametersChangeMsg(¶ms, &bitrate, &framerate)); + ASSERT_EQ(1U, bitrate); + ASSERT_EQ(2U, framerate); +} + +TEST_F(VideoEncoderResourceTest, NotifyError) { + { + // Check an error from the encoder aborts GetVideoFrame and + // GetBitstreamBuffer callbacks. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_frame_cb; + PP_Resource video_frame; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb)); + ASSERT_FALSE(get_frame_cb.called()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + + ResourceMessageCallParams error_params(encoder.get(), 0); + SendNotifyError(error_params, PP_ERROR_FAILED); + + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_ERROR_FAILED, get_frame_cb.result()); + ASSERT_TRUE(get_bitstream_buffer_cb.called()); + ASSERT_EQ(PP_ERROR_FAILED, get_bitstream_buffer_cb.result()); + } + { + // Check an error from the encoder aborts Encode and GetBitstreamBuffer + // callbacks. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_frame_cb, encode_cb1, encode_cb2; + PP_Resource video_frame1, video_frame2; + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallFirstGetVideoFrame(encoder.get(), &video_frame1, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + + get_frame_cb.Reset(); + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallFirstGetVideoFrame(encoder.get(), &video_frame2, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallEncode(encoder.get(), video_frame1, PP_FALSE, &encode_cb1)); + ASSERT_FALSE(encode_cb1.called()); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallEncode(encoder.get(), video_frame2, PP_FALSE, &encode_cb2)); + ASSERT_FALSE(encode_cb2.called()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + + ResourceMessageCallParams error_params(encoder.get(), 0); + SendNotifyError(error_params, PP_ERROR_FAILED); + + ASSERT_TRUE(encode_cb1.called()); + ASSERT_EQ(PP_ERROR_FAILED, encode_cb1.result()); + ASSERT_TRUE(encode_cb2.called()); + ASSERT_EQ(PP_ERROR_FAILED, encode_cb2.result()); + ASSERT_TRUE(get_bitstream_buffer_cb.called()); + ASSERT_EQ(PP_ERROR_FAILED, get_bitstream_buffer_cb.result()); + } +} + +TEST_F(VideoEncoderResourceTest, Close) { + { + // Check closing the encoder aborts GetVideoFrame and + // GetBitstreamBuffer callbacks. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_frame_cb; + PP_Resource video_frame; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb)); + ASSERT_FALSE(get_frame_cb.called()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + + CallClose(encoder.get()); + + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_ERROR_ABORTED, get_frame_cb.result()); + ASSERT_TRUE(get_bitstream_buffer_cb.called()); + ASSERT_EQ(PP_ERROR_ABORTED, get_bitstream_buffer_cb.result()); + } + { + // Check closing the encoder aborts Encode and GetBitstreamBuffer + // callbacks. + LockingResourceReleaser encoder(CreateAndInitializeEncoder()); + + MockCompletionCallback get_frame_cb, encode_cb1, encode_cb2; + PP_Resource video_frame1, video_frame2; + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallFirstGetVideoFrame(encoder.get(), &video_frame1, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + + get_frame_cb.Reset(); + ASSERT_EQ( + PP_OK_COMPLETIONPENDING, + CallFirstGetVideoFrame(encoder.get(), &video_frame2, &get_frame_cb)); + ASSERT_TRUE(get_frame_cb.called()); + ASSERT_EQ(PP_OK, get_frame_cb.result()); + + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallEncode(encoder.get(), video_frame1, PP_FALSE, &encode_cb1)); + ASSERT_FALSE(encode_cb1.called()); + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallEncode(encoder.get(), video_frame2, PP_FALSE, &encode_cb2)); + ASSERT_FALSE(encode_cb2.called()); + + MockCompletionCallback get_bitstream_buffer_cb; + PP_BitstreamBuffer bitstream_buffer; + ASSERT_EQ(PP_OK_COMPLETIONPENDING, + CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, + &get_bitstream_buffer_cb)); + + CallClose(encoder.get()); + + ASSERT_TRUE(encode_cb1.called()); + ASSERT_EQ(PP_ERROR_ABORTED, encode_cb1.result()); + ASSERT_TRUE(encode_cb2.called()); + ASSERT_EQ(PP_ERROR_ABORTED, encode_cb2.result()); + ASSERT_TRUE(get_bitstream_buffer_cb.called()); + ASSERT_EQ(PP_ERROR_ABORTED, get_bitstream_buffer_cb.result()); + } +} + +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/tests/test_video_encoder.cc b/ppapi/tests/test_video_encoder.cc new file mode 100644 index 0000000..4737ed0 --- /dev/null +++ b/ppapi/tests/test_video_encoder.cc @@ -0,0 +1,41 @@ +// 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 "ppapi/tests/test_video_encoder.h" + +#include "ppapi/c/pp_codecs.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/cpp/video_encoder.h" +#include "ppapi/tests/testing_instance.h" + +REGISTER_TEST_CASE(VideoEncoder); + +bool TestVideoEncoder::Init() { + video_encoder_interface_ = static_cast<const PPB_VideoEncoder_0_1*>( + pp::Module::Get()->GetBrowserInterface(PPB_VIDEOENCODER_INTERFACE_0_1)); + return video_encoder_interface_ && CheckTestingInterface(); +} + +void TestVideoEncoder::RunTests(const std::string& filter) { + RUN_CALLBACK_TEST(TestVideoEncoder, Create, filter); +} + +std::string TestVideoEncoder::TestCreate() { + // Test that we get results for supported formats. + { + pp::VideoEncoder video_encoder(instance_); + ASSERT_FALSE(video_encoder.is_null()); + + TestCompletionCallbackWithOutput<std::vector<PP_VideoProfileDescription> > + callback(instance_->pp_instance(), false); + callback.WaitForResult( + video_encoder.GetSupportedProfiles(callback.GetCallback())); + + ASSERT_GE(callback.output().size(), 0U); + } + // TODO(llandwerlin): add more tests once software video encode is + // available. + + PASS(); +} diff --git a/ppapi/tests/test_video_encoder.h b/ppapi/tests/test_video_encoder.h new file mode 100644 index 0000000..f814f9e --- /dev/null +++ b/ppapi/tests/test_video_encoder.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef PPAPI_TESTS_TEST_VIDEO_ENCODER_H_ +#define PPAPI_TESTS_TEST_VIDEO_ENCODER_H_ + +#include <string> + +#include "ppapi/c/pp_stdint.h" +#include "ppapi/c/ppb_video_encoder.h" +#include "ppapi/tests/test_case.h" + +class TestVideoEncoder : public TestCase { + public: + explicit TestVideoEncoder(TestingInstance* instance) : TestCase(instance) {} + + private: + // TestCase implementation. + virtual bool Init(); + virtual void RunTests(const std::string& filter); + + std::string TestCreate(); + + // Used by the tests that access the C API directly. + const PPB_VideoEncoder_0_1* video_encoder_interface_; +}; + +#endif // PPAPI_TESTS_TEST_VIDEO_ENCODER_H_ |