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

#include "media/gpu/chromeos/video_decoder_pipeline.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "media/base/async_destroy_video_decoder.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/media_util.h"
#include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
#include "media/gpu/chromeos/frame_registry.h"
#include "media/gpu/chromeos/image_processor.h"
#include "media/gpu/chromeos/image_processor_factory.h"
#include "media/gpu/chromeos/native_pixmap_frame_resource.h"
#include "media/gpu/chromeos/oop_video_decoder.h"
#include "media/gpu/chromeos/platform_video_frame_pool.h"
#include "media/gpu/chromeos/registered_frame_converter.h"
#include "media/gpu/chromeos/video_frame_resource.h"
#include "media/gpu/macros.h"
#include "media/media_buildflags.h"

#if BUILDFLAG(USE_VAAPI)
#include <drm_fourcc.h>
#include "media/gpu/vaapi/vaapi_video_decoder.h"
#endif  // BUILDFLAG(USE_VAAPI)

#if BUILDFLAG(USE_V4L2_CODEC)
#include "media/gpu/v4l2/v4l2_stateful_video_decoder.h"
#include "media/gpu/v4l2/v4l2_video_decoder.h"
#endif  // BUILDFLAG(USE_V4L2_CODEC)

#if !(BUILDFLAG(USE_VAAPI) || BUILDFLAG(USE_V4L2_CODEC))
#error Either VA-API or V4L2 must be used for decode acceleration on Chrome OS.
#endif

