chromium/content/renderer/pepper/pepper_video_decoder_host.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/pepper/pepper_video_decoder_host.h"

#include <stddef.h>

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/metrics/histogram_functions.h"
#include "base/not_fatal_until.h"
#include "base/ranges/algorithm.h"
#include "build/build_config.h"
#include "content/common/pepper_file_util.h"
#include "content/public/common/content_client.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/public/renderer/ppapi_gfx_conversion.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/renderer_ppapi_host.h"
#include "content/renderer/pepper/ppb_graphics_3d_impl.h"
#include "content/renderer/pepper/video_decoder_shim.h"
#include "content/renderer/render_thread_impl.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/ipc/client/command_buffer_proxy_impl.h"
#include "media/base/limits.h"
#include "media/base/media_util.h"
#include "media/video/video_decode_accelerator.h"
#include "ppapi/c/pp_completion_callback.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/video_decoder_constants.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/ppb_graphics_3d_api.h"

using ppapi::proxy::SerializedHandle;
using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_Graphics3D_API;

namespace content {

namespace {

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class HardwareAccelerationBehavior : int {
  kOther = 0,

  // The PepperVideoDecoderHost used a hardware decoder backed by the legacy
  // VideoDecodeAccelerator path from beginning to end.
  kHardwareDecoderOnlyWithoutMojoVideoDecoder = 1,

  // The PepperVideoDecoderHost initialized (and possibly started using) a
  // hardware decoder backed by the legacy VideoDecodeAccelerator path but then
  // fell back to software decoding.
  kHardwareDecoderWithoutMojoVideoDecoderAndThenSoftwareDecoder = 2,

  // The PepperVideoDecoderHost used a hardware decoder backed by the newer
  // MojoVideoDecoder path from beginning to end.
  kHardwareDecoderOnlyWithMojoVideoDecoder = 3,

  // The PepperVideoDecoderHost initialized (and possibly started using) a
  // hardware decoder backed by the newer MojoVideoDecoder path but then fell
  // back to software decoding.
  kHardwareDecoderWithMojoVideoDecoderAndThenSoftwareDecoder = 4,

  // The PepperVideoDecoderHost used a software video decoder from beginning to
  // end.
  kSoftwareDecoderOnly = 5,

  kMaxValue = kSoftwareDecoderOnly
};

media::VideoCodecProfile PepperToMediaVideoProfile(PP_VideoProfile profile) {
  switch (profile) {
    case PP_VIDEOPROFILE_H264BASELINE:
      return media::H264PROFILE_BASELINE;
    case PP_VIDEOPROFILE_H264MAIN:
      return media::H264PROFILE_MAIN;
    case PP_VIDEOPROFILE_H264EXTENDED:
      return media::H264PROFILE_EXTENDED;
    case PP_VIDEOPROFILE_H264HIGH:
      return media::H264PROFILE_HIGH;
    case PP_VIDEOPROFILE_H264HIGH10PROFILE:
      return media::H264PROFILE_HIGH10PROFILE;
    case PP_VIDEOPROFILE_H264HIGH422PROFILE:
      return media::H264PROFILE_HIGH422PROFILE;
    case PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE:
      return media::H264PROFILE_HIGH444PREDICTIVEPROFILE;
    case PP_VIDEOPROFILE_H264SCALABLEBASELINE:
      return media::H264PROFILE_SCALABLEBASELINE;
    case PP_VIDEOPROFILE_H264SCALABLEHIGH:
      return media::H264PROFILE_SCALABLEHIGH;
    case PP_VIDEOPROFILE_H264STEREOHIGH:
      return media::H264PROFILE_STEREOHIGH;
    case PP_VIDEOPROFILE_H264MULTIVIEWHIGH:
      return media::H264PROFILE_MULTIVIEWHIGH;
    case PP_VIDEOPROFILE_VP8_ANY:
      return media::VP8PROFILE_ANY;
    case PP_VIDEOPROFILE_VP9_ANY:
      return media::VP9PROFILE_PROFILE0;
    // No default case, to catch unhandled PP_VideoProfile values.
  }

  return media::VIDEO_CODEC_PROFILE_UNKNOWN;
}

}  // namespace

PepperVideoDecoderHost::PendingDecode::PendingDecode(
    int32_t decode_id,
    uint32_t shm_id,
    uint32_t size,
    const ppapi::host::ReplyMessageContext& reply_context)
    : decode_id(decode_id),
      shm_id(shm_id),
      size(size),
      reply_context(reply_context) {}

PepperVideoDecoderHost::PendingDecode::~PendingDecode() {}

PepperVideoDecoderHost::MappedBuffer::MappedBuffer(
    base::UnsafeSharedMemoryRegion region,
    base::WritableSharedMemoryMapping mapping)
    : region(std::move(region)), mapping(std::move(mapping)) {}

PepperVideoDecoderHost::MappedBuffer::~MappedBuffer() {}

PepperVideoDecoderHost::MappedBuffer::MappedBuffer(MappedBuffer&&) = default;
PepperVideoDecoderHost::MappedBuffer& PepperVideoDecoderHost::MappedBuffer::
operator=(MappedBuffer&&) = default;

PepperVideoDecoderHost::SharedImage::SharedImage(
    gfx::Size size,
    PictureBufferState state,
    scoped_refptr<gpu::ClientSharedImage> client_shared_image)
    : size(size),
      state(state),
      client_shared_image(std::move(client_shared_image)) {}

PepperVideoDecoderHost::SharedImage::SharedImage(
    const SharedImage& shared_image) = default;

PepperVideoDecoderHost::SharedImage::~SharedImage() = default;

PepperVideoDecoderHost::PepperVideoDecoderHost(RendererPpapiHost* host,
                                               PP_Instance instance,
                                               PP_Resource resource)
    : ResourceHost(host->GetPpapiHost(), instance, resource),
      renderer_ppapi_host_(host) {}

PepperVideoDecoderHost::~PepperVideoDecoderHost() {
  if (decoder_) {
    scoped_refptr<viz::RasterContextProvider> context_provider =
        decoder_->context_provider();
    // Destroy `decoder_`, so it will destroy all available shared images.
    decoder_->Destroy();

    // If video decoder was destroyed before plugin returned all shared images,
    // this is our last chance to destroy them.
    if (!shared_images_.empty()) {
      CHECK(context_provider);
      // Plugin's GLES2Interface and Renderer's RasterInterface are synchronized
      // by issued `ShallowFlushCHROMIUM` after each work. To synchronize with
      // SharedImageInterface we generate sync token here.
      gpu::SyncToken sync_token;
      context_provider->RasterInterface()->GenUnverifiedSyncTokenCHROMIUM(
          sync_token.GetData());

      auto* sii = context_provider->SharedImageInterface();

      for (auto& shared_image : shared_images_) {
        // All assigned textures should have been destroyed by `decoder_`
        CHECK_NE(shared_image.second.state, PictureBufferState::ASSIGNED);
        sii->DestroySharedImage(
            sync_token, std::move(shared_image.second.client_shared_image));
      }
    }
  }

  auto hw_behavior = HardwareAccelerationBehavior::kOther;
  if (software_fallback_used_) {
    if (mojo_video_decoder_path_initialized_) {
      hw_behavior = HardwareAccelerationBehavior::
          kHardwareDecoderWithMojoVideoDecoderAndThenSoftwareDecoder;
    } else {
      hw_behavior = HardwareAccelerationBehavior::kSoftwareDecoderOnly;
    }
  } else if (mojo_video_decoder_path_initialized_) {
    hw_behavior =
        HardwareAccelerationBehavior::kHardwareDecoderOnlyWithMojoVideoDecoder;
  }

  base::UmaHistogramEnumeration(
      "Media.PepperVideoDecoder.HardwareAccelerationBehavior", hw_behavior);
}

int32_t PepperVideoDecoderHost::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context) {
  PPAPI_BEGIN_MESSAGE_MAP(PepperVideoDecoderHost, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_Initialize,
                                      OnHostMsgInitialize)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_GetShm,
                                      OnHostMsgGetShm)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoDecoder_Decode,
                                      OnHostMsgDecode)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
        PpapiHostMsg_VideoDecoder_RecycleSharedImage,
        OnHostMsgRecycleSharedImage)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoDecoder_Flush,
                                        OnHostMsgFlush)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoDecoder_Reset,
                                        OnHostMsgReset)
  PPAPI_END_MESSAGE_MAP()
  return PP_ERROR_FAILED;
}

