// 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/proxy/video_encoder_resource.h" #include #include #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/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(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_2_Thunk()), encoder_iface_0_1_(thunk::GetPPB_VideoEncoder_0_1_Thunk()), video_frames_manager_(this) {} ~VideoEncoderResourceTest() override {} const PPB_VideoEncoder_0_2* encoder_iface() const { return encoder_iface_; } const PPB_VideoEncoder_0_1* encoder_iface_0_1() const { return encoder_iface_0_1_; } 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& 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 mem(new base::SharedMemory()); ASSERT_TRUE(mem->CreateAnonymous(buffer_size)); shared_memory_bitstreams_.push_back(std::move(mem)); } } void CreateVideoFramesSharedMemory(uint32_t frame_length, uint32_t frame_count) { scoped_ptr 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, std::move(shared_memory_frames), 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& 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 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( 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( 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(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( 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_2* encoder_iface_; const PPB_VideoEncoder_0_1* encoder_iface_0_1_; ScopedVector 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 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.hardware_accelerated = PP_TRUE; 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.hardware_accelerated = PP_FALSE; profiles_response.push_back(profile); SendGetSupportedProfilesReply(params, profiles_response); ASSERT_EQ(profiles_response.size(), static_cast(cb.result())); ASSERT_EQ(0, memcmp(&profiles[0], &profiles_response[0], sizeof(profiles))); } } TEST_F(VideoEncoderResourceTest, GetSupportedProfiles0_1) { // Verifies that GetSupportedProfiles calls into the renderer and // the we get the right results back. { LockingResourceReleaser encoder(CreateEncoder()); PP_VideoProfileDescription_0_1 profiles[2]; PP_ArrayOutput output; output.user_data = &profiles[0]; output.GetDataBuffer = ForwardUserData; ResourceMessageCallParams params; MockCompletionCallback cb; int32_t result = encoder_iface_0_1()->GetSupportedProfiles( encoder.get(), output, PP_MakeOptionalCompletionCallback( &MockCompletionCallback::Callback, &cb)); ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); ASSERT_TRUE(CheckGetSupportedProfilesMsg(¶ms)); std::vector 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.hardware_accelerated = PP_TRUE; 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.hardware_accelerated = PP_FALSE; profiles_response.push_back(profile); SendGetSupportedProfilesReply(params, profiles_response); ASSERT_EQ(profiles_response.size(), static_cast(cb.result())); for (uint32_t i = 0; i < profiles_response.size(); i++) { ASSERT_EQ(profiles_response[i].profile, profiles[i].profile); ASSERT_EQ(profiles_response[i].max_resolution.width, profiles[i].max_resolution.width); ASSERT_EQ(profiles_response[i].max_resolution.height, profiles[i].max_resolution.height); ASSERT_EQ(profiles_response[i].max_framerate_numerator, profiles[i].max_framerate_numerator); ASSERT_EQ(profiles_response[i].max_framerate_denominator, profiles[i].max_framerate_denominator); if (profiles_response[i].hardware_accelerated) ASSERT_EQ(PP_HARDWAREACCELERATION_ONLY, profiles[i].acceleration); else ASSERT_EQ(PP_HARDWAREACCELERATION_NONE, profiles[i].acceleration); } } } 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 // successful. 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(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 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()); // Verify that a remaining encode response from the renderer is // discarded. ResourceMessageCallParams params; uint32_t frame_id; bool force_frame; ASSERT_TRUE(CheckEncodeMsg(¶ms, &frame_id, &force_frame)); SendEncodeReply(params, frame_id); } } } // namespace proxy } // namespace ppapi