| // 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 <memory> |
| #include <utility> |
| |
| #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_frame_resource.h" |
| #include "ppapi/shared_impl/array_writer.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 { |
| |
| std::vector<PP_VideoProfileDescription_0_1> PP_VideoProfileDescriptionTo_0_1( |
| std::vector<PP_VideoProfileDescription> profiles) { |
| std::vector<PP_VideoProfileDescription_0_1> profiles_0_1; |
| |
| for (uint32_t i = 0; i < profiles.size(); ++i) { |
| const PP_VideoProfileDescription& profile = profiles[i]; |
| PP_VideoProfileDescription_0_1 profile_0_1; |
| |
| profile_0_1.profile = profile.profile; |
| profile_0_1.max_resolution = profile.max_resolution; |
| profile_0_1.max_framerate_numerator = profile.max_framerate_numerator; |
| profile_0_1.max_framerate_denominator = profile.max_framerate_denominator; |
| profile_0_1.acceleration = profile.hardware_accelerated == PP_TRUE |
| ? PP_HARDWAREACCELERATION_ONLY |
| : PP_HARDWAREACCELERATION_NONE; |
| |
| profiles_0_1.push_back(profile_0_1); |
| } |
| |
| return profiles_0_1; |
| } |
| |
| } // namespace |
| |
| VideoEncoderResource::ShmBuffer::ShmBuffer( |
| uint32_t id, |
| std::unique_ptr<base::SharedMemory> shm) |
| : id(id), shm(std::move(shm)) {} |
| |
| 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), |
| 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() { |
| return this; |
| } |
| |
| int32_t VideoEncoderResource::GetSupportedProfiles( |
| const PP_ArrayOutput& output, |
| const scoped_refptr<TrackedCallback>& callback) { |
| 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, false)); |
| return PP_OK_COMPLETIONPENDING; |
| } |
| |
| int32_t VideoEncoderResource::GetSupportedProfiles0_1( |
| const PP_ArrayOutput& output, |
| const scoped_refptr<TrackedCallback>& callback) { |
| 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, true)); |
| 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( |
| PP_VideoFrame_Format input_format, |
| const PP_Size* input_visible_size, |
| PP_VideoProfile output_profile, |
| uint32_t initial_bitrate, |
| PP_HardwareAcceleration acceleration, |
| const scoped_refptr<TrackedCallback>& callback) { |
| if (initialized_) |
| return PP_ERROR_FAILED; |
| if (TrackedCallback::IsPending(initialize_callback_)) |
| return PP_ERROR_INPROGRESS; |
| |
| 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) { |
| 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) { |
| 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* bitstream_buffer, |
| const scoped_refptr<TrackedCallback>& callback) { |
| 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* 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, |
| bool version0_1, |
| const ResourceMessageReplyParams& params, |
| const std::vector<PP_VideoProfileDescription>& profiles) { |
| int32_t error = params.result(); |
| if (error) { |
| NotifyError(error); |
| return; |
| } |
| |
| ArrayWriter writer(output); |
| if (!writer.is_valid()) { |
| SafeRunCallback(&get_supported_profiles_callback_, PP_ERROR_BADARGUMENT); |
| return; |
| } |
| |
| bool write_result; |
| if (version0_1) |
| write_result = |
| writer.StoreVector(PP_VideoProfileDescriptionTo_0_1(profiles)); |
| else |
| write_result = writer.StoreVector(profiles); |
| |
| if (!write_result) { |
| SafeRunCallback(&get_supported_profiles_callback_, PP_ERROR_FAILED); |
| return; |
| } |
| |
| SafeRunCallback(&get_supported_profiles_callback_, |
| base::checked_cast<int32_t>(profiles.size())); |
| } |
| |
| 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; |
| |
| SafeRunCallback(&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, |
| std::make_unique<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) { |
| // We need to ensure there are still callbacks to be called before |
| // processing this message. We might receive a EncodeReply message |
| // after having sent a Close message to the renderer. In this case, |
| // we don't have any callback left to call. |
| if (encode_callbacks_.empty()) |
| return; |
| 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); |
| SafeRunCallback(&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) { |
| std::unique_ptr<base::SharedMemory> shm( |
| new base::SharedMemory(shm_handles[i], true)); |
| CHECK(shm->Map(buffer_length)); |
| |
| auto buffer = std::make_unique<ShmBuffer>(i, std::move(shm)); |
| bitstream_buffer_map_.insert( |
| std::make_pair(buffer->shm->memory(), buffer->id)); |
| shm_buffers_.push_back(std::move(buffer)); |
| } |
| } |
| |
| 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; |
| SafeRunCallback(&get_supported_profiles_callback_, error); |
| SafeRunCallback(&initialize_callback_, error); |
| SafeRunCallback(&get_video_frame_callback_, error); |
| get_video_frame_data_ = nullptr; |
| SafeRunCallback(&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; |
| SafeRunCallback(&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; |
| SafeRunCallback(&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; |
| SafeRunCallback(&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 |
| } // namespace ppapi |