int32_t PepperVideoDecoderHost::OnHostMsgInitialize(
    ppapi::host::HostMessageContext* context,
    const ppapi::HostResource& graphics_context,
    PP_VideoProfile profile,
    PP_HardwareAcceleration acceleration,
    uint32_t min_picture_count) {
  if (initialized_)
    return PP_ERROR_FAILED;
  if (min_picture_count > ppapi::proxy::kMaximumPictureCount)
    return PP_ERROR_BADARGUMENT;

  EnterResourceNoLock<PPB_Graphics3D_API> enter_graphics(
      graphics_context.host_resource(), true);
  if (enter_graphics.failed())
    return PP_ERROR_FAILED;
  PPB_Graphics3D_Impl* graphics3d =
      static_cast<PPB_Graphics3D_Impl*>(enter_graphics.object());

  gpu::CommandBufferProxyImpl* command_buffer =
      graphics3d->GetCommandBufferProxy();
  if (!command_buffer)
    return PP_ERROR_FAILED;

  profile_ = PepperToMediaVideoProfile(profile);
  software_fallback_allowed_ = (acceleration != PP_HARDWAREACCELERATION_ONLY);

  min_picture_count_ = min_picture_count;

  if (acceleration != PP_HARDWAREACCELERATION_NONE) {
    uint32_t shim_texture_pool_size = media::limits::kMaxVideoFrames + 1;
    shim_texture_pool_size =
        std::max(shim_texture_pool_size, min_picture_count_);
    auto new_decoder = VideoDecoderShim::Create(this, shim_texture_pool_size,
                                                /*use_hw_decoder=*/true);
    if (new_decoder && new_decoder->Initialize(profile_)) {
      decoder_ = std::move(new_decoder);
      initialized_ = true;
      mojo_video_decoder_path_initialized_ = true;
      return PP_OK;
    }

    decoder_.reset();
    if (acceleration == PP_HARDWAREACCELERATION_ONLY)
      return PP_ERROR_NOTSUPPORTED;
  }

#if BUILDFLAG(IS_ANDROID)
  return PP_ERROR_NOTSUPPORTED;
#else
  if (!TryFallbackToSoftwareDecoder())
    return PP_ERROR_FAILED;

  initialized_ = true;
  return PP_OK;
#endif
}