namespace media {
namespace {

using PixelLayoutCandidate = ImageProcessor::PixelLayoutCandidate;

// Picks the preferred compositor renderable format from |candidates|, if any.
// If |preferred_fourcc| is provided, contained in |candidates|, and considered
// renderable, it returns that. Otherwise, it goes through
// |renderable_fourccs| until it finds one that's in |candidates|. If
// it can't find a renderable format in |candidates|, it returns std::nullopt.
std::optional<Fourcc> PickRenderableFourcc(
    const std::vector<Fourcc>& renderable_fourccs,
    const std::vector<Fourcc>& candidates,
    std::optional<Fourcc> preferred_fourcc) {
  if (preferred_fourcc &&
      std::ranges::contains(candidates, *preferred_fourcc) &&
      std::ranges::contains(renderable_fourccs, *preferred_fourcc)) {
    return preferred_fourcc;
  }
  for (const auto& value : renderable_fourccs) {
    if (std::ranges::contains(candidates, value))
      return value;
  }
  return std::nullopt;
}

// Estimates the number of buffers needed in the output frame pool to fill the
// Renderer pipeline (this pool may provide buffers to the VideoDecoder
// directly or to the ImageProcessor, when this is instantiated).
size_t EstimateRequiredRendererPipelineBuffers(bool low_delay,
                                               bool use_protected) {
  // kMaxVideoFrames is meant to be the number of VideoFrames needed to populate
  // the whole Renderer playback pipeline when there's no smoothing playback
  // queue, i.e. in low latency scenarios such as WebRTC etc. For non-low
  // latency scenarios, a large smoothing playback is used in the Renderer
  // process. Heuristically, the extra depth needed is in the range of 15 or
  // so, so we need to add a few extra buffers.
  // For V4L2 secure playback, we need to limit the pipeline depth or we will
  // run out of memory. We are going further than we are with the test because
  // the memory consequences of using more are too great and so far have yielded
  // no problems in testing.
  constexpr size_t kExpectedNonLatencyPipelineDepth = 16;
#if BUILDFLAG(USE_V4L2_CODEC)
  constexpr size_t kExpectedNonLatencyPipelineDepthSecure = 6;
#endif
  static_assert(kExpectedNonLatencyPipelineDepth > limits::kMaxVideoFrames,
                "kMaxVideoFrames is expected to be relatively small");
  constexpr size_t kReducedNonLatencyPipelineDepth = 8;

  if (low_delay) {
    return limits::kMaxVideoFrames + 1;
#if BUILDFLAG(USE_V4L2_CODEC)
  } else if (use_protected &&
             ActiveLinuxVideoDecoderType() == VideoDecoderType::kV4L2) {
    return kExpectedNonLatencyPipelineDepthSecure;
#endif
  } else if (base::FeatureList::IsEnabled(kReduceHardwareVideoDecoderBuffers)) {
    return kReducedNonLatencyPipelineDepth;
  } else {
    return kExpectedNonLatencyPipelineDepth;
  }
}

scoped_refptr<base::SequencedTaskRunner> GetDecoderTaskRunner(
    bool in_video_decoder_process) {
  // Note that the decoder thread is created with base::MayBlock(). This is
  // because the underlying |decoder_| may need to allocate a dummy buffer
  // to discover the most native modifier accepted by the hardware video
  // decoder; this in turn may need to open the render node, and this is the
  // operation that may block.
  if (in_video_decoder_process) {
    return base::ThreadPool::CreateSequencedTaskRunner(
        {base::TaskPriority::USER_VISIBLE, base::MayBlock()});
  }
  return base::ThreadPool::CreateSingleThreadTaskRunner(
      {base::WithBaseSyncPrimitives(), base::TaskPriority::USER_VISIBLE,
       base::MayBlock()},
      base::SingleThreadTaskRunnerThreadMode::DEDICATED);
}

int GetMaxNumDecoderInstances(const gpu::GpuDriverBugWorkarounds& workarounds) {
  constexpr int kDefaultMaxNumDecoderInstances =
      std::numeric_limits<int>::max();
  if (workarounds.max_num_hw_video_decoders_10) {
    return 10;
  }
  return kDefaultMaxNumDecoderInstances;
}

}  //  namespace

VideoDecoderMixin::VideoDecoderMixin(
    std::unique_ptr<MediaLog> media_log,
    scoped_refptr<base::SequencedTaskRunner> decoder_task_runner,
    base::WeakPtr<VideoDecoderMixin::Client> client)
    : media_log_(std::move(media_log)),
      decoder_task_runner_(std::move(decoder_task_runner)),
      client_(std::move(client)) {}

VideoDecoderMixin::~VideoDecoderMixin() = default;

bool VideoDecoderMixin::NeedsTranscryption() {
  return false;
}

CroStatus VideoDecoderMixin::AttachSecureBuffer(
    scoped_refptr<DecoderBuffer>& buffer) {
  return CroStatus::Codes::kOk;
}

void VideoDecoderMixin::Initialize(const VideoDecoderConfig& config,
                                   bool low_delay,
                                   CdmContext* cdm_context,
                                   InitCB init_cb,
                                   const OutputCB& output_cb,
                                   const WaitingCB& waiting_cb) {
  NOTREACHED() << "FrameResource version of Initialize is used instead";
}

void VideoDecoderMixin::ReleaseSecureBuffer(uint64_t secure_handle) {}

size_t VideoDecoderMixin::GetMaxOutputFramePoolSize() const {
  return std::numeric_limits<size_t>::max();
}

VideoDecoderPipeline::ClientFlushCBState::ClientFlushCBState(
    DecodeCB flush_cb,
    DecoderStatus decoder_decode_status)
    : flush_cb(std::move(flush_cb)),
      decoder_decode_status(decoder_decode_status) {}

VideoDecoderPipeline::ClientFlushCBState::~ClientFlushCBState() = default;

// static
base::AtomicRefCount
    VideoDecoderPipeline::DecoderReservation::num_decoder_instances_(0);

std::unique_ptr<VideoDecoderPipeline::DecoderReservation>
VideoDecoderPipeline::DecoderReservation::Take(int max_decoders) {
  if (max_decoders == std::numeric_limits<int>::max()) {
    return base::WrapUnique(new VideoDecoderPipeline::DecoderReservation(
        /*reservation_taken=*/false));
  }
  // If Increment() pushes |num_decoder_instances_| beyond |max_decoders| return
  // nullptr and undo the Increment(). Note: Increment() returns the previous
  // number of instances.
  if (num_decoder_instances_.Increment() >= max_decoders) {
    num_decoder_instances_.Decrement();
    return nullptr;
  }
  return base::WrapUnique(
      new VideoDecoderPipeline::DecoderReservation(/*reservation_taken=*/true));
}

VideoDecoderPipeline::DecoderReservation::DecoderReservation(
    bool reservation_taken)
    : reservation_taken_(reservation_taken) {}

VideoDecoderPipeline::DecoderReservation::~DecoderReservation() {
  if (reservation_taken_) {
    num_decoder_instances_.Decrement();
  }
}

// static
std::unique_ptr<VideoDecoder> VideoDecoderPipeline::Create(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    scoped_refptr<base::SequencedTaskRunner> client_task_runner,
    std::unique_ptr<DmabufVideoFramePool> frame_pool,
    std::unique_ptr<FrameResourceConverter> frame_converter,
    std::vector<Fourcc> renderable_fourccs,
    std::unique_ptr<MediaLog> media_log,
    mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder,
    bool in_video_decoder_process) {
  DCHECK(client_task_runner);
  DCHECK(!renderable_fourccs.empty());

  CreateDecoderFunctionCB create_decoder_function_cb;
  bool uses_oop_video_decoder = false;
  if (oop_video_decoder) {
    DCHECK(!frame_pool);
    create_decoder_function_cb =
        base::BindOnce(&OOPVideoDecoder::Create, std::move(oop_video_decoder));
    uses_oop_video_decoder = true;
  } else {
    DCHECK(frame_pool);
    switch (ActiveLinuxVideoDecoderType()) {
#if BUILDFLAG(USE_VAAPI)
      case VideoDecoderType::kVaapi:
        create_decoder_function_cb = base::BindOnce(&VaapiVideoDecoder::Create);
        break;
#endif  // BUILDFLAG(USE_VAAPI)
#if BUILDFLAG(USE_V4L2_CODEC)
      case VideoDecoderType::kV4L2:
        if (IsV4L2DecoderStateful()) {
          create_decoder_function_cb =
              base::BindOnce(&V4L2StatefulVideoDecoder::Create);
        } else {
          create_decoder_function_cb =
              base::BindOnce(&V4L2VideoDecoder::Create);
        }
        break;
#endif  // BUILDFLAG(USE_V4L2_CODEC)
      default:
        return nullptr;
    }
  }

  std::unique_ptr<DecoderReservation> decoder_reservation =
      DecoderReservation::Take(GetMaxNumDecoderInstances(workarounds));
  if (!decoder_reservation) {
    return nullptr;
  }

  auto* pipeline = new VideoDecoderPipeline(
      std::move(decoder_reservation), workarounds,
      std::move(client_task_runner), std::move(frame_pool),
      std::move(frame_converter), std::move(renderable_fourccs),
      std::move(media_log), std::move(create_decoder_function_cb),
      uses_oop_video_decoder, in_video_decoder_process);
  return std::make_unique<AsyncDestroyVideoDecoder<VideoDecoderPipeline>>(
      base::WrapUnique(pipeline));
}

// static
std::unique_ptr<VideoDecoder> VideoDecoderPipeline::CreateForARC(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    scoped_refptr<base::SequencedTaskRunner> client_task_runner,
    std::unique_ptr<DmabufVideoFramePool> frame_pool,
    std::vector<Fourcc> renderable_fourccs,
    std::unique_ptr<MediaLog> media_log) {
  DCHECK(client_task_runner);
  DCHECK(frame_pool);
  DCHECK(!renderable_fourccs.empty());

  CreateDecoderFunctionCB create_decoder_function_cb;
  switch (ActiveLinuxVideoDecoderType()) {
#if BUILDFLAG(USE_VAAPI)
    case VideoDecoderType::kVaapi:
      create_decoder_function_cb = base::BindOnce(&VaapiVideoDecoder::Create);
      break;
#endif  // BUILDFLAG(USE_VAAPI)
#if BUILDFLAG(USE_V4L2_CODEC)
    case VideoDecoderType::kV4L2:
      create_decoder_function_cb = base::BindOnce(&V4L2VideoDecoder::Create);
      break;
#endif  // BUILDFLAG(USE_V4L2_CODEC)
    default:
      return nullptr;
  }

  std::unique_ptr<DecoderReservation> decoder_reservation =
      DecoderReservation::Take(GetMaxNumDecoderInstances(workarounds));
  if (!decoder_reservation) {
    return nullptr;
  }

  auto* pipeline = new VideoDecoderPipeline(
      std::move(decoder_reservation), workarounds,
      std::move(client_task_runner), std::move(frame_pool),
      RegisteredFrameConverter::Create(base::MakeRefCounted<FrameRegistry>()),
      std::move(renderable_fourccs), std::move(media_log),
      std::move(create_decoder_function_cb),
      /*uses_oop_video_decoder=*/false,
      // TODO(b/195769334): Set this properly once OOP-VD is enabled for ARC.
      /*in_video_decoder_process=*/false);
  return std::make_unique<AsyncDestroyVideoDecoder<VideoDecoderPipeline>>(
      base::WrapUnique(pipeline));
}

// static
std::unique_ptr<VideoDecoder> VideoDecoderPipeline::CreateForTesting(
    scoped_refptr<base::SequencedTaskRunner> client_task_runner,
    std::unique_ptr<FrameResourceConverter> frame_converter,
    std::unique_ptr<MediaLog> media_log,
    bool ignore_resolution_changes_to_smaller_for_testing) {
  CreateDecoderFunctionCB create_decoder_function_cb;
  switch (ActiveLinuxVideoDecoderType()) {
#if BUILDFLAG(USE_VAAPI)
    case VideoDecoderType::kVaapi:
      create_decoder_function_cb = base::BindOnce(&VaapiVideoDecoder::Create);
      break;
#endif  // BUILDFLAG(USE_VAAPI)
#if BUILDFLAG(USE_V4L2_CODEC)
    case VideoDecoderType::kV4L2:
      if (IsV4L2DecoderStateful()) {
        create_decoder_function_cb =
            base::BindOnce(&V4L2StatefulVideoDecoder::Create);
      } else {
        create_decoder_function_cb = base::BindOnce(&V4L2VideoDecoder::Create);
      }
      break;
#endif  // BUILDFLAG(USE_V4L2_CODEC)
    default:
      return nullptr;
  }

  std::unique_ptr<DecoderReservation> decoder_reservation =
      DecoderReservation::Take(std::numeric_limits<int>::max());
  CHECK(decoder_reservation);  // Take(std::numeric_limits<int>::max()) is
                               // guaranteed to return a valid reservation.

  auto* pipeline = new VideoDecoderPipeline(
      std::move(decoder_reservation), gpu::GpuDriverBugWorkarounds(),
      std::move(client_task_runner), std::make_unique<PlatformVideoFramePool>(),
      std::move(frame_converter),
      VideoDecoderPipeline::DefaultPreferredRenderableFourccs(),
      std::move(media_log), std::move(create_decoder_function_cb),
      /*uses_oop_video_decoder=*/false, /*in_video_decoder_process=*/true);

  if (ignore_resolution_changes_to_smaller_for_testing)
    pipeline->ignore_resolution_changes_to_smaller_for_testing_ = true;

  return std::make_unique<AsyncDestroyVideoDecoder<VideoDecoderPipeline>>(
      base::WrapUnique(pipeline));
}

// static
std::vector<Fourcc> VideoDecoderPipeline::DefaultPreferredRenderableFourccs() {
  // Preferred output formats in order of preference.
  // TODO(mcasas): query the platform for its preferred formats and modifiers.
  return {
      Fourcc(Fourcc::NV12),
      Fourcc(Fourcc::P010),
      // Only used for Hana (MT8173). Remove when that device reaches EOL
      Fourcc(Fourcc::YV12),
  };
}

// static
void VideoDecoderPipeline::NotifySupportKnown(
    mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder,
    base::OnceCallback<void(mojo::PendingRemote<mojom::VideoDecoder>)> cb) {
  if (oop_video_decoder) {
    OOPVideoDecoder::NotifySupportKnown(std::move(oop_video_decoder),
                                        std::move(cb));
    return;
  }
  std::move(cb).Run(std::move(oop_video_decoder));
}

// static
std::optional<SupportedVideoDecoderConfigs>
VideoDecoderPipeline::GetSupportedConfigs(
    VideoDecoderType decoder_type,
    const gpu::GpuDriverBugWorkarounds& workarounds) {
  std::optional<SupportedVideoDecoderConfigs> configs;
  switch (decoder_type) {
    case VideoDecoderType::kOutOfProcess:
      configs = OOPVideoDecoder::GetSupportedConfigs();
      break;
#if BUILDFLAG(USE_VAAPI)
    case VideoDecoderType::kVaapi:
      configs = VaapiVideoDecoder::GetSupportedConfigs();
      break;
#endif  // BUILDFLAG(USE_VAAPI)
#if BUILDFLAG(USE_V4L2_CODEC)
    case VideoDecoderType::kV4L2:
      configs = GetSupportedV4L2DecoderConfigs();
      break;
#endif  // BUILDFLAG(USE_V4L2_CODEC)
    default:
      configs = std::nullopt;
  }

  if (!configs)
    return std::nullopt;

  if (workarounds.disable_accelerated_vp8_decode) {
    std::erase_if(configs.value(), [](const auto& config) {
      return config.profile_min >= VP8PROFILE_MIN &&
             config.profile_max <= VP8PROFILE_MAX;
    });
  }

  if (workarounds.disable_accelerated_vp9_decode) {
    std::erase_if(configs.value(), [](const auto& config) {
      return config.profile_min >= VP9PROFILE_PROFILE0 &&
             config.profile_max <= VP9PROFILE_PROFILE0;
    });
  }

  if (workarounds.disable_accelerated_vp9_profile2_decode) {
    std::erase_if(configs.value(), [](const auto& config) {
      return config.profile_min >= VP9PROFILE_PROFILE2 &&
             config.profile_max <= VP9PROFILE_PROFILE2;
    });
  }

  if (workarounds.disable_accelerated_h264_decode) {
    std::erase_if(configs.value(), [](const auto& config) {
      return config.profile_min >= H264PROFILE_MIN &&
             config.profile_max <= H264PROFILE_MAX;
    });
  }

  if (workarounds.disable_accelerated_hevc_decode) {
    std::erase_if(configs.value(), [](const auto& config) {
      return config.profile_min >= HEVCPROFILE_MIN &&
             config.profile_max <= HEVCPROFILE_MAX;
    });
  }

  if (workarounds.disable_accelerated_av1_decode) {
    std::erase_if(configs.value(), [](const auto& config) {
      return config.profile_min >= AV1PROFILE_MIN &&
             config.profile_max <= AV1PROFILE_MAX;
    });
  }

  return configs;
}

VideoDecoderPipeline::VideoDecoderPipeline(
    std::unique_ptr<DecoderReservation> decoder_reservation,
    const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
    scoped_refptr<base::SequencedTaskRunner> client_task_runner,
    std::unique_ptr<DmabufVideoFramePool> frame_pool,
    std::unique_ptr<FrameResourceConverter> frame_converter,
    std::vector<Fourcc> renderable_fourccs,
    std::unique_ptr<MediaLog> media_log,
    CreateDecoderFunctionCB create_decoder_function_cb,
    bool uses_oop_video_decoder,
    bool in_video_decoder_process)
    : decoder_reservation_(std::move(decoder_reservation)),
      gpu_workarounds_(gpu_workarounds),
      client_task_runner_(std::move(client_task_runner)),
      decoder_task_runner_(
          uses_oop_video_decoder
              ? client_task_runner_
              : GetDecoderTaskRunner(in_video_decoder_process)),
      main_frame_pool_(std::move(frame_pool)),
      frame_converter_(std::move(frame_converter)),
      renderable_fourccs_(std::move(renderable_fourccs)),
      media_log_(std::move(media_log)),
      create_decoder_function_cb_(std::move(create_decoder_function_cb)),
      oop_decoder_can_read_without_stalling_(false),
      uses_oop_video_decoder_(uses_oop_video_decoder) {
  CHECK(decoder_reservation_);
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  DETACH_FROM_SEQUENCE(decoder_sequence_checker_);
  DCHECK_EQ(!main_frame_pool_, uses_oop_video_decoder_);
  DCHECK(client_task_runner_);
  CHECK(frame_converter_);
  DVLOGF(2);

  decoder_weak_this_ = decoder_weak_this_factory_.GetWeakPtr();

  if (main_frame_pool_) {
    main_frame_pool_->set_parent_task_runner(decoder_task_runner_);
  }
  frame_converter_->Initialize(
      decoder_task_runner_,
      base::BindRepeating(&VideoDecoderPipeline::OnFrameConverted,
                          decoder_weak_this_));
}

VideoDecoderPipeline::~VideoDecoderPipeline() {
  // We have to destroy |main_frame_pool_| and |frame_converter_| on
  // |decoder_task_runner_|, so the destructor must be called on
  // |decoder_task_runner_|.
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  decoder_weak_this_factory_.InvalidateWeakPtrs();

  // Destroy |frame_converter_| before |main_frame_pool_| and |decoder| because
  // the former may have a raw pointer to the latter (in the unwrap-frame
  // callback).
  //
  // TODO(andrescj): consider making the unwrap-frame callback work with WeakPtr
  // instead.
  frame_converter_.reset();
  main_frame_pool_.reset();
#if BUILDFLAG(IS_CHROMEOS)
  // We must release |buffer_transcryptor_| before the decoder because it holds
  // a raw pointer to |decoder_|.
  buffer_transcryptor_.reset();
#endif  // BUILDFLAG(IS_CHROMEOS)
  decoder_.reset();
}

// static
void VideoDecoderPipeline::DestroyAsync(
    std::unique_ptr<VideoDecoderPipeline> pipeline) {
  DVLOGF(2);
  DCHECK(pipeline);
  DCHECK_CALLED_ON_VALID_SEQUENCE(pipeline->client_sequence_checker_);

  auto* decoder_task_runner = pipeline->decoder_task_runner_.get();
  decoder_task_runner->DeleteSoon(FROM_HERE, std::move(pipeline));
}

VideoDecoderType VideoDecoderPipeline::GetDecoderType() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  // TODO(mcasas): query |decoder_| instead. This is difficult because it can
  // only be accessed on |decoder_sequence_checker_|: this method is supposed
  // to be synchronous.

