From 691cccea9cb56cdfae01aa7adfcba096c2a22c5c Mon Sep 17 00:00:00 2001 From: sergeyu Date: Wed, 9 Sep 2015 11:28:00 -0700 Subject: [PATCH] Implement dynamic software fallback in PPB_VideoDecoder API. Hardware decoder are usually limited in which types of streams they can handle. Particularly they often limit stream resolution. But the decoder API doesn't expose any of these details. As result decoding fails when a frame that's is too big is sent to the decoder. Now whenever hardware decoder returns an error PepperVideoDecoderHost tries to decode the same data using software decoder. BUG=514906 Review URL: https://codereview.chromium.org/1303183003 Cr-Commit-Position: refs/heads/master@{#347962} --- .../renderer/pepper/pepper_video_decoder_host.cc | 188 +++++++++++++++------ .../renderer/pepper/pepper_video_decoder_host.h | 37 +++- 2 files changed, 169 insertions(+), 56 deletions(-) diff --git a/content/renderer/pepper/pepper_video_decoder_host.cc b/content/renderer/pepper/pepper_video_decoder_host.cc index 2c875ee07a3f..c2b0c2830dfe 100644 --- a/content/renderer/pepper/pepper_video_decoder_host.cc +++ b/content/renderer/pepper/pepper_video_decoder_host.cc @@ -71,25 +71,24 @@ media::VideoCodecProfile PepperToMediaVideoProfile(PP_VideoProfile profile) { } // namespace PepperVideoDecoderHost::PendingDecode::PendingDecode( + int32_t decode_id, uint32_t shm_id, + uint32_t size, const ppapi::host::ReplyMessageContext& reply_context) - : shm_id(shm_id), reply_context(reply_context) { -} + : decode_id(decode_id), + shm_id(shm_id), + size(size), + reply_context(reply_context) {} -PepperVideoDecoderHost::PendingDecode::~PendingDecode() { -} +PepperVideoDecoderHost::PendingDecode::~PendingDecode() {} PepperVideoDecoderHost::PepperVideoDecoderHost(RendererPpapiHost* host, PP_Instance instance, PP_Resource resource) : ResourceHost(host->GetPpapiHost(), instance, resource), - renderer_ppapi_host_(host), - min_picture_count_(0), - initialized_(false) { -} + renderer_ppapi_host_(host) {} -PepperVideoDecoderHost::~PepperVideoDecoderHost() { -} +PepperVideoDecoderHost::~PepperVideoDecoderHost() {} int32_t PepperVideoDecoderHost::OnResourceMessageReceived( const IPC::Message& msg, @@ -135,7 +134,8 @@ int32_t PepperVideoDecoderHost::OnHostMsgInitialize( if (!command_buffer) return PP_ERROR_FAILED; - media::VideoCodecProfile media_profile = PepperToMediaVideoProfile(profile); + profile_ = PepperToMediaVideoProfile(profile); + software_fallback_allowed_ = (acceleration != PP_HARDWAREACCELERATION_ONLY); // Check for Dev API use // TODO(lpique): remove check when PPB_VideoDecoder_1_1 reaches beta/stable. @@ -152,7 +152,7 @@ int32_t PepperVideoDecoderHost::OnHostMsgInitialize( // This is not synchronous, but subsequent IPC messages will be buffered, so // it is okay to immediately send IPC messages. decoder_ = command_buffer->CreateVideoDecoder(); - if (decoder_ && decoder_->Initialize(media_profile, this)) { + if (decoder_ && decoder_->Initialize(profile_, this)) { initialized_ = true; return PP_OK; } @@ -164,11 +164,7 @@ int32_t PepperVideoDecoderHost::OnHostMsgInitialize( #if defined(OS_ANDROID) return PP_ERROR_NOTSUPPORTED; #else - uint32_t shim_texture_pool_size = media::limits::kMaxVideoFrames + 1; - shim_texture_pool_size = std::max(shim_texture_pool_size, - min_picture_count_); - decoder_.reset(new VideoDecoderShim(this, shim_texture_pool_size)); - if (!decoder_->Initialize(media_profile, this)) + if (!TryFallbackToSoftwareDecoder()) return PP_ERROR_FAILED; initialized_ = true; @@ -243,14 +239,14 @@ int32_t PepperVideoDecoderHost::OnHostMsgDecode( if (shm_buffer_busy_[shm_id]) return PP_ERROR_FAILED; // Reject non-unique decode_id values. - if (pending_decodes_.find(decode_id) != pending_decodes_.end()) + if (GetPendingDecodeById(decode_id) != pending_decodes_.end()) return PP_ERROR_FAILED; if (flush_reply_context_.is_valid() || reset_reply_context_.is_valid()) return PP_ERROR_FAILED; - pending_decodes_.insert(std::make_pair( - decode_id, PendingDecode(shm_id, context->MakeReplyMessageContext()))); + pending_decodes_.push_back(PendingDecode(decode_id, shm_id, size, + context->MakeReplyMessageContext())); shm_buffer_busy_[shm_id] = true; decoder_->Decode( @@ -267,8 +263,23 @@ int32_t PepperVideoDecoderHost::OnHostMsgAssignTextures( return PP_ERROR_FAILED; DCHECK(decoder_); + // Verify that the new texture IDs are unique and store them in + // |new_textures|. + PictureBufferMap new_textures; + for (uint32_t i = 0; i < texture_ids.size(); i++) { + if (picture_buffer_map_.find(texture_ids[i]) != picture_buffer_map_.end() || + new_textures.find(texture_ids[i]) != new_textures.end()) { + // Can't assign the same texture more than once. + return PP_ERROR_BADARGUMENT; + } + new_textures.insert( + std::make_pair(texture_ids[i], PictureBufferState::ASSIGNED)); + } + + picture_buffer_map_.insert(new_textures.begin(), new_textures.end()); + std::vector picture_buffers; - for (uint32 i = 0; i < texture_ids.size(); i++) { + for (uint32_t i = 0; i < texture_ids.size(); i++) { media::PictureBuffer buffer( texture_ids[i], // Use the texture_id to identify the buffer. gfx::Size(size.width, size.height), @@ -286,22 +297,26 @@ int32_t PepperVideoDecoderHost::OnHostMsgRecyclePicture( return PP_ERROR_FAILED; DCHECK(decoder_); - TextureSet::iterator it = pictures_in_use_.find(texture_id); - if (it == pictures_in_use_.end()) + PictureBufferMap::iterator it = picture_buffer_map_.find(texture_id); + if (it == picture_buffer_map_.end()) return PP_ERROR_BADARGUMENT; - pictures_in_use_.erase(it); + switch (it->second) { + case PictureBufferState::ASSIGNED: + return PP_ERROR_BADARGUMENT; - TextureSet::iterator dismissed_texture = - dismissed_pictures_in_use_.find(texture_id); - if (dismissed_texture != dismissed_pictures_in_use_.end()) { - // The texture was already dismissed by the decoder. Notify the plugin. - host()->SendUnsolicitedReply( - pp_resource(), - PpapiPluginMsg_VideoDecoder_DismissPicture(texture_id)); - dismissed_pictures_in_use_.erase(dismissed_texture); - } else { - decoder_->ReusePictureBuffer(texture_id); + case PictureBufferState::IN_USE: + it->second = PictureBufferState::ASSIGNED; + decoder_->ReusePictureBuffer(texture_id); + break; + + case PictureBufferState::DISMISSED: + picture_buffer_map_.erase(it); + // The texture was already dismissed by the decoder. Notify the plugin. + host()->SendUnsolicitedReply( + pp_resource(), + PpapiPluginMsg_VideoDecoder_DismissPicture(texture_id)); + break; } return PP_OK; @@ -346,12 +361,14 @@ void PepperVideoDecoderHost::ProvidePictureBuffers( } void PepperVideoDecoderHost::PictureReady(const media::Picture& picture) { + PictureBufferMap::iterator it = + picture_buffer_map_.find(picture.picture_buffer_id()); + DCHECK(it != picture_buffer_map_.end()); + DCHECK(it->second == PictureBufferState::ASSIGNED); + it->second = PictureBufferState::IN_USE; + // Don't bother validating the visible rect, since the plugin process is less // trusted than the gpu process. - DCHECK(pictures_in_use_.find(picture.picture_buffer_id()) == - pictures_in_use_.end()); - pictures_in_use_.insert(picture.picture_buffer_id()); - PP_Rect visible_rect = PP_FromGfxRect(picture.visible_rect()); host()->SendUnsolicitedReply(pp_resource(), PpapiPluginMsg_VideoDecoder_PictureReady( @@ -360,13 +377,18 @@ void PepperVideoDecoderHost::PictureReady(const media::Picture& picture) { } void PepperVideoDecoderHost::DismissPictureBuffer(int32 picture_buffer_id) { + PictureBufferMap::iterator it = picture_buffer_map_.find(picture_buffer_id); + DCHECK(it != picture_buffer_map_.end()); + // If the texture is still used by the plugin keep it until the plugin // recycles it. - if (pictures_in_use_.find(picture_buffer_id) != pictures_in_use_.end()) { - dismissed_pictures_in_use_.insert(picture_buffer_id); + if (it->second == PictureBufferState::IN_USE) { + it->second = PictureBufferState::DISMISSED; return; } + DCHECK(it->second == PictureBufferState::ASSIGNED); + picture_buffer_map_.erase(it); host()->SendUnsolicitedReply( pp_resource(), PpapiPluginMsg_VideoDecoder_DismissPicture(picture_buffer_id)); @@ -374,16 +396,14 @@ void PepperVideoDecoderHost::DismissPictureBuffer(int32 picture_buffer_id) { void PepperVideoDecoderHost::NotifyEndOfBitstreamBuffer( int32 bitstream_buffer_id) { - PendingDecodeMap::iterator it = pending_decodes_.find(bitstream_buffer_id); + PendingDecodeList::iterator it = GetPendingDecodeById(bitstream_buffer_id); if (it == pending_decodes_.end()) { NOTREACHED(); return; } - const PendingDecode& pending_decode = it->second; - host()->SendReply( - pending_decode.reply_context, - PpapiPluginMsg_VideoDecoder_DecodeReply(pending_decode.shm_id)); - shm_buffer_busy_[pending_decode.shm_id] = false; + host()->SendReply(it->reply_context, + PpapiPluginMsg_VideoDecoder_DecodeReply(it->shm_id)); + shm_buffer_busy_[it->shm_id] = false; pending_decodes_.erase(it); } @@ -416,14 +436,23 @@ void PepperVideoDecoderHost::NotifyError( break; // No default case, to catch unhandled enum values. } + + // Try to initialize software decoder and use it instead. + if (!software_fallback_used_ && software_fallback_allowed_) { + VLOG(0) + << "Hardware decoder has returned an error. Trying Software decoder."; + if (TryFallbackToSoftwareDecoder()) + return; + } + host()->SendUnsolicitedReply( pp_resource(), PpapiPluginMsg_VideoDecoder_NotifyError(pp_error)); } const uint8_t* PepperVideoDecoderHost::DecodeIdToAddress(uint32_t decode_id) { - PendingDecodeMap::const_iterator it = pending_decodes_.find(decode_id); + PendingDecodeList::const_iterator it = GetPendingDecodeById(decode_id); DCHECK(it != pending_decodes_.end()); - uint32_t shm_id = it->second.shm_id; + uint32_t shm_id = it->shm_id; return static_cast(shm_buffers_[shm_id]->memory()); } @@ -441,4 +470,67 @@ void PepperVideoDecoderHost::RequestTextures( mailboxes)); } +bool PepperVideoDecoderHost::TryFallbackToSoftwareDecoder() { + DCHECK(!software_fallback_used_ && software_fallback_allowed_); + + uint32_t shim_texture_pool_size = media::limits::kMaxVideoFrames + 1; + shim_texture_pool_size = std::max(shim_texture_pool_size, + min_picture_count_); + scoped_ptr new_decoder( + new VideoDecoderShim(this, shim_texture_pool_size)); + if (!new_decoder->Initialize(profile_, this)) + return false; + + software_fallback_used_ = true; + decoder_.reset(new_decoder.release()); + + // Dismiss all assigned pictures and mark all pictures in use as DISMISSED. + PictureBufferMap pictures_pending_dismission; + for (auto& picture : picture_buffer_map_) { + if (picture.second == PictureBufferState::ASSIGNED) { + host()->SendUnsolicitedReply( + pp_resource(), + PpapiPluginMsg_VideoDecoder_DismissPicture(picture.first)); + } else { + pictures_pending_dismission.insert( + std::make_pair(picture.first, PictureBufferState::DISMISSED)); + } + } + picture_buffer_map_.swap(pictures_pending_dismission); + + // If there was a pending Reset() it can be finished now. + if (reset_reply_context_.is_valid()) { + while (!pending_decodes_.empty()) { + const PendingDecode& decode = pending_decodes_.front(); + host()->SendReply(decode.reply_context, + PpapiPluginMsg_VideoDecoder_DecodeReply(decode.shm_id)); + DCHECK(shm_buffer_busy_[decode.shm_id]); + shm_buffer_busy_[decode.shm_id] = false; + pending_decodes_.pop_front(); + } + NotifyResetDone(); + } + + // Resubmit all pending decodes. + for (const PendingDecode& decode : pending_decodes_) { + DCHECK(shm_buffer_busy_[decode.shm_id]); + decoder_->Decode(media::BitstreamBuffer( + decode.decode_id, shm_buffers_[decode.shm_id]->handle(), decode.size)); + } + + // Flush the new decoder if Flush() was pending. + if (flush_reply_context_.is_valid()) + decoder_->Flush(); + + return true; +} + +PepperVideoDecoderHost::PendingDecodeList::iterator +PepperVideoDecoderHost::GetPendingDecodeById(int32_t decode_id) { + return std::find_if(pending_decodes_.begin(), pending_decodes_.end(), + [decode_id](const PendingDecode& item) { + return item.decode_id == decode_id; + }); +} + } // namespace content diff --git a/content/renderer/pepper/pepper_video_decoder_host.h b/content/renderer/pepper/pepper_video_decoder_host.h index b0b6ca210e86..b82a0cc4ed90 100644 --- a/content/renderer/pepper/pepper_video_decoder_host.h +++ b/content/renderer/pepper/pepper_video_decoder_host.h @@ -5,7 +5,9 @@ #ifndef CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_DECODER_HOST_H_ #define CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_DECODER_HOST_H_ +#include #include +#include #include #include "base/basictypes.h" @@ -40,14 +42,25 @@ class CONTENT_EXPORT PepperVideoDecoderHost ~PepperVideoDecoderHost() override; private: + enum class PictureBufferState { + ASSIGNED, + IN_USE, + DISMISSED, + }; + struct PendingDecode { - PendingDecode(uint32_t shm_id, + PendingDecode(int32_t decode_id, + uint32_t shm_id, + uint32_t size, const ppapi::host::ReplyMessageContext& reply_context); ~PendingDecode(); + const int32_t decode_id; const uint32_t shm_id; + const uint32_t size; const ppapi::host::ReplyMessageContext reply_context; }; + typedef std::list PendingDecodeList; friend class VideoDecoderShim; @@ -95,11 +108,21 @@ class CONTENT_EXPORT PepperVideoDecoderHost uint32 texture_target, const std::vector& mailboxes); + // Tries to initialize software decoder. Returns true on success. + bool TryFallbackToSoftwareDecoder(); + + PendingDecodeList::iterator GetPendingDecodeById(int32_t decode_id); + // Non-owning pointer. RendererPpapiHost* renderer_ppapi_host_; + media::VideoCodecProfile profile_; + scoped_ptr decoder_; + bool software_fallback_allowed_ = false; + bool software_fallback_used_ = false; + // A vector holding our shm buffers, in sync with a similar vector in the // resource. We use a buffer's index in these vectors as its id on both sides // of the proxy. Only add buffers or update them in place so as not to @@ -110,18 +133,16 @@ class CONTENT_EXPORT PepperVideoDecoderHost std::vector shm_buffer_busy_; uint32_t min_picture_count_; - typedef std::set TextureSet; - TextureSet pictures_in_use_; - TextureSet dismissed_pictures_in_use_; + typedef std::map PictureBufferMap; + PictureBufferMap picture_buffer_map_; - // Maps decode uid to PendingDecode info. - typedef base::hash_map PendingDecodeMap; - PendingDecodeMap pending_decodes_; + // Keeps list of pending decodes. + PendingDecodeList pending_decodes_; ppapi::host::ReplyMessageContext flush_reply_context_; ppapi::host::ReplyMessageContext reset_reply_context_; - bool initialized_; + bool initialized_ = false; DISALLOW_COPY_AND_ASSIGN(PepperVideoDecoderHost); }; -- 2.11.4.GIT