int32_t PepperVideoDecoderHost::OnHostMsgGetShm(
    ppapi::host::HostMessageContext* context,
    uint32_t shm_id,
    uint32_t shm_size) {
  if (!initialized_)
    return PP_ERROR_FAILED;

  // Make the buffers larger since we hope to reuse them.
  shm_size = std::max(
      shm_size,
      static_cast<uint32_t>(ppapi::proxy::kMinimumBitstreamBufferSize));
  if (shm_size > ppapi::proxy::kMaximumBitstreamBufferSize)
    return PP_ERROR_FAILED;

  if (shm_id >= ppapi::proxy::kMaximumPendingDecodes)
    return PP_ERROR_FAILED;
  // The shm_id must be inside or at the end of shm_buffers_.
  if (shm_id > shm_buffers_.size())
    return PP_ERROR_FAILED;
  // Reject an attempt to reallocate a busy shm buffer.
  if (shm_id < shm_buffers_.size() && shm_buffers_[shm_id].busy)
    return PP_ERROR_FAILED;

  auto shm = base::UnsafeSharedMemoryRegion::Create(shm_size);
  auto mapping = shm.Map();
  if (!shm.IsValid() || !mapping.IsValid())
    return PP_ERROR_FAILED;

  SerializedHandle handle(
      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
          renderer_ppapi_host_->ShareUnsafeSharedMemoryRegionWithRemote(shm)));
  if (shm_id == shm_buffers_.size()) {
    shm_buffers_.emplace_back(std::move(shm), std::move(mapping));
  } else {
    // Note by the check above this buffer cannot be busy.
    shm_buffers_[shm_id] = MappedBuffer(std::move(shm), std::move(mapping));
  }

  ppapi::host::ReplyMessageContext reply_context =
      context->MakeReplyMessageContext();
  reply_context.params.AppendHandle(std::move(handle));
  host()->SendReply(reply_context,
                    PpapiPluginMsg_VideoDecoder_GetShmReply(shm_size));

  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperVideoDecoderHost::OnHostMsgDecode(
    ppapi::host::HostMessageContext* context,
    uint32_t shm_id,
    uint32_t size,
    int32_t decode_id) {
  if (!initialized_)
    return PP_ERROR_FAILED;
  DCHECK(decoder_);
  // |shm_id| is just an index into shm_buffers_. Make sure it's in range.
  if (static_cast<size_t>(shm_id) >= shm_buffers_.size())
    return PP_ERROR_FAILED;
  // Reject an attempt to pass a busy buffer to the decoder again.
  if (shm_buffers_[shm_id].busy)
    return PP_ERROR_FAILED;
  // Reject non-unique decode_id values.
  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_.push_back(PendingDecode(decode_id, shm_id, size,
                                           context->MakeReplyMessageContext()));

  shm_buffers_[shm_id].busy = true;
  decoder_->Decode(media::BitstreamBuffer(
      decode_id, shm_buffers_[shm_id].region.Duplicate(), size));

  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperVideoDecoderHost::OnHostMsgRecycleSharedImage(
    ppapi::host::HostMessageContext* context,
    const gpu::Mailbox& mailbox) {
  if (!initialized_) {
    return PP_ERROR_FAILED;
  }

  DCHECK(decoder_);

  auto it = shared_images_.find(mailbox);
  if (it == shared_images_.end()) {
    return PP_ERROR_BADARGUMENT;
  }

  switch (it->second.state) {
    case PictureBufferState::ASSIGNED:
      return PP_ERROR_BADARGUMENT;

    case PictureBufferState::IN_USE:
      it->second.state = PictureBufferState::ASSIGNED;
      decoder_->ReuseSharedImage(mailbox, it->second.size);
      break;

    case PictureBufferState::DISMISSED:
      DestroySharedImageInternal(it);
      break;
  }

  return PP_OK;
}

int32_t PepperVideoDecoderHost::OnHostMsgFlush(
    ppapi::host::HostMessageContext* context) {
  if (!initialized_)
    return PP_ERROR_FAILED;
  DCHECK(decoder_);
  if (flush_reply_context_.is_valid() || reset_reply_context_.is_valid())
    return PP_ERROR_FAILED;

  flush_reply_context_ = context->MakeReplyMessageContext();
  decoder_->Flush();

  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperVideoDecoderHost::OnHostMsgReset(
    ppapi::host::HostMessageContext* context) {
  if (!initialized_)
    return PP_ERROR_FAILED;
  DCHECK(decoder_);
  if (flush_reply_context_.is_valid() || reset_reply_context_.is_valid())
    return PP_ERROR_FAILED;

  reset_reply_context_ = context->MakeReplyMessageContext();
  decoder_->Reset();

  return PP_OK_COMPLETIONPENDING;
}

gpu::Mailbox PepperVideoDecoderHost::CreateSharedImage(gfx::Size size) {
  CHECK(decoder_);
  const auto& context_provider = decoder_->context_provider();
  CHECK(context_provider);

  auto* sii = context_provider->SharedImageInterface();
  auto* rii = context_provider->RasterInterface();

  // These shared images have the contents of VideoFrames copied into them via
  // the raster interface and then are read and/or written by the plugin via GL.
  auto client_shared_image = sii->CreateSharedImage(
      {viz::SinglePlaneFormat::kRGBA_8888, size, gfx::ColorSpace(),
       kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType,
       gpu::SHARED_IMAGE_USAGE_GLES2_READ |
           gpu::SHARED_IMAGE_USAGE_GLES2_WRITE |
           gpu::SHARED_IMAGE_USAGE_RASTER_WRITE,
       "PepperVideoDecoder"},
      gpu::SurfaceHandle());
  CHECK(client_shared_image);
  auto mailbox = client_shared_image->mailbox();

  // This SI will be used on raster interface later, to avoid plumbing
  // SyncTokens just for creation wait on it here.
  rii->WaitSyncTokenCHROMIUM(sii->GenUnverifiedSyncToken().GetConstData());

  shared_images_.emplace(mailbox,
                         SharedImage{size, PictureBufferState::ASSIGNED,
                                     std::move(client_shared_image)});
  return mailbox;
}

void PepperVideoDecoderHost::DestroySharedImage(const gpu::Mailbox& mailbox) {
  auto it = shared_images_.find(mailbox);
  CHECK(it != shared_images_.end());

  // VideoDecoderShim tracks only assigned images.
  CHECK_EQ(it->second.state, PictureBufferState::ASSIGNED);
  DestroySharedImageInternal(it);
}

void PepperVideoDecoderHost::DestroySharedImageInternal(
    std::map<gpu::Mailbox, SharedImage>::iterator it) {
  CHECK(decoder_);
  const auto& context_provider = decoder_->context_provider();
  CHECK(context_provider);

  // Plugin's GLES2Interface and Renderer's RasterInterface are synchronized by
  // issued `ShallowFlushCHROMIUM` after each work. To synchronize with
  // SharedImageInterface we generate sync token here.
  gpu::SyncToken sync_token;
  context_provider->RasterInterface()->GenUnverifiedSyncTokenCHROMIUM(
      sync_token.GetData());

  auto* sii = context_provider->SharedImageInterface();
  sii->DestroySharedImage(sync_token,
                          std::move(it->second.client_shared_image));
  shared_images_.erase(it);
}

void PepperVideoDecoderHost::SharedImageReady(int32_t bitstream_id,
                                              const gpu::Mailbox& mailbox,
                                              gfx::Size size,
                                              const gfx::Rect& visible_rect) {
  auto it = shared_images_.find(mailbox);
  CHECK(it != shared_images_.end());
  CHECK_EQ(it->second.state, PictureBufferState::ASSIGNED);
  it->second.state = PictureBufferState::IN_USE;

  host()->SendUnsolicitedReply(pp_resource(),
                               PpapiPluginMsg_VideoDecoder_SharedImageReady(
                                   bitstream_id, mailbox, PP_FromGfxSize(size),
                                   PP_FromGfxRect(visible_rect)));
}

void PepperVideoDecoderHost::NotifyEndOfBitstreamBuffer(
    int32_t bitstream_buffer_id) {
  auto it = GetPendingDecodeById(bitstream_buffer_id);
  if (it == pending_decodes_.end()) {
    NOTREACHED_IN_MIGRATION();
    return;
  }
  host()->SendReply(it->reply_context,
                    PpapiPluginMsg_VideoDecoder_DecodeReply(it->shm_id));
  shm_buffers_[it->shm_id].busy = false;
  pending_decodes_.erase(it);
}

void PepperVideoDecoderHost::NotifyFlushDone() {
  DCHECK(pending_decodes_.empty());
  host()->SendReply(flush_reply_context_,
                    PpapiPluginMsg_VideoDecoder_FlushReply());
  flush_reply_context_ = ppapi::host::ReplyMessageContext();
}

void PepperVideoDecoderHost::NotifyResetDone() {
  DCHECK(pending_decodes_.empty());
  host()->SendReply(reset_reply_context_,
                    PpapiPluginMsg_VideoDecoder_ResetReply());
  reset_reply_context_ = ppapi::host::ReplyMessageContext();
}

void PepperVideoDecoderHost::NotifyError(
    media::VideoDecodeAccelerator::Error error) {
  int32_t pp_error = PP_ERROR_FAILED;
  switch (error) {
    case media::VideoDecodeAccelerator::UNREADABLE_INPUT:
      pp_error = PP_ERROR_MALFORMED_INPUT;
      break;
    case media::VideoDecodeAccelerator::ILLEGAL_STATE:
    case media::VideoDecodeAccelerator::INVALID_ARGUMENT:
    case media::VideoDecodeAccelerator::PLATFORM_FAILURE:
      pp_error = PP_ERROR_RESOURCE_FAILED;
      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) {
  PendingDecodeList::const_iterator it = GetPendingDecodeById(decode_id);
  CHECK(it != pending_decodes_.end(), base::NotFatalUntil::M130);
  uint32_t shm_id = it->shm_id;
  return static_cast<uint8_t*>(shm_buffers_[shm_id].mapping.memory());
}

bool PepperVideoDecoderHost::TryFallbackToSoftwareDecoder() {
#if BUILDFLAG(IS_ANDROID)
  return false;
#else
  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_);
  std::unique_ptr<VideoDecoderShim> new_decoder(VideoDecoderShim::Create(
      this, shim_texture_pool_size, /*use_hw_decoder=*/false));
  if (!new_decoder || !new_decoder->Initialize(profile_)) {
    return false;
  }

  software_fallback_used_ = true;

  if (decoder_) {
    decoder_->Destroy();
    decoder_.reset();
  }
  decoder_ = std::move(new_decoder);

  for (auto& shared_image : shared_images_) {
    // All ASSIGNED images were deleted by decoder. And there shouldn't be any
    // DISMISSED images yet, because it's set only in this function and this
    // point can only be reached once.

    CHECK_EQ(shared_image.second.state, PictureBufferState::IN_USE);
    // Mark as dismissed and delete once plug-in returns them.
    shared_image.second.state = PictureBufferState::DISMISSED;
  }

  // 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_buffers_[decode.shm_id].busy);
      shm_buffers_[decode.shm_id].busy = false;
      pending_decodes_.pop_front();
    }
    NotifyResetDone();
  }

  // Resubmit all pending decodes.
  for (const PendingDecode& decode : pending_decodes_) {
    DCHECK(shm_buffers_[decode.shm_id].busy);
    decoder_->Decode(media::BitstreamBuffer(
        decode.decode_id, shm_buffers_[decode.shm_id].region.Duplicate(),
        decode.size));
  }

  // Flush the new decoder if Flush() was pending.
  if (flush_reply_context_.is_valid())
    decoder_->Flush();

  return true;
#endif
}

PepperVideoDecoderHost::PendingDecodeList::iterator
PepperVideoDecoderHost::GetPendingDecodeById(int32_t decode_id) {
  return base::ranges::find(pending_decodes_, decode_id,
                            &PendingDecode::decode_id);
}

}  // namespace content