  if (uses_oop_video_decoder_) {
    return VideoDecoderType::kOutOfProcess;
  }

  return ActiveLinuxVideoDecoderType();
}

bool VideoDecoderPipeline::IsPlatformDecoder() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);

  return true;
}

int VideoDecoderPipeline::GetMaxDecodeRequests() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  return decoder_max_decode_requests_;
}

bool VideoDecoderPipeline::FramesHoldExternalResources() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  return true;
}

bool VideoDecoderPipeline::NeedsBitstreamConversion() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);

  // TODO(mcasas): also query |decoder_|.
  return needs_bitstream_conversion_;
}

bool VideoDecoderPipeline::CanReadWithoutStalling() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);

  if (uses_oop_video_decoder_) {
    return oop_decoder_can_read_without_stalling_.load(
        std::memory_order_seq_cst);
  }

  // TODO(mcasas): also query |decoder_|.
  return main_frame_pool_ && !main_frame_pool_->IsExhausted();
}

size_t VideoDecoderPipeline::GetDecoderMaxOutputFramePoolSize() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  return decoder_ ? decoder_->GetMaxOutputFramePoolSize()
                  : std::numeric_limits<size_t>::max();
}

void VideoDecoderPipeline::Initialize(const VideoDecoderConfig& config,
                                      bool low_delay,
                                      CdmContext* cdm_context,
                                      InitCB init_cb,
                                      const OutputCB& output_cb,
                                      const WaitingCB& waiting_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  VLOGF(2) << "config: " << config.AsHumanReadableString();

  if (!config.IsValidConfig()) {
    VLOGF(1) << "config is not valid";
    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
    return;
  }
#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
  if (config.is_encrypted() && !cdm_context) {
    VLOGF(1) << "Encrypted streams require a CdmContext";
    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
    return;
  }
#else   // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
  if (config.is_encrypted() && !allow_encrypted_content_for_testing_) {
    VLOGF(1) << "Encrypted streams are not supported for this VD";
    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
    return;
  }
#endif  // !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)

  // Make sure that the configuration requested is supported by the driver,
  // which must provide such information.
  const auto supported_configs =
      supported_configs_for_testing_.empty()
          ? VideoDecoderPipeline::GetSupportedConfigs(GetDecoderType(),
                                                      gpu_workarounds_)
          : supported_configs_for_testing_;
  if (!supported_configs.has_value()) {
    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
    return;
  }
  if (!IsVideoDecoderConfigSupported(supported_configs.value(), config)) {
    VLOGF(1) << "Video configuration is not supported: "
             << config.AsHumanReadableString();
    MEDIA_LOG(INFO, media_log_) << "Video configuration is not supported: "
                                << config.AsHumanReadableString();
    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig);
    return;
  }
  constexpr auto kVideoCodecProfileCount = media::VIDEO_CODEC_PROFILE_MAX + 1;
  base::UmaHistogramEnumeration(
      "Media.PlatformVideoDecoding.VideoCodecProfile", config.profile(),
      static_cast<VideoCodecProfile>(kVideoCodecProfileCount));

  needs_bitstream_conversion_ = (config.codec() == VideoCodec::kH264) ||
                                (config.codec() == VideoCodec::kHEVC);

  decoder_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&VideoDecoderPipeline::InitializeTask, decoder_weak_this_,
                     config, low_delay, cdm_context, std::move(init_cb),
                     std::move(output_cb), std::move(waiting_cb)));
}

void VideoDecoderPipeline::InitializeTask(const VideoDecoderConfig& config,
                                          bool low_delay,
                                          CdmContext* cdm_context,
                                          InitCB init_cb,
                                          const OutputCB& output_cb,
                                          const WaitingCB& waiting_cb) {
  DVLOGF(3);
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  client_output_cb_ = std::move(output_cb);
  waiting_cb_ = std::move(waiting_cb);

  // |decoder_| may be Initialize()d multiple times (e.g. on |config| changes)
  // but can only be created once.
  if (!decoder_ && !create_decoder_function_cb_.is_null()) {
    // Note: because we std::move(create_decoder_function_cb_), we only reach
    // this code once. Therefore, we don't need to worry about this assignment
    // potentially destroying an existing |decoder_| which means we don't have
    // to call |frame_converter_|->set_get_original_frame_cb() here.
    decoder_ =
        std::move(create_decoder_function_cb_)
            .Run(media_log_->Clone(), decoder_task_runner_, decoder_weak_this_);
  }
  // Note: |decoder_| might fail to be created, e.g. on V4L2 platforms.
  if (!decoder_) {
    OnError("|decoder_| creation failed.");
    client_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(init_cb),
                       DecoderStatus::Codes::kFailedToCreateDecoder));
    return;
  }

  if (frame_converter_->UsesGetOriginalFrameCB()) {
    FrameResourceConverter::GetOriginalFrameCB get_original_frame_cb;

    if (uses_oop_video_decoder_) {
      // Note: base::Unretained() is safe because either a) |decoder_| outlives
      // the |frame_converter_| or b) we call
      // |frame_converter_|->set_get_original_frame_cb() with a null
      // GetOriginalFrameCB before destroying |decoder_|.
      get_original_frame_cb = base::BindRepeating(
          &OOPVideoDecoder::GetOriginalFrame,
          base::Unretained(static_cast<OOPVideoDecoder*>(decoder_.get())));
    } else {
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_V4L2_CODEC)
      if (!main_frame_pool_) {
        get_original_frame_cb = base::NullCallback();
      } else
#endif  // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_V4L2_CODEC)
      {
        CHECK(main_frame_pool_);
        PlatformVideoFramePool* platform_video_frame_pool =
            main_frame_pool_->AsPlatformVideoFramePool();
        // The only |frame_converter_| that needs the GetOriginalFrameCB
        // callback is the MailboxVideoFrameConverter. When it is used, the
        // |main_frame_pool_| should always be a PlatformVideoFramePool.
        CHECK(platform_video_frame_pool);

        // Note: base::Unretained() is safe because either a) the
        // |main_frame_pool_| outlives |frame_converter_| or b) we call
        // |frame_converter_|->set_get_original_frame_cb() with a null
        // GetOriginalFrameCB before destroying |main_frame_pool_|.
        get_original_frame_cb =
            base::BindRepeating(&PlatformVideoFramePool::GetOriginalFrame,
                                base::Unretained(platform_video_frame_pool));
      }
    }

    frame_converter_->set_get_original_frame_cb(
        std::move(get_original_frame_cb));
  }

  estimated_num_buffers_for_renderer_ =
      EstimateRequiredRendererPipelineBuffers(low_delay, config.is_encrypted());

#if BUILDFLAG(USE_V4L2_CODEC)
  decryption_needs_vp9_superframe_splitting_ =
      config.codec() == VideoCodec::kVP9;
#endif

#if BUILDFLAG(USE_VAAPI)
  if (ignore_resolution_changes_to_smaller_for_testing_) {
    static_cast<VaapiVideoDecoder*>(decoder_.get())
        ->set_ignore_resolution_changes_to_smaller_vp9_for_testing(  // IN-TEST
            true);
  }
#endif

  decoder_->Initialize(
      config, /* low_delay=*/false, cdm_context,
      base::BindOnce(&VideoDecoderPipeline::OnInitializeDone,
                     decoder_weak_this_, std::move(init_cb), cdm_context),
      base::BindRepeating(&VideoDecoderPipeline::OnFrameDecoded,
                          decoder_weak_this_),
      base::BindRepeating(&VideoDecoderPipeline::OnDecoderWaiting,
                          decoder_weak_this_));
}

void VideoDecoderPipeline::OnInitializeDone(InitCB init_cb,
                                            CdmContext* cdm_context,
                                            DecoderStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4) << "Initialization status = " << static_cast<int>(status.code());

  if (!status.is_ok()) {
    MEDIA_LOG(ERROR, media_log_)
        << "VideoDecoderPipeline |decoder_| Initialize() failed, status: "
        << static_cast<int>(status.code());
    frame_converter_->set_get_original_frame_cb(base::NullCallback());
#if BUILDFLAG(IS_CHROMEOS)
    // We always need to destroy |buffer_transcryptor_| if it exists before
    // |decoder_|.
    buffer_transcryptor_.reset();
#endif  // BUILDFLAG(IS_CHROMEOS)
    decoder_.reset();
  } else {
    MEDIA_LOG(INFO, media_log_)
        << "VideoDecoderPipeline |decoder_| Initialize() successful";
  }

#if BUILDFLAG(IS_CHROMEOS)
  if (decoder_ && decoder_->NeedsTranscryption()) {
    if (!cdm_context) {
      VLOGF(1) << "CdmContext required for transcryption";
      frame_converter_->set_get_original_frame_cb(base::NullCallback());
      // We always need to destroy |buffer_transcryptor_| if it exists before
      // |decoder_|.
      buffer_transcryptor_.reset();
      decoder_.reset();
      status = DecoderStatus::Codes::kUnsupportedEncryptionMode;
    } else {
      // We need to enable transcryption for protected content.
      buffer_transcryptor_ = std::make_unique<DecoderBufferTranscryptor>(
          cdm_context, *decoder_, decryption_needs_vp9_superframe_splitting_,
          base::BindRepeating(&VideoDecoderPipeline::OnBufferTranscrypted,
                              decoder_weak_this_),
          base::BindRepeating(&VideoDecoderPipeline::OnDecoderWaiting,
                              decoder_weak_this_));
    }
  } else {
    // In case this was created on a prior initialization but no longer needed.
    buffer_transcryptor_.reset();
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  client_task_runner_->PostTask(FROM_HERE,
                                base::BindOnce(std::move(init_cb), status));
}

void VideoDecoderPipeline::Reset(base::OnceClosure reset_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  DVLOGF(3);

  decoder_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&VideoDecoderPipeline::ResetTask,
                                decoder_weak_this_, std::move(reset_cb)));
}

void VideoDecoderPipeline::ResetTask(base::OnceClosure reset_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

#if BUILDFLAG(IS_CHROMEOS)
  drop_transcrypted_buffers_ = true;
#endif  // BUILDFLAG(IS_CHROMEOS)
  need_apply_new_resolution = false;
  decoder_->Reset(base::BindOnce(&VideoDecoderPipeline::OnResetDone,
                                 decoder_weak_this_, std::move(reset_cb)));
}

void VideoDecoderPipeline::OnResetDone(base::OnceClosure reset_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  if (image_processor_)
    image_processor_->Reset();
  frame_converter_->AbortPendingFrames();

#if BUILDFLAG(IS_CHROMEOS)
  if (buffer_transcryptor_)
    buffer_transcryptor_->Reset(DecoderStatus::Codes::kAborted);
#endif  // BUILDFLAG(IS_CHROMEOS)

  CallFlushCbIfNeeded(/*override_status=*/DecoderStatus::Codes::kAborted);

  if (need_frame_pool_rebuild_) {
    need_frame_pool_rebuild_ = false;
    if (main_frame_pool_)
      main_frame_pool_->ReleaseAllFrames();
    if (auxiliary_frame_pool_)
      auxiliary_frame_pool_->ReleaseAllFrames();
  }

#if BUILDFLAG(IS_CHROMEOS)
  drop_transcrypted_buffers_ = false;
#endif  // BUILDFLAG(IS_CHROMEOS)

  client_task_runner_->PostTask(FROM_HERE, std::move(reset_cb));
}

void VideoDecoderPipeline::Decode(scoped_refptr<DecoderBuffer> buffer,
                                  DecodeCB decode_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
  CHECK(buffer);
  DVLOGF(4);
  TRACE_EVENT1(
      "media,gpu", "VideoDecoderPipeline::Decode", "timestamp",
      (buffer->end_of_stream() ? 0 : buffer->timestamp().InMicroseconds()));
  decoder_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&VideoDecoderPipeline::DecodeTask, decoder_weak_this_,
                     std::move(buffer), std::move(decode_cb)));
}

void VideoDecoderPipeline::DecodeTask(scoped_refptr<DecoderBuffer> buffer,
                                      DecodeCB decode_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DCHECK(decoder_);
  CHECK(buffer);
  DVLOGF(4);
  TRACE_EVENT1(
      "media,gpu", "VideoDecoderPipeline::DecodeTask", "timestamp",
      (buffer->end_of_stream() ? 0 : buffer->timestamp().InMicroseconds()));
  if (has_error_) {
    client_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
    return;
  }

  const bool is_flush = buffer->end_of_stream();
#if BUILDFLAG(IS_CHROMEOS)
  if (buffer_transcryptor_) {
    buffer_transcryptor_->EnqueueBuffer(
        std::move(buffer),
        base::BindOnce(&VideoDecoderPipeline::OnDecodeDone, decoder_weak_this_,
                       is_flush, std::move(decode_cb)));
    return;
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  decoder_->Decode(
      std::move(buffer),
      base::BindOnce(&VideoDecoderPipeline::OnDecodeDone, decoder_weak_this_,
                     is_flush, std::move(decode_cb)));
}

void VideoDecoderPipeline::OnDecodeDone(bool is_flush,
                                        DecodeCB decode_cb,
                                        DecoderStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4) << "is_flush: " << is_flush
            << ", status: " << static_cast<int>(status.code());

  if (has_error_)
    status = DecoderStatus::Codes::kFailed;

  if (is_flush) {
    client_flush_cb_state_.emplace(
        /*flush_cb=*/std::move(decode_cb), /*decoder_decode_status=*/status);
    CallFlushCbIfNeeded(/*override_status=*/std::nullopt);
    return;
  }

  client_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(std::move(decode_cb), std::move(status)));
}

void VideoDecoderPipeline::OnFrameDecoded(scoped_refptr<FrameResource> frame) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4);
  TRACE_EVENT1("media,gpu", "VideoDecoderPipeline::OnFrameDecoded", "timestamp",
               (frame ? frame->timestamp().InMicroseconds() : 0));

#if BUILDFLAG(IS_CHROMEOS)
  if (buffer_transcryptor_) {
    buffer_transcryptor_->SecureBuffersMayBeAvailable();
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  if (uses_oop_video_decoder_) {
    oop_decoder_can_read_without_stalling_.store(
        decoder_->CanReadWithoutStalling(), std::memory_order_seq_cst);
  }

  if (image_processor_) {
    image_processor_->Process(
        std::move(frame),
        base::BindOnce(&VideoDecoderPipeline::OnFrameProcessed,
                       decoder_weak_this_));
    return;
  }

  frame_converter_->ConvertFrame(std::move(frame));
}

void VideoDecoderPipeline::OnFrameProcessed(
    scoped_refptr<FrameResource> frame) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4);
  TRACE_EVENT1("media,gpu", "VideoDecoderPipeline::OnFrameProcessed",
               "timestamp", (frame ? frame->timestamp().InMicroseconds() : 0));
  frame_converter_->ConvertFrame(std::move(frame));
}

void VideoDecoderPipeline::OnFrameConverted(
    scoped_refptr<VideoFrame> video_frame) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4);
  TRACE_EVENT1("media,gpu", "VideoDecoderPipeline::OnFrameConverted",
               "timestamp",
               (video_frame ? video_frame->timestamp().InMicroseconds() : 0));
  if (!video_frame) {
    return OnError("Frame converter returns null frame.");
  }
  if (has_error_) {
    DVLOGF(2) << "Skip returning frames after error occurs.";
    return;
  }

  // Flag that the video frame was decoded in a power efficient way.
  video_frame->metadata().power_efficient = true;

  video_frame->metadata().read_lock_fences_enabled = true;

  // MojoVideoDecoderService expects the |output_cb_| to be called on the client
  // task runner, even though media::VideoDecoder states frames should be output
  // without any thread jumping.
  // Note that all the decode/flush/output/reset callbacks are executed on
  // |client_task_runner_|.
  client_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(client_output_cb_, std::move(video_frame)));

  // After outputting a frame, flush might be completed.
  CallFlushCbIfNeeded(/*override_status=*/std::nullopt);
  CallApplyResolutionChangeIfNeeded();
}

void VideoDecoderPipeline::OnDecoderWaiting(WaitingReason reason) {
  DVLOGF(3);
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  if (reason == media::WaitingReason::kDecoderStateLost)
    need_frame_pool_rebuild_ = true;

  client_task_runner_->PostTask(FROM_HERE, base::BindOnce(waiting_cb_, reason));
}

bool VideoDecoderPipeline::HasPendingFrames() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  const bool frame_converter_has_pending_frames_ =
      frame_converter_->HasPendingFrames();
  const bool image_processor_has_pending_frames_ =
      image_processor_ && image_processor_->HasPendingFrames();

  DVLOGF(3) << "|frame_converter_|: "
            << (frame_converter_has_pending_frames_ ? "yes" : "no")
            << ", |image_processor_|: "
            << (image_processor_has_pending_frames_ ? "yes" : "no");
  return frame_converter_has_pending_frames_ ||
         image_processor_has_pending_frames_;
}

void VideoDecoderPipeline::OnError(const std::string& msg) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  VLOGF(1) << msg;
  MEDIA_LOG(ERROR, media_log_) << "VideoDecoderPipeline " << msg;

  has_error_ = true;

  if (image_processor_)
    image_processor_->Reset();
  frame_converter_->AbortPendingFrames();

#if BUILDFLAG(IS_CHROMEOS)
  if (buffer_transcryptor_)
    buffer_transcryptor_->Reset(DecoderStatus::Codes::kFailed);
#endif  // BUILDFLAG(IS_CHROMEOS)

  CallFlushCbIfNeeded(/*override_status=*/DecoderStatus::Codes::kFailed);
}

void VideoDecoderPipeline::CallFlushCbIfNeeded(
    std::optional<DecoderStatus> override_status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  if (!client_flush_cb_state_) {
    return;
  }

  // All the call sites where |override_status| is non-null should guarantee
  // that there are no pending frames. If there were pending frames, then we
  // would drop |override_status|, and that seems like undesired behavior.
  const bool has_pending_frames = HasPendingFrames();
  DCHECK(!override_status || !has_pending_frames);

  if (has_pending_frames) {
    // Flush is not completed yet.
    return;
  }

  DecodeCB flush_cb = std::move(client_flush_cb_state_->flush_cb);
  const DecoderStatus status =
      override_status.value_or(client_flush_cb_state_->decoder_decode_status);
  DVLOGF(3) << "status: " << static_cast<int>(status.code());
  client_flush_cb_state_.reset();
  client_task_runner_->PostTask(FROM_HERE,
                                base::BindOnce(std::move(flush_cb), status));
}

void VideoDecoderPipeline::PrepareChangeResolution() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);
  DCHECK(!need_apply_new_resolution);

  need_apply_new_resolution = true;
  CallApplyResolutionChangeIfNeeded();
}

void VideoDecoderPipeline::CallApplyResolutionChangeIfNeeded() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4);

  if (need_apply_new_resolution && !HasPendingFrames()) {
    need_apply_new_resolution = false;
    decoder_->ApplyResolutionChange();
  }
}

DmabufVideoFramePool* VideoDecoderPipeline::GetVideoFramePool() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  // TODO(andrescj): consider returning a WeakPtr instead. That way, if callers
  // store the returned pointer, they know that they should check it's valid
  // because the video frame pool can change across resolution changes if we go
  // from using an image processor to not using one (or viceversa).
  if (image_processor_)
    return auxiliary_frame_pool_.get();
  return main_frame_pool_.get();
}

void VideoDecoderPipeline::NotifyEstimatedMaxDecodeRequests(int num) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DCHECK(num);
  decoder_max_decode_requests_ = num;
}

CroStatus::Or<PixelLayoutCandidate>
VideoDecoderPipeline::PickDecoderOutputFormat(
    const std::vector<PixelLayoutCandidate>& candidates,
    const gfx::Rect& decoder_visible_rect,
    const gfx::Size& decoder_natural_size,
    std::optional<gfx::Size> output_size,
    size_t num_codec_reference_frames,
    bool use_protected,
    bool need_aux_frame_pool,
    std::optional<DmabufVideoFramePool::CreateFrameCB> allocator) {
  DVLOGF(3);
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  // Verify |num_codec_reference_frames| has a reasonable value. Anecdotally 16
  // is the largest amount of reference frames seen, on an ITU-T H.264 test
  // vector (CAPCM*1_Sand_E.h264).
  CHECK_LE(num_codec_reference_frames, 32u);
  CHECK(!uses_oop_video_decoder_);

  if (candidates.empty())
    return CroStatus::Codes::kNoDecoderOutputFormatCandidates;

  auxiliary_frame_pool_.reset();
  image_processor_.reset();

  // As long as we're not scaling, check if any of the |candidates| formats is
  // directly renderable. If so, and (VA-API-only) the modifier of buffers
  // provided by the frame pool matches the one supported by the |decoder_|, we
  // don't need an image processor.
  std::optional<PixelLayoutCandidate> viable_candidate;
  if (!output_size || *output_size == decoder_visible_rect.size()) {
    for (const auto& fourcc : renderable_fourccs_) {
      for (const auto& candidate : candidates) {
        if (candidate.fourcc == fourcc) {
          viable_candidate = candidate;
          break;
        }
      }
      if (viable_candidate)
        break;
    }
  }

  // TODO(jkardatzke): Remove this when we have protected content rendering on
  // ARM working. This is temporary so that video will actually decode on HWDRM
  // ARM devices during development (even though it won't be visible).
#if BUILDFLAG(USE_V4L2_CODEC) && BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
  if (use_protected) {
    for (const auto& candidate : candidates) {
      if (candidate.fourcc == Fourcc(Fourcc::MM21) ||
          candidate.fourcc == Fourcc(Fourcc::MT2T)) {
        LOG(WARNING) << "Forcing MM21/MT2T format for V4L2 protected content";
        viable_candidate = candidate;
      }
    }
  }
#endif

#if BUILDFLAG(IS_LINUX)
  // Linux: behavior depends on which backend the decoder represents, signaled
  // by whether it supplied a custom allocator.
  if (allocator.has_value()) {
#if BUILDFLAG(USE_VAAPI)
    // VA-API path: a custom allocator (libva-backed) is mandatory and must be
    // attached to a PlatformVideoFramePool. Frames will be backed by
    // NativePixmap with VideoFrame::STORAGE_DMABUFS.
    CHECK(main_frame_pool_->AsPlatformVideoFramePool());
    main_frame_pool_->AsPlatformVideoFramePool()->SetCustomFrameAllocator(
        *allocator, VideoFrame::STORAGE_DMABUFS);
#else
    // Reaching this branch with an allocator but no VA-API compiled in would
    // indicate a programming error.
    NOTREACHED();
#endif  // BUILDFLAG(USE_VAAPI)
  } else {
#if BUILDFLAG(USE_V4L2_CODEC)
    // V4L2 path: no custom allocator; let V4L2 itself allocate buffers if it
    // can decode directly to a preferred format.
    // TODO(wenst@) Test with full Chromium Browser
    if (viable_candidate) {
      // This is not compatible with VdVideoDecodeAccelerator, which expects
      // GPU buffers in VdVideoDecodeAccelerator::GetPicture().
      frame_converter_->set_get_original_frame_cb(base::NullCallback());
      main_frame_pool_.reset();
      return *viable_candidate;
    }
#else
    // Reaching this branch without an allocator but with no V4L2 compiled in
    // would indicate a programming error.
    NOTREACHED();
#endif  // BUILDFLAG(USE_V4L2_CODEC)
  }
#elif BUILDFLAG(IS_CHROMEOS)
  // Ash Chrome can use any type of frame pool (because it may get requests from
  // ARC++/ARCVM) but never a custom allocator.
  CHECK(!allocator.has_value());
#else
#error "Unsupported platform"
#endif

  if (viable_candidate) {
    // If maximum decoder frame pool size is less than the number of codec
    // reference frames (plus one frame for the frame being decoded), then the
    // decode will stall. Instead, this returns an error.
    if ((num_codec_reference_frames + 1) > GetDecoderMaxOutputFramePoolSize()) {
      return CroStatus::Codes::kInsufficientFramePoolSize;
    }

    // |main_frame_pool_| needs to allocate enough buffers for both the codec
    // reference needs and the Renderer pipeline.
    // |num_codec_reference_frames| is augmented by 1 to account for the frame
    // being decoded.
    const size_t num_pictures = std::min(
        GetDecoderMaxOutputFramePoolSize(),
        num_codec_reference_frames + 1 + estimated_num_buffers_for_renderer_);
    VLOGF(1) << "Initializing frame pool with up to " << num_pictures
             << " VideoFrames. No ImageProcessor needed.";

#if BUILDFLAG(USE_V4L2_CODEC)
    if (use_protected) {
      // Check to make sure we aren't going to blow our memory budget for V4L2
      // secure playback. We have 210MB reserved for the frame pool we allocate
      // here.
      constexpr size_t kMaxV4L2ProtectedMemory = 210 * 1024 * 1024;
      // Resolution
      size_t mem_required =
          viable_candidate->size.width() * viable_candidate->size.height();
      // YUV 4:2:0
      mem_required = mem_required * 3 / 2;
      if (viable_candidate->fourcc == Fourcc(Fourcc::MT2T)) {
        // 10-bit
        mem_required = mem_required * 5 / 4;
      }
      // For each picture
      mem_required *= num_pictures;
      VLOGF(1) << "V4L2 secure memory requirement is "
               << (mem_required / (1024 * 1024)) << "MB";
      if (mem_required > kMaxV4L2ProtectedMemory) {
        LOG(ERROR) << "Exceeded max memory for secure V4L2: "
                   << (mem_required / (1024 * 1024)) << "MB of "
                   << (kMaxV4L2ProtectedMemory / (1024 * 1024)) << "MB";
        return CroStatus::Codes::kUnableToAllocateSecureBuffer;
      }
    }
#endif  // BUILDFLAG(USE_V4L2_CODEC)

    CroStatus::Or<GpuBufferLayout> status_or_layout =
        main_frame_pool_->Initialize(viable_candidate->fourcc,
                                     viable_candidate->size,
                                     decoder_visible_rect, decoder_natural_size,
                                     num_pictures, use_protected);
    if (!status_or_layout.has_value())
      return std::move(status_or_layout).error();

    // TODO(mcasas): Consider changing the code here to update
    // viable_candidate->modifier to be |status_or_layout|'s modifier(), so
    // that callers of this method don't need to inspect GetGpuBufferLayout()
    // of this class' GetVideoFramePool().

#if BUILDFLAG(USE_VAAPI) && BUILDFLAG(IS_CHROMEOS)
    // Linux does not check the modifiers since it does not set any.
    const GpuBufferLayout layout(std::move(status_or_layout).value());
    if (layout.modifier() == viable_candidate->modifier) {
      return *viable_candidate;
    } else if (layout.modifier() != DRM_FORMAT_MOD_LINEAR) {
      // In theory, we could accept any |layout|.modifier(). However, the only
      // known use case for a modifier different than the one native to the
      // |decoder_| is when Android wishes to get linear decoded data. Thus, to
      // reduce the number of of moving parts that can fail, we restrict the
      // modifiers of pool buffers to be either the hardware decoder's native
      // modifier or DRM_FORMAT_MOD_LINEAR.
      DVLOGF(2) << "Unsupported modifier, " << std::hex
                << viable_candidate->modifier << ", passed in";
      return CroStatus::Codes::kFailedToCreateImageProcessor;
    }
#else
    return *viable_candidate;
#endif  // BUILDFLAG(USE_VAAPI) && BUILDFLAG(IS_CHROMEOS)
  }

  // We haven't found a |viable_candidate|, and need to instantiate an
  // ImageProcessor; this might need to allocate buffers internally, but only
  // to fill the Renderer pipeline.
  // TODO(b/267691989): The number of buffers for the image processor may need
  // need to be limited with a mechanism similar to
  // VideoDecoderMixin::GetMaxOutputFramePoolSize() depending on the backend.
  // Consider exposing the max frame pool size to the ImageProcessor.
  std::unique_ptr<ImageProcessor> image_processor;
  if (create_image_processor_cb_for_testing_) {
    image_processor = create_image_processor_cb_for_testing_.Run(
        candidates,
        /*input_visible_rect=*/decoder_visible_rect,
        output_size ? *output_size : decoder_visible_rect.size(),
        estimated_num_buffers_for_renderer_);
  } else {
    VLOGF(2) << "Initializing ImageProcessor; max buffers: "
             << estimated_num_buffers_for_renderer_;
    // |output_storage_type| holds the storage type of frames created by
    // |main_frame_pool_|, which are used as the output frames for the image
    // processor.
    // As part of b/277581596, |main_frame_pool_| will be changed to create
    // NativePixmap frames instead of GPU memory buffer frames. There will be
    // points during the transition where |main_frame_pool_| will use different
    // storage types for different platforms. The plan is to migrate the
    // ChromeOS Chrome browser frame pool first. Then later, Linux and ChromeOS
    // ARC. To handle correctly configuring |image_processor| during this
    // transition, the frame pool is being used as the source of truth for the
    // |output_storage_type|.
    // TODO(nhebert): Clean up this comment after the NativePixmap migration
    // completes.
    const VideoFrame::StorageType output_storage_type =
        main_frame_pool_->GetFrameStorageType();
    image_processor = ImageProcessorFactory::CreateWithInputCandidates(
        candidates, /*input_visible_rect=*/decoder_visible_rect,
        output_size ? *output_size : decoder_visible_rect.size(),
        output_storage_type, estimated_num_buffers_for_renderer_,
        decoder_task_runner_,
        base::BindRepeating(&PickRenderableFourcc, renderable_fourccs_),
        base::BindPostTaskToCurrentDefault(
            base::BindRepeating(&VideoDecoderPipeline::OnError,
                                decoder_weak_this_, "ImageProcessor error")));
  }

  if (!image_processor) {
    DVLOGF(2) << "Unable to find ImageProcessor to convert format";
    // TODO(crbug.com/40139291): Make CreateWithInputCandidates return an Or
    // type.
    return CroStatus::Codes::kFailedToCreateImageProcessor;
  }

  if (need_aux_frame_pool) {
    // Initialize the auxiliary frame pool with the input format of the image
    // processor. Same as before, this might need to allocate buffers
    // internally, but only to serve the codec needs, hence |num_pictures| is
    // just the number of codec reference frames, plus one to serve the video
    // destination.
    auxiliary_frame_pool_ = std::make_unique<PlatformVideoFramePool>();
    // Use here |num_codec_reference_frames| + 2: one to account for the frame
    // being decoded and an extra one for the ImageProcessor.
    const size_t num_pictures = num_codec_reference_frames + 2;

    // If maximum frame pool size is less than the number of codec reference
    // frames |num_pictures|, the decode will stall. Instead, this returns an
    // error.
    if (num_pictures > GetDecoderMaxOutputFramePoolSize()) {
      return CroStatus::Codes::kInsufficientFramePoolSize;
    }

    VLOGF(2) << "Initializing auxiliary frame pool with up to " << num_pictures
             << " VideoFrames";
    auxiliary_frame_pool_->set_parent_task_runner(decoder_task_runner_);

#if BUILDFLAG(IS_LINUX)
    // The custom allocator creates frames backed by NativePixmap, which uses a
    // VideoFrame::StorageType of VideoFrame::STORAGE_DMABUFS.
    auxiliary_frame_pool_->AsPlatformVideoFramePool()->SetCustomFrameAllocator(
        *allocator, VideoFrame::STORAGE_DMABUFS);
#endif

    CroStatus::Or<GpuBufferLayout> status_or_layout =
        auxiliary_frame_pool_->Initialize(
            image_processor->input_config().fourcc,
            image_processor->input_config().size, decoder_visible_rect,
            decoder_natural_size, num_pictures, use_protected);
    if (!status_or_layout.has_value()) {
      // A PlatformVideoFramePool should never abort initialization.
      DCHECK_NE(status_or_layout.code(), CroStatus::Codes::kResetRequired);
      DVLOGF(2) << "Could not initialize the auxiliary frame pool";
      return std::move(status_or_layout).error();
    }
  }
  // Note that fourcc is specified in ImageProcessor's factory method.
  auto fourcc = image_processor->input_config().fourcc;
  auto size = image_processor->input_config().size;
  size_t num_buffers;
  // We need to instantiate an ImageProcessor with a pool large enough to serve
  // the Renderer pipeline, it should be enough to use
  // |estimated_num_buffers_for_renderer_|. Experimentally it is not enough for
  // some ARM devices that are using ImageProcessor (b/264212288), hence set the
  // max from |estimated_num_buffers_for_renderer_| and empirically chosen
  // kMinImageProcessorOutputFramePoolSize.
  // TODO(b/270990622): Add VD renderer buffer count parameter and plumb it back
  // to clients
#if BUILDFLAG(USE_V4L2_CODEC)
  const size_t kMinImageProcessorOutputFramePoolSize = 10;
  num_buffers = std::max<size_t>(estimated_num_buffers_for_renderer_,
                                 kMinImageProcessorOutputFramePoolSize);
#else
  num_buffers = estimated_num_buffers_for_renderer_;
#endif

  // TODO(b/203240043): Verify that if we're using the image processor for tiled
  // to linear transformation, that the created frame pool is of linear format.
  // TODO(b/203240043): Add CHECKs to verify that the image processor is being
  // created for only valid use cases. Writing to a linear output buffer, e.g.
  VLOGF(2) << "Initializing Image Processor frame pool with up to "
           << num_buffers << " VideoFrames";
  auto status_or_image_processor = ImageProcessorWithPool::Create(
      std::move(image_processor), main_frame_pool_.get(), num_buffers,
      use_protected, decoder_task_runner_);
  if (!status_or_image_processor.has_value()) {
    DVLOGF(2) << "Unable to create ImageProcessorWithPool.";
    return std::move(status_or_image_processor).error();
  }

  image_processor_ = std::move(status_or_image_processor).value();
  VLOGF(2) << "ImageProcessor is created: " << image_processor_->backend_type();
  if (decoder_)
    decoder_->SetDmaIncoherentV4L2(image_processor_->SupportsIncoherentBufs());

  // TODO(b/203240043): Currently, the modifier is not read by any callers of
  // this function. We can eventually provide it by making it available to fetch
  // through the |image_processor|.
  return PixelLayoutCandidate{fourcc, size,
                              gfx::NativePixmapHandle::kNoModifier};
}

#if BUILDFLAG(IS_CHROMEOS)
void VideoDecoderPipeline::OnBufferTranscrypted(
    scoped_refptr<DecoderBuffer> transcrypted_buffer,
    DecodeCB decode_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DCHECK(!has_error_);
  if (!transcrypted_buffer) {
    OnError("Error in buffer transcryption");
    std::move(decode_callback).Run(DecoderStatus::Codes::kFailed);
    return;
  }

  if (drop_transcrypted_buffers_) {
    std::move(decode_callback).Run(DecoderStatus::Codes::kAborted);
    return;
  }

  decoder_->Decode(std::move(transcrypted_buffer), std::move(decode_callback));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace media
