chromium/media/gpu/v4l2/v4l2_video_decoder.cc

// 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/v4l2/v4l2_video_decoder.h"

#include <drm_fourcc.h>

#include <algorithm>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/video_types.h"
#include "media/base/video_util.h"
#include "media/gpu/chromeos/chromeos_status.h"
#include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
#include "media/gpu/chromeos/fourcc.h"
#include "media/gpu/chromeos/image_processor.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/chromeos/video_decoder_pipeline.h"
#include "media/gpu/gpu_video_decode_accelerator_helpers.h"
#include "media/gpu/macros.h"
#include "media/gpu/v4l2/legacy/v4l2_video_decoder_backend_stateful.h"
#include "media/gpu/v4l2/v4l2_status.h"
#include "media/gpu/v4l2/v4l2_utils.h"
#include "media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
// gn check does not account for BUILDFLAG(), so including this header will
// make gn check fail for builds other than ash-chrome. See gn help nogncheck
// for more information.
#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h"  // nogncheck
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

// TODO(jkardatzke): Remove these once they are in linux/videodev2.h.
#define V4L2_CID_MPEG_MTK_BASE (0x00990000 | 0x2000)
#define V4L2_CID_MPEG_MTK_GET_SECURE_HANDLE (V4L2_CID_MPEG_MTK_BASE + 8)
#define V4L2_CID_MPEG_MTK_SET_SECURE_MODE (V4L2_CID_MPEG_MTK_BASE + 9)

namespace media {

namespace {

using PixelLayoutCandidate = ImageProcessor::PixelLayoutCandidate;

// See http://crbug.com/255116.
constexpr int k480pArea = 720 * 480;
constexpr int k1080pArea = 1920 * 1088;
// We are aligning these with the Widevine spec for sample sizes for various
// resolutions. 1MB for SD, 2MB for HD and 4MB for UHD.
// Input bitstream buffer size for up to 480p streams.
constexpr size_t kInputBuferMaxSizeFor480p = 1024 * 1024;
// Input bitstream buffer size for up to 1080p streams.
constexpr size_t kInputBufferMaxSizeFor1080p = 2 * kInputBuferMaxSizeFor480p;
// Input bitstream buffer size for up to 4k streams.
constexpr size_t kInputBufferMaxSizeFor4k = 2 * kInputBufferMaxSizeFor1080p;

// Input format V4L2 fourccs this class supports.
const std::vector<uint32_t> kSupportedInputFourccs = {
    // V4L2 stateless formats
    V4L2_PIX_FMT_H264_SLICE,
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
    V4L2_PIX_FMT_HEVC_SLICE,
#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
    V4L2_PIX_FMT_VP8_FRAME,
    V4L2_PIX_FMT_VP9_FRAME,
    V4L2_PIX_FMT_AV1_FRAME,
    // V4L2 stateful formats
    V4L2_PIX_FMT_H264,
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
    V4L2_PIX_FMT_HEVC,
#endif  // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
    V4L2_PIX_FMT_VP8,
    V4L2_PIX_FMT_VP9,
    V4L2_PIX_FMT_AV1,
};

// These values are logged to UMA. Entries should not be renumbered and numeric
// values should never be reused. Please keep in sync with
// "V4l2VideoDecoderFunctions" in src/tools/metrics/histograms/enums.xml.
enum class V4l2VideoDecoderFunctions {
  kInitializeBackend = 0,
  kSetupInputFormat = 1,
  kSetupOutputFormat = 2,
  kStartStreamV4L2Queue = 3,
  kStopStreamV4L2Queue = 4,
  // Anything else is captured in this last entry.
  kOtherV4l2VideoDecoderFunction = 5,
  kMaxValue = kOtherV4l2VideoDecoderFunction,
};

constexpr std::array<const char*,
                     static_cast<size_t>(V4l2VideoDecoderFunctions::kMaxValue) +
                         1>
    kV4l2VideoDecoderFunctionNames = {
        "InitializeBackend",   "SetupInputFormat",
        "SetupOutputFormat",   "StartStreamV4L2Queue",
        "StopStreamV4L2Queue", "Other V4l2VideoDecoder functions"};

// Translates |function| into a human readable string for logging.
const char* V4l2VideoDecoderFunctionName(V4l2VideoDecoderFunctions function) {
  CHECK(function <= V4l2VideoDecoderFunctions::kMaxValue);
  return kV4l2VideoDecoderFunctionNames[static_cast<size_t>(function)];
}

// Logs and records UMA when member functions of V4l2VideoDecoder class fail.
void LogAndRecordUMA(const base::Location& location,
                     V4l2VideoDecoderFunctions function,
                     const std::string& message = "") {
  LOG(ERROR) << V4l2VideoDecoderFunctionName(function) << " failed at "
             << location.ToString() << (message.empty() ? " " : " : ")
             << message;
  base::UmaHistogramEnumeration("Media.V4l2VideoDecoder.Error", function);
}

size_t GetInputBufferSizeForResolution(const gfx::Size& resolution) {
  auto area = resolution.GetArea();
  if (area < k480pArea) {
    return kInputBuferMaxSizeFor480p;
  } else if (area < k1080pArea) {
    return kInputBufferMaxSizeFor1080p;
  } else {
    return kInputBufferMaxSizeFor4k;
  }
}

}  // namespace

// static
base::AtomicRefCount V4L2VideoDecoder::num_instances_(0);

// static
std::unique_ptr<VideoDecoderMixin> V4L2VideoDecoder::Create(
    std::unique_ptr<MediaLog> media_log,
    scoped_refptr<base::SequencedTaskRunner> decoder_task_runner,
    base::WeakPtr<VideoDecoderMixin::Client> client) {
  DCHECK(decoder_task_runner->RunsTasksInCurrentSequence());
  DCHECK(client);

  return base::WrapUnique<VideoDecoderMixin>(
      new V4L2VideoDecoder(std::move(media_log), std::move(decoder_task_runner),
                           std::move(client), new V4L2Device()));
}

// static
std::optional<SupportedVideoDecoderConfigs>
V4L2VideoDecoder::GetSupportedConfigs() {
  auto device = base::MakeRefCounted<V4L2Device>();
  auto configs = device->GetSupportedDecodeProfiles(kSupportedInputFourccs);
  if (configs.empty())
    return std::nullopt;

  return ConvertFromSupportedProfiles(configs,
#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
                                      true /* allow_encrypted */);
#else
                                      false /* allow_encrypted */);
#endif
}

V4L2VideoDecoder::V4L2VideoDecoder(
    std::unique_ptr<MediaLog> media_log,
    scoped_refptr<base::SequencedTaskRunner> decoder_task_runner,
    base::WeakPtr<VideoDecoderMixin::Client> client,
    scoped_refptr<V4L2Device> device)
    : VideoDecoderMixin(std::move(media_log),
                        std::move(decoder_task_runner),
                        std::move(client)),
      device_(std::move(device)),
      weak_this_for_polling_factory_(this) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  VLOGF(2);

  weak_this_for_polling_ = weak_this_for_polling_factory_.GetWeakPtr();
}

V4L2VideoDecoder::~V4L2VideoDecoder() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(2);

  // Call all pending decode callback.
  if (backend_) {
    backend_->ClearPendingRequests(DecoderStatus::Codes::kAborted);
    backend_ = nullptr;
  }

  // Stop and Destroy device.
  if (!StopStreamV4L2Queue(true)) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kStopStreamV4L2Queue);
  }

  if (input_queue_) {
    if (!input_queue_->DeallocateBuffers())
      VLOGF(1) << "Failed to deallocate V4L2 input buffers";
    input_queue_ = nullptr;
  }
  if (output_queue_) {
    if (!output_queue_->DeallocateBuffers())
      VLOGF(1) << "Failed to deallocate V4L2 output buffers";
    output_queue_ = nullptr;
  }

  weak_this_for_polling_factory_.InvalidateWeakPtrs();

  if (can_use_decoder_)
    num_instances_.Decrement();
}

void V4L2VideoDecoder::Initialize(const VideoDecoderConfig& config,
                                  bool /*low_delay*/,
                                  CdmContext* cdm_context,
                                  InitCB init_cb,
                                  const PipelineOutputCB& output_cb,
                                  const WaitingCB& /*waiting_cb*/) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DCHECK(config.IsValidConfig());
  DVLOGF(3);

  switch (state_) {
    case State::kUninitialized:
    case State::kInitialized:
    case State::kDecoding:
      // Expected state, do nothing.
      break;
    case State::kFlushing:
    case State::kError:
      VLOGF(1) << "V4L2 decoder should not be initialized at state: "
               << static_cast<int>(state_);
      std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
      return;
  }

  cdm_context_ref_ = nullptr;

  if (config.is_encrypted()) {
#if !BUILDFLAG(IS_CHROMEOS_ASH)
    VLOGF(1) << "Encrypted content is not supported";
    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
    return;
#else
    if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
      VLOGF(1) << "Cannot support encrypted stream w/out ChromeOsCdmContext";
      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
      return;
    }
    if (config.codec() != VideoCodec::kH264 &&
        config.codec() != VideoCodec::kVP9 &&
        config.codec() != VideoCodec::kAV1 &&
        config.codec() != VideoCodec::kHEVC) {
      VLOGF(1) << GetCodecName(config.codec())
               << " is not supported for encrypted content";
      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
      return;
    }
    cdm_context_ref_ = cdm_context->GetChromeOsCdmContext()->GetCdmContextRef();
#endif
  }

  // In the decoding state, we need to stop the queues since they have been
  // started already.
  if (state_ == State::kDecoding) {
    if (!StopStreamV4L2Queue(true)) {
      LogAndRecordUMA(FROM_HERE,
                      V4l2VideoDecoderFunctions::kStopStreamV4L2Queue);

      // TODO(crbug.com/40139291): Make StopStreamV4L2Queue return a StatusOr,
      // and pipe that back instead.
      std::move(init_cb).Run(
          DecoderStatus(DecoderStatus::Codes::kNotInitialized)
              .AddCause(
                  V4L2Status(V4L2Status::Codes::kFailedToStopStreamQueue)));
      return;
    }
  }
  // In the decoding or initialized state, we need to tear everything else down
  // as well.
  if (state_ == State::kDecoding || state_ == State::kInitialized) {
    if (!input_queue_->DeallocateBuffers() ||
        !output_queue_->DeallocateBuffers()) {
      VLOGF(1) << "Failed to deallocate V4L2 buffers";
      std::move(init_cb).Run(
          DecoderStatus(DecoderStatus::Codes::kNotInitialized)
              .AddCause(
                  V4L2Status(V4L2Status::Codes::kFailedToDestroyQueueBuffers)));
      return;
    }
    input_queue_ = nullptr;
    output_queue_ = nullptr;

    if (can_use_decoder_) {
      num_instances_.Decrement();
      can_use_decoder_ = false;
    }

    device_ = base::MakeRefCounted<V4L2Device>();
    continue_change_resolution_cb_.Reset();
    if (backend_)
      backend_ = nullptr;
  }
  if (config.is_encrypted()) {
    device_->set_secure_allocate_cb(
        base::BindRepeating(&V4L2VideoDecoder::AllocateSecureBuffer,
                            weak_this_for_callbacks_.GetWeakPtr()));
  } else {
    device_->set_secure_allocate_cb(AllocateSecureBufferAsCallback());
  }

  DCHECK(!input_queue_);
  DCHECK(!output_queue_);

  profile_ = config.profile();
  aspect_ratio_ = config.aspect_ratio();
  color_space_ = config.color_space_info();
  current_resolution_ = config.visible_rect().size();

  if (profile_ == VIDEO_CODEC_PROFILE_UNKNOWN) {
    VLOGF(1) << "Unknown profile.";
    SetState(State::kError);
    std::move(init_cb).Run(
        DecoderStatus(DecoderStatus::Codes::kNotInitialized)
            .AddCause(V4L2Status(V4L2Status::Codes::kNoProfile)));
    return;
  }
  if (VideoCodecProfileToVideoCodec(profile_) == VideoCodec::kAV1 &&
      !base::FeatureList::IsEnabled(kChromeOSHWAV1Decoder)) {
    VLOGF(1) << "AV1 hardware video decoding is disabled";
    std::move(init_cb).Run(
        DecoderStatus(DecoderStatus::Codes::kNotInitialized)
            .AddCause(V4L2Status(V4L2Status::Codes::kNoProfile)));
    return;
  }

  V4L2Status status = InitializeBackend();
  if (status != V4L2Status::Codes::kOk) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kInitializeBackend);

    SetState(State::kError);
    std::move(init_cb).Run(DecoderStatus(DecoderStatus::Codes::kNotInitialized)
                               .AddCause(std::move(status)));
    return;
  }

  // Call init_cb.
  output_cb_ = std::move(output_cb);

  // Call init_cb
  if (pending_secure_allocate_callbacks_) {
    // We need to wait for these to complete before we invoke the init callback.
    pending_init_cb_ = std::move(init_cb);
    return;
  }
  SetState(State::kInitialized);
  std::move(init_cb).Run(DecoderStatus::Codes::kOk);
}

bool V4L2VideoDecoder::NeedsBitstreamConversion() const {
  DCHECK(output_cb_) << "V4L2VideoDecoder hasn't been initialized";
  NOTREACHED_IN_MIGRATION();
  return (profile_ >= H264PROFILE_MIN && profile_ <= H264PROFILE_MAX) ||
         (profile_ >= HEVCPROFILE_MIN && profile_ <= HEVCPROFILE_MAX);
}

bool V4L2VideoDecoder::CanReadWithoutStalling() const {
  NOTIMPLEMENTED();
  NOTREACHED();
}

int V4L2VideoDecoder::GetMaxDecodeRequests() const {
  NOTREACHED();
}

VideoDecoderType V4L2VideoDecoder::GetDecoderType() const {
  return VideoDecoderType::kV4L2;
}

bool V4L2VideoDecoder::IsPlatformDecoder() const {
  return true;
}

V4L2Status V4L2VideoDecoder::InitializeBackend() {
  DVLOGF(3);
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  can_use_decoder_ =
      num_instances_.Increment() < kMaxNumOfInstances ||
      !base::FeatureList::IsEnabled(media::kLimitConcurrentDecoderInstances);
  if (!can_use_decoder_) {
    VLOGF(1) << "Reached maximum number of decoder instances ("
             << kMaxNumOfInstances << ")";
    return V4L2Status::Codes::kMaxDecoderInstanceCount;
  }

  constexpr bool kStateful = false;
  constexpr bool kStateless = true;
  std::optional<std::pair<bool, uint32_t>> api_and_format;
  // Try both kStateful and kStateless APIs via |fourcc| and select the first
  // combination where Open()ing the |device_| works.
  for (const auto api : {kStateful, kStateless}) {
    const auto fourcc = VideoCodecProfileToV4L2PixFmt(profile_, api);
    if (fourcc == V4L2_PIX_FMT_INVALID ||
        !device_->Open(V4L2Device::Type::kDecoder, fourcc)) {
      continue;
    }
    api_and_format = std::make_pair(api, fourcc);
    break;
  }

  if (!api_and_format.has_value()) {
    num_instances_.Decrement();
    can_use_decoder_ = false;
    VLOGF(1) << "No V4L2 API found for profile: " << GetProfileName(profile_);
    return V4L2Status::Codes::kNoDriverSupportForFourcc;
  }

  // TODO(jkardatzke): Remove this when we switch to using the V4L2 secure
  // memory flag. This CTRL is not in the ToT kernel, so only set this when we
  // are doing secure playback (which has dependent code also not in the ToT
  // kernel).
#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
  if (cdm_context_ref_) {
    // Set SVP (secure video pipeline) mode.
    struct v4l2_ext_control ctrl;
    struct v4l2_ext_controls ctrls;
    memset(&ctrls, 0, sizeof(ctrls));
    memset(&ctrl, 0, sizeof(ctrl));
    ctrl.id = V4L2_CID_MPEG_MTK_SET_SECURE_MODE;
    ctrl.value = 1;

    ctrls.count = 1;
    ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
    ctrls.controls = &ctrl;

    VLOGF(1) << "Setting secure playback mode";
    if (device_->Ioctl(VIDIOC_S_EXT_CTRLS, &ctrls)) {
      PLOG(ERROR) << "Failed setting secure playback mode";
    }
  }
#endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)

  struct v4l2_capability caps;
  const __u32 kCapsRequired = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
  if (device_->Ioctl(VIDIOC_QUERYCAP, &caps) ||
      (caps.capabilities & kCapsRequired) != kCapsRequired) {
    VLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP, "
             << "caps check failed: 0x" << std::hex << caps.capabilities;
    return V4L2Status::Codes::kFailedFileCapabilitiesCheck;
  }

  // Create Input/Output V4L2Queue
  input_queue_ = device_->GetQueue(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
  output_queue_ = device_->GetQueue(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
  if (!input_queue_ || !output_queue_) {
    VLOGF(1) << "Failed to create V4L2 queue.";
    return V4L2Status::Codes::kFailedResourceAllocation;
  }

  const auto preferred_api_and_format = api_and_format.value();
  input_format_fourcc_ = preferred_api_and_format.second;
  if (preferred_api_and_format.first == kStateful) {
    VLOGF(1) << "Using a stateful API for profile: " << GetProfileName(profile_)
             << " and fourcc: " << FourccToString(input_format_fourcc_);
    backend_ = std::make_unique<V4L2StatefulVideoDecoderBackend>(
        this, device_, profile_, color_space_, decoder_task_runner_);
  } else {
    DCHECK_EQ(preferred_api_and_format.first, kStateless);
    VLOGF(1) << "Using a stateless API for profile: "
             << GetProfileName(profile_)
             << " and fourcc: " << FourccToString(input_format_fourcc_);
    backend_ = std::make_unique<V4L2StatelessVideoDecoderBackend>(
        this, device_, profile_, color_space_, decoder_task_runner_,
        cdm_context_ref_ ? cdm_context_ref_->GetCdmContext() : nullptr);
  }

  if (!backend_->Initialize()) {
    VLOGF(1) << "Failed to initialize backend.";
    return V4L2Status::Codes::kFailedResourceAllocation;
  }

  if (!SetupInputFormat()) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kSetupInputFormat);
    return V4L2Status::Codes::kBadFormat;
  }

  if (!AllocateInputBuffers()) {
    VLOGF(1) << "Failed to allocate input buffer.";
    return V4L2Status::Codes::kFailedResourceAllocation;
  }

  return V4L2Status::Codes::kOk;
}

void V4L2VideoDecoder::AllocateSecureBuffer(uint32_t size,
                                            SecureBufferAllocatedCB callback) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  pending_secure_allocate_callbacks_++;
  // Wrap this with a default handler if it gets dropped somehow or otherwise we
  // could hang waiting to finish init.
  cdm_context_ref_->GetCdmContext()
      ->GetChromeOsCdmContext()
      ->AllocateSecureBuffer(
          size,
          mojo::WrapCallbackWithDefaultInvokeIfNotRun(
              base::BindPostTaskToCurrentDefault(base::BindOnce(
                  &V4L2VideoDecoder::AllocateSecureBufferCB,
                  weak_this_for_callbacks_.GetWeakPtr(), std::move(callback))),
              mojo::PlatformHandle()));
#else
  NOTREACHED_IN_MIGRATION();
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

void V4L2VideoDecoder::AllocateSecureBufferCB(SecureBufferAllocatedCB callback,
                                              mojo::PlatformHandle mojo_fd) {
  if (state_ == State::kError) {
    // Drop this and return, we've already entered the error state from a prior
    // failed callback so we have nothing to do.
    return;
  }
  if (!mojo_fd.is_valid()) {
    LOG(ERROR) << "Invalid Mojo FD returned for secure buffer allocation";
    SetState(State::kError);
    std::move(pending_init_cb_)
        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
    return;
  }
  base::ScopedFD secure_fd = mojo_fd.TakeFD();
  if (!secure_fd.is_valid()) {
    LOG(ERROR) << "Invalid FD returned for secure buffer allocation";
    SetState(State::kError);
    std::move(pending_init_cb_)
        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
    return;
  }

  // Also resolve the secure handle in case failure occurs there, then we know
  // what we pass into the callback is all valid.
  struct v4l2_ext_control ctrl;
  struct v4l2_ext_controls ctrls;
  memset(&ctrls, 0, sizeof(ctrls));
  memset(&ctrl, 0, sizeof(ctrl));
  ctrl.id = V4L2_CID_MPEG_MTK_GET_SECURE_HANDLE;
  ctrl.value = secure_fd.get();

  ctrls.count = 1;
  ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
  ctrls.controls = &ctrl;

  if (device_->Ioctl(VIDIOC_S_EXT_CTRLS, &ctrls)) {
    RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocSExtCtrls);
    PLOG(ERROR) << "Failed getting secure buffer identifier for FD "
                << secure_fd.get();
    SetState(State::kError);
    std::move(pending_init_cb_)
        .Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
    return;
  }
  uint64_t secure_handle = static_cast<uint64_t>(ctrl.value);

  // We have the secure buffer and secure handle, pass it into the callback.
  std::move(callback).Run(std::move(secure_fd), secure_handle);

  CHECK_GT(pending_secure_allocate_callbacks_, 0u);
  pending_secure_allocate_callbacks_--;
  if (!pending_secure_allocate_callbacks_) {
    if (pending_init_cb_) {
      // This is from the initial secure buffer allocations during Initialize().
      SetState(State::kInitialized);
      std::move(pending_init_cb_).Run(DecoderStatus::Codes::kOk);
    } else {
      // This is from the secure buffer allocations due to a resolution change.
      OnChangeResolutionDone(pending_change_resolution_done_status_);
    }
  }
}

bool V4L2VideoDecoder::SetupInputFormat() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  // Check if the format is supported.
  const auto v4l2_codecs_as_pix_fmts = EnumerateSupportedPixFmts(
      base::BindRepeating(&V4L2Device::Ioctl, device_),
      V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
  if (!base::Contains(v4l2_codecs_as_pix_fmts, input_format_fourcc_)) {
    DVLOGF(1) << FourccToString(input_format_fourcc_)
              << " not recognised, skipping...";
    return false;
  }
  VLOGF(1) << "Input (OUTPUT queue) Fourcc: "
           << FourccToString(input_format_fourcc_);

  // Determine the input buffer size, for backends that support stopping the
  // input queue on resolution changes this can be dynamic; otherwise we need to
  // calculate it based on the maximum frame size the decoder can handle.
  size_t input_size;
  if (backend_->StopInputQueueOnResChange()) {
    input_size = GetInputBufferSizeForResolution(current_resolution_);
  } else {
    gfx::Size max_size, min_size;
    GetSupportedResolution(base::BindRepeating(&V4L2Device::Ioctl, device_),
                           input_format_fourcc_, &min_size, &max_size);
    input_size = GetInputBufferSizeForResolution(max_size);
  }

  // Setup the input format.
  auto format =
      input_queue_->SetFormat(input_format_fourcc_, gfx::Size(), input_size);
  if (!format) {
    VPLOGF(1) << "Failed to call IOCTL to set input format.";
    return false;
  }
  DCHECK_EQ(format->fmt.pix_mp.pixelformat, input_format_fourcc_)
      << "The input (OUTPUT) queue must accept the requested pixel format "
      << FourccToString(input_format_fourcc_) << ", but it returned instead: "
      << FourccToString(format->fmt.pix_mp.pixelformat);

  return true;
}

bool V4L2VideoDecoder::AllocateInputBuffers() {
  const size_t num_OUTPUT_buffers =
      backend_->GetNumOUTPUTQueueBuffers(!!cdm_context_ref_);
  // Secure playback uses dmabufs for the OUTPUT queue, otherwise we use mmap
  // buffers.
  v4l2_memory input_queue_memory =
      !!cdm_context_ref_ ? V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP;
  VLOGF(1) << "Requesting: " << num_OUTPUT_buffers << " OUTPUT buffers of type "
           << (input_queue_memory == V4L2_MEMORY_MMAP ? "V4L2_MEMORY_MMAP"
                                                      : "V4L2_MEMORY_DMABUF");
  return input_queue_->AllocateBuffers(num_OUTPUT_buffers, input_queue_memory,
                                       incoherent_) != 0;
}

CroStatus V4L2VideoDecoder::SetupOutputFormat(const gfx::Size& size,
                                              const gfx::Rect& visible_rect,
                                              size_t num_codec_reference_frames,
                                              uint8_t bit_depth) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3) << "size: " << size.ToString()
            << ", visible_rect: " << visible_rect.ToString();

  if (bit_depth == 10u) {
    VLOGF(1) << "10-bit format, need to set EXT_CTRLS first";
    CroStatus ext_status = SetExtCtrls10Bit(size);
    if (ext_status != CroStatus::Codes::kOk) {
      return ext_status;
    }
  }

  const auto v4l2_pix_fmts = EnumerateSupportedPixFmts(
      base::BindRepeating(&V4L2Device::Ioctl, device_),
      V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);

  std::vector<PixelLayoutCandidate> candidates;
  for (const uint32_t& pixfmt : v4l2_pix_fmts) {
    const auto candidate = Fourcc::FromV4L2PixFmt(pixfmt);
    if (!candidate) {
      DVLOGF(1) << FourccToString(pixfmt) << " is not recognised, skipping...";
      continue;
    }
    VLOGF(1) << "Output (CAPTURE queue) candidate: " << candidate->ToString();

    // Some drivers will enumerate all possible formats for a video stream.
    // If the compressed video stream is 10 bits, the driver will enumerate both
    // P010 and NV12, and then down sample to NV12 if it is selected. This is
    // not desired, so drop the candidates that don't match the bit depth of the
    // stream.
    size_t candidate_bit_depth = BitDepth(candidate->ToVideoPixelFormat());
    if (candidate_bit_depth != bit_depth) {
      DVLOGF(1) << "Enumerated format " << candidate->ToString()
                << " with a bit depth of " << candidate_bit_depth
                << " removed from consideration because it does not match"
                << " the bit depth returned by the backend of "
                << base::strict_cast<size_t>(bit_depth);
      continue;
    }

    std::optional<struct v4l2_format> format =
        output_queue_->TryFormat(pixfmt, size, 0);
    if (!format)
      continue;

    gfx::Size adjusted_size(format->fmt.pix_mp.width,
                            format->fmt.pix_mp.height);

    candidates.emplace_back(
        PixelLayoutCandidate{.fourcc = *candidate, .size = adjusted_size});
  }

  // Ask the pipeline to pick the output format.
  CroStatus::Or<PixelLayoutCandidate> status_or_output_format =
      client_->PickDecoderOutputFormat(
          candidates, visible_rect, aspect_ratio_.GetNaturalSize(visible_rect),
          /*output_size=*/std::nullopt, num_codec_reference_frames,
          /*use_protected=*/!!cdm_context_ref_, /*need_aux_frame_pool=*/false,
          std::nullopt);
  if (!status_or_output_format.has_value()) {
    VLOGF(1) << "Failed to pick an output format.";
    return std::move(status_or_output_format).error().code();
  }
  const PixelLayoutCandidate output_format =
      std::move(status_or_output_format).value();
  Fourcc fourcc = std::move(output_format.fourcc);
  gfx::Size picked_size = std::move(output_format.size);

  // We successfully picked the output format. Now setup output format again.
  std::optional<struct v4l2_format> format =
      output_queue_->SetFormat(fourcc.ToV4L2PixFmt(), picked_size, 0);
  DCHECK(format);
  gfx::Size adjusted_size(format->fmt.pix_mp.width, format->fmt.pix_mp.height);
  if (!gfx::Rect(adjusted_size).Contains(gfx::Rect(picked_size))) {
    VLOGF(1) << "The adjusted coded size (" << adjusted_size.ToString()
             << ") should contains the original coded size("
             << picked_size.ToString() << ").";
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  // Got the adjusted size from the V4L2 driver. Now setup the frame pool.
  // TODO(akahuang): It is possible there is an allocatable formats among
  // candidates, but PickDecoderOutputFormat() selects other non-allocatable
  // format. The correct flow is to attach an info to candidates if it is
  // created by VideoFramePool.
  DmabufVideoFramePool* pool = client_->GetVideoFramePool();
  if (pool) {
    std::optional<GpuBufferLayout> layout = pool->GetGpuBufferLayout();
    if (!layout.has_value()) {
      VLOGF(1) << "Failed to get format from VFPool";
      return CroStatus::Codes::kFailedToChangeResolution;
    }

    if (layout->size() != adjusted_size) {
      VLOGF(1) << "The size adjusted by VFPool is different from one "
               << "adjusted by a video driver. fourcc: " << fourcc.ToString()
               << ", (video driver v.s. VFPool) " << adjusted_size.ToString()
               << " != " << layout->size().ToString();
      return CroStatus::Codes::kFailedToChangeResolution;
    }

    VLOGF(1) << "buffer modifier: " << std::hex << layout->modifier();
    if (layout->modifier() != DRM_FORMAT_MOD_LINEAR &&
        layout->modifier() != gfx::NativePixmapHandle::kNoModifier) {
      std::optional<struct v4l2_format> modifier_format =
          output_queue_->SetModifierFormat(layout->modifier(), picked_size);
      if (!modifier_format)
        return CroStatus::Codes::kFailedToChangeResolution;

      gfx::Size size_for_modifier_format(format->fmt.pix_mp.width,
                                         format->fmt.pix_mp.height);
      if (size_for_modifier_format != adjusted_size) {
        VLOGF(1)
            << "Buffers were allocated for " << adjusted_size.ToString()
            << " but modifier format is expecting buffers to be allocated for "
            << size_for_modifier_format.ToString();
        return CroStatus::Codes::kFailedToChangeResolution;
      }
    }
  }

  return CroStatus::Codes::kOk;
}

CroStatus V4L2VideoDecoder::SetExtCtrls10Bit(const gfx::Size& size) {
  std::vector<struct v4l2_ext_control> ctrls;
  struct v4l2_ctrl_hevc_sps v4l2_sps;
  struct v4l2_ctrl_vp9_frame v4l2_vp9_frame;
#if BUILDFLAG(IS_CHROMEOS)
  struct v4l2_ctrl_av1_sequence v4l2_av1_sequence;
#endif

  struct v4l2_ext_control ctrl;
  memset(&ctrl, 0, sizeof(ctrl));

  // 10-bit formats require codec specific parameters be passed before the
  // CAPTURE queue will report the proper decoded formats.
  if (input_format_fourcc_ == V4L2_PIX_FMT_HEVC_SLICE) {
    // For HEVC the SPS data is sent in to indicate 10-bit content. We also set
    // the size and chroma format since that should be all the information
    // needed in order to know the format.
    VLOGF(1) << "Setting EXT_CTRLS for 10-bit HEVC";
    memset(&v4l2_sps, 0, sizeof(v4l2_sps));
    v4l2_sps.pic_width_in_luma_samples = size.width();
    v4l2_sps.pic_height_in_luma_samples = size.height();
    v4l2_sps.bit_depth_luma_minus8 = 2;
    v4l2_sps.bit_depth_chroma_minus8 = 2;
    v4l2_sps.chroma_format_idc = 1;  // 4:2:0

    ctrl.id = V4L2_CID_STATELESS_HEVC_SPS;
    ctrl.size = sizeof(v4l2_sps);
    ctrl.ptr = &v4l2_sps;

    ctrls.push_back(ctrl);
  } else if (input_format_fourcc_ == V4L2_PIX_FMT_VP9_FRAME) {
    // VP9 requires the profile (only profile 2), bit depth , and flags
    VLOGF(1) << "Setting EXT_CTRLS for 10-bit VP9.2";
    memset(&v4l2_vp9_frame, 0, sizeof(v4l2_vp9_frame));
    v4l2_vp9_frame.bit_depth = 10;
    v4l2_vp9_frame.profile = 2;
    v4l2_vp9_frame.flags =
        V4L2_VP9_FRAME_FLAG_X_SUBSAMPLING | V4L2_VP9_FRAME_FLAG_Y_SUBSAMPLING;

    ctrl.id = V4L2_CID_STATELESS_VP9_FRAME;
    ctrl.size = sizeof(v4l2_vp9_frame);
    ctrl.ptr = &v4l2_vp9_frame;

    ctrls.push_back(ctrl);
#if BUILDFLAG(IS_CHROMEOS)
  } else if (input_format_fourcc_ == V4L2_PIX_FMT_AV1_FRAME) {
    // AV1 only requires that the |bit_depth| parameter be set to enable
    // 10 bit formats on the CAPTURE queue.
    VLOGF(1) << "Setting EXT_CTRLS for 10-bit AV1";
    memset(&v4l2_av1_sequence, 0, sizeof(v4l2_av1_sequence));
    v4l2_av1_sequence.bit_depth = 10;

    ctrl.id = V4L2_CID_STATELESS_AV1_SEQUENCE;
    ctrl.size = sizeof(v4l2_av1_sequence);
    ctrl.ptr = &v4l2_av1_sequence;

    ctrls.push_back(ctrl);
#endif
  } else {
    // TODO(b/): Add other 10-bit codecs
    return CroStatus::Codes::kNoDecoderOutputFormatCandidates;
  }

  struct v4l2_ext_controls ext_ctrls;
  memset(&ext_ctrls, 0, sizeof(ext_ctrls));
  ext_ctrls.count = ctrls.size();
  ext_ctrls.controls = ctrls.data();
  ext_ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
  ext_ctrls.request_fd = -1;
  if (device_->Ioctl(VIDIOC_S_EXT_CTRLS, &ext_ctrls) != 0) {
    VPLOGF(1) << "ioctl() failed: VIDIOC_S_EXT_CTRLS";
    return CroStatus::Codes::kNoDecoderOutputFormatCandidates;
  }

  return CroStatus::Codes::kOk;
}

void V4L2VideoDecoder::Reset(base::OnceClosure closure) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  // In order to preserve the order of the callbacks between Decode() and
  // Reset(), we also trampoline the callback of Reset().
  auto trampoline_reset_cb =
      base::BindOnce(&base::SequencedTaskRunner::PostTask,
                     base::SequencedTaskRunner::GetCurrentDefault(), FROM_HERE,
                     std::move(closure));

  if (state_ == State::kInitialized) {
    std::move(trampoline_reset_cb).Run();
    return;
  }
  if (!backend_) {
    VLOGF(1) << "Backend was destroyed while resetting.";
    SetState(State::kError);
    return;
  }

  // Reset callback for resolution change, because the pipeline won't notify
  // flushed after reset.
  if (continue_change_resolution_cb_) {
    continue_change_resolution_cb_.Reset();
    backend_->OnChangeResolutionDone(CroStatus::Codes::kResetRequired);
  }

  // Call all pending decode callback.
  backend_->ClearPendingRequests(DecoderStatus::Codes::kAborted);

  // Streamoff V4L2 queues to drop input and output buffers.
  RestartStream();

  // If during flushing, Reset() will abort the following flush tasks.
  // Now we are ready to decode new buffer. Go back to decoding state.
  SetState(State::kDecoding);

  std::move(trampoline_reset_cb).Run();
}

void V4L2VideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
                              DecodeCB decode_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DCHECK_NE(state_, State::kUninitialized);

  // VideoDecoder interface: |decode_cb| can't be called from within Decode().
  auto trampoline_decode_cb = base::BindOnce(
      [](const scoped_refptr<base::SequencedTaskRunner>& this_sequence_runner,
         DecodeCB decode_cb, DecoderStatus status) {
        this_sequence_runner->PostTask(
            FROM_HERE, base::BindOnce(std::move(decode_cb), status));
      },
      base::SequencedTaskRunner::GetCurrentDefault(), std::move(decode_cb));

  if (state_ == State::kError) {
    std::move(trampoline_decode_cb).Run(DecoderStatus::Codes::kFailed);
    return;
  }

  if (state_ == State::kInitialized) {
    // Start streaming input queue and polling. This is required for the
    // stateful decoder, and doesn't hurt for the stateless one.
    if (!StartStreamV4L2Queue(false)) {
      LogAndRecordUMA(FROM_HERE,
                      V4l2VideoDecoderFunctions::kStartStreamV4L2Queue);
      SetState(State::kError);
      std::move(trampoline_decode_cb)
          .Run(DecoderStatus(DecoderStatus::Codes::kFailed)
                   .AddCause(V4L2Status(
                       V4L2Status::Codes::kFailedToStartStreamQueue)));
      return;
    }
    SetState(State::kDecoding);
  }

  backend_->EnqueueDecodeTask(std::move(buffer),
                              std::move(trampoline_decode_cb));
}

bool V4L2VideoDecoder::StartStreamV4L2Queue(bool start_output_queue) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  if (!input_queue_->Streamon() ||
      (start_output_queue && !output_queue_->Streamon())) {
    VLOGF(1) << "Failed to streamon V4L2 queue.";
    SetState(State::kError);
    return false;
  }

  if (!device_->StartPolling(
          base::BindRepeating(&V4L2VideoDecoder::ServiceDeviceTask,
                              weak_this_for_polling_),
          base::BindRepeating(&V4L2VideoDecoder::SetState,
                              weak_this_for_polling_, State::kError))) {
    SetState(State::kError);
    return false;
  }

  return true;
}

bool V4L2VideoDecoder::StopStreamV4L2Queue(bool stop_input_queue) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  if (!device_->StopPolling()) {
    SetState(State::kError);
    return false;
  }

  // Invalidate the callback from the device.
  weak_this_for_polling_factory_.InvalidateWeakPtrs();
  weak_this_for_polling_ = weak_this_for_polling_factory_.GetWeakPtr();

  // Streamoff input and output queue.
  if (input_queue_ && stop_input_queue && !input_queue_->Streamoff()) {
    SetState(State::kError);
    return false;
  }

  if (output_queue_ && !output_queue_->Streamoff()) {
    SetState(State::kError);
    return false;
  }

  if (backend_)
    backend_->OnStreamStopped(stop_input_queue);

  return true;
}

void V4L2VideoDecoder::InitiateFlush() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  SetState(State::kFlushing);
}

void V4L2VideoDecoder::CompleteFlush() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  if (state_ != State::kFlushing) {
    VLOGF(1) << "Completed flush in the wrong state: "
             << static_cast<int>(state_);
    SetState(State::kError);
  } else {
    SetState(State::kDecoding);
  }
}

void V4L2VideoDecoder::RestartStream() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  // If the queues are streaming before reset, then we need to start streaming
  // them after stopping.
  const bool is_input_streaming = input_queue_->IsStreaming();
  const bool is_output_streaming = output_queue_->IsStreaming();

  if (!StopStreamV4L2Queue(true)) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kStopStreamV4L2Queue);
    return;
  }

  if (is_input_streaming) {
    if (!StartStreamV4L2Queue(is_output_streaming)) {
      LogAndRecordUMA(FROM_HERE,
                      V4l2VideoDecoderFunctions::kStartStreamV4L2Queue);
      return;
    }
  }

  if (state_ != State::kDecoding)
    SetState(State::kDecoding);
}

void V4L2VideoDecoder::ChangeResolution(gfx::Size pic_size,
                                        gfx::Rect visible_rect,
                                        size_t num_codec_reference_frames,
                                        uint8_t bit_depth) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);
  DCHECK(!continue_change_resolution_cb_);

  // After the pipeline flushes all frames, we can start changing resolution.
  // base::Unretained() is safe because |continue_change_resolution_cb_| is
  // called inside the class, so the pointer must be valid.
  continue_change_resolution_cb_ =
      base::BindOnce(&V4L2VideoDecoder::ContinueChangeResolution,
                     base::Unretained(this), pic_size, visible_rect,
                     num_codec_reference_frames, bit_depth)
          .Then(base::BindOnce(&V4L2VideoDecoder::OnChangeResolutionDone,
                               base::Unretained(this)));

  DCHECK(client_);
  client_->PrepareChangeResolution();
}

void V4L2VideoDecoder::ApplyResolutionChange() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);
  DCHECK(continue_change_resolution_cb_);

  std::move(continue_change_resolution_cb_).Run();
}

size_t V4L2VideoDecoder::GetMaxOutputFramePoolSize() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  // VIDEO_MAX_FRAME is used as a size in V4L2 decoder drivers like Qualcomm
  // Venus. We should not exceed this limit for the frame pool that the decoder
  // writes into.
  return VIDEO_MAX_FRAME;
}

bool V4L2VideoDecoder::NeedsTranscryption() {
  return !!cdm_context_ref_;
}

CroStatus V4L2VideoDecoder::AttachSecureBuffer(
    scoped_refptr<DecoderBuffer>& buffer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  if (!cdm_context_ref_) {
    return CroStatus::Codes::kOk;
  }

  if (state_ == State::kError) {
    return CroStatus::Codes::kUnableToAllocateSecureBuffer;
  }

  auto secure_handle_or_error = input_queue_->GetFreeSecureHandle();
  if (!secure_handle_or_error.has_value()) {
    // This may only mean we are currently out of buffers, it's not necessarily
    // a fatal error.
    return std::move(secure_handle_or_error).error().code();
  }
  buffer->WritableSideData().secure_handle =
      std::move(secure_handle_or_error).value();
  return CroStatus::Codes::kOk;
}

void V4L2VideoDecoder::ReleaseSecureBuffer(uint64_t secure_handle) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  input_queue_->ReleaseSecureHandle(secure_handle);
}

CroStatus V4L2VideoDecoder::ContinueChangeResolution(
    const gfx::Size& pic_size,
    const gfx::Rect& visible_rect,
    const size_t num_codec_reference_frames,
    const uint8_t bit_depth) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  if (!backend_) {
    VLOGF(1) << "Backend was destroyed while changing resolution.";
    SetState(State::kError);
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  // If we already reset, then skip it.
  // TODO(akahuang): Revisit to check if this condition may happen or not.
  if (state_ != State::kFlushing)
    return CroStatus::Codes::kResetRequired;

  // Stateful decoders require the input queue to keep running during resolution
  // changes, but stateless ones require it to be stopped.
  if (!StopStreamV4L2Queue(backend_->StopInputQueueOnResChange())) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kStopStreamV4L2Queue);

    return CroStatus::Codes::kFailedToChangeResolution;
  }

  // See if we should also reallocate the input buffers. We only do this if the
  // backend supports stopping the input queue on resolution changes and the
  // buffer size would now be different.
  current_resolution_ = visible_rect.size();
  if (backend_->StopInputQueueOnResChange()) {
    size_t curr_buffer_size =
        input_queue_->GetMemoryUsage() / input_queue_->AllocatedBuffersCount();
    if (curr_buffer_size !=
        GetInputBufferSizeForResolution(current_resolution_)) {
      if (!input_queue_->DeallocateBuffers()) {
        SetState(State::kError);
        return CroStatus::Codes::kFailedToChangeResolution;
      }
      // Set the input format again, which will use the new buffer size.
      if (!SetupInputFormat()) {
        LogAndRecordUMA(FROM_HERE,
                        V4l2VideoDecoderFunctions::kSetupInputFormat);
        SetState(State::kError);
        return CroStatus::Codes::kFailedToChangeResolution;
      }
      if (!AllocateInputBuffers()) {
        VLOGF(1) << "Failed to allocate input buffers on resolution change";
        SetState(State::kError);
        return CroStatus::Codes::kFailedToChangeResolution;
      }
    }
  }

  if (!output_queue_->DeallocateBuffers()) {
    SetState(State::kError);
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  if (!backend_->ApplyResolution(pic_size, visible_rect)) {
    SetState(State::kError);
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  const CroStatus status = SetupOutputFormat(
      pic_size, visible_rect, num_codec_reference_frames, bit_depth);
  if (status == CroStatus::Codes::kResetRequired) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kSetupOutputFormat,
                    "SetupOutputFormat is aborted");

    return CroStatus::Codes::kResetRequired;
  }
  if (status != CroStatus::Codes::kOk) {
    LogAndRecordUMA(FROM_HERE, V4l2VideoDecoderFunctions::kSetupOutputFormat,
                    "Failed to setup output format, status= " +
                        base::NumberToString(static_cast<int>(status.code())));

    SetState(State::kError);
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  // If our |client_| has a VideoFramePool to allocate buffers for us, we'll
  // use it, otherwise we have to ask the driver.
  const bool use_v4l2_allocated_buffers = !client_->GetVideoFramePool();

  const v4l2_memory type =
      use_v4l2_allocated_buffers ? V4L2_MEMORY_MMAP : V4L2_MEMORY_DMABUF;
  // If we don't use driver-allocated buffers, request as many as possible
  // (VIDEO_MAX_FRAME) since they are shallow allocations. Otherwise, allocate
  // |num_codec_reference_frames| plus one for the video frame being decoded,
  // and one for our client (presumably an ImageProcessor).
  const size_t v4l2_num_buffers = use_v4l2_allocated_buffers
                                      ? num_codec_reference_frames + 2
                                      : VIDEO_MAX_FRAME;

  VLOGF(1) << "Requesting: " << v4l2_num_buffers << " CAPTURE buffers of type "
           << (use_v4l2_allocated_buffers ? "V4L2_MEMORY_MMAP"
                                          : "V4L2_MEMORY_DMABUF");

  const auto allocated_buffers =
      output_queue_->AllocateBuffers(v4l2_num_buffers, type, incoherent_);

  if (allocated_buffers < v4l2_num_buffers) {
    LOGF(ERROR) << "Failed to allocated enough CAPTURE buffers, requested: "
                << v4l2_num_buffers << " and got: " << allocated_buffers;
    SetState(State::kError);
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  if (!StartStreamV4L2Queue(true)) {
    LogAndRecordUMA(FROM_HERE,
                    V4l2VideoDecoderFunctions::kStartStreamV4L2Queue);

    SetState(State::kError);
    return CroStatus::Codes::kFailedToChangeResolution;
  }

  return CroStatus::Codes::kOk;
}

void V4L2VideoDecoder::OnChangeResolutionDone(CroStatus status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3) << static_cast<int>(status.code());

  if (!backend_) {
    // We don't need to set error state here because ContinueChangeResolution()
    // should have already done it if |backend_| is null.
    VLOGF(1) << "Backend was destroyed before resolution change finished.";
    return;
  }
  // If we still have secure buffer allocations pending, then wait until they
  // have completed before invoking the callback to complete the resolution
  // change.
  if (pending_secure_allocate_callbacks_) {
    pending_change_resolution_done_status_ = status;
    return;
  }
  backend_->OnChangeResolutionDone(status);
}

void V4L2VideoDecoder::ServiceDeviceTask(bool event) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);

  if (input_queue_ && output_queue_) {
    DVLOGF(3) << "Number of queued input buffers: "
              << input_queue_->QueuedBuffersCount()
              << ", Number of queued output buffers: "
              << output_queue_->QueuedBuffersCount();
    TRACE_COUNTER_ID2(
        "media,gpu", "V4L2 queue sizes", this, "input (OUTPUT_queue)",
        input_queue_->QueuedBuffersCount(), "output (CAPTURE_queue)",
        output_queue_->QueuedBuffersCount());
  }

  if (backend_)
    backend_->OnServiceDeviceTask(event);

  // Dequeue V4L2 output buffer first to reduce output latency.
  bool success;
  while (output_queue_ && output_queue_->QueuedBuffersCount() > 0) {
    V4L2ReadableBufferRef dequeued_buffer;

    std::tie(success, dequeued_buffer) = output_queue_->DequeueBuffer();
    if (!success) {
      SetState(State::kError);
      return;
    }
    if (!dequeued_buffer)
      break;

    if (backend_)
      backend_->OnOutputBufferDequeued(std::move(dequeued_buffer));
  }

  // Dequeue V4L2 input buffer.
  while (input_queue_ && input_queue_->QueuedBuffersCount() > 0) {
    V4L2ReadableBufferRef dequeued_buffer;

    std::tie(success, dequeued_buffer) = input_queue_->DequeueBuffer();
    if (!success) {
      SetState(State::kError);
      return;
    }
    if (!dequeued_buffer)
      break;
  }
}

void V4L2VideoDecoder::OutputFrame(scoped_refptr<FrameResource> frame,
                                   const gfx::Rect& visible_rect,
                                   const VideoColorSpace& color_space,
                                   base::TimeDelta timestamp) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4) << "timestamp: " << timestamp.InMilliseconds() << " msec";

  // Set the timestamp at which the decode operation started on the
  // |frame|. If the frame has been outputted before (e.g. because of VP9
  // show-existing-frame feature) we can't overwrite the timestamp directly, as
  // the original frame might still be in use. Instead we wrap the frame in
  // another frame with a different timestamp.
  if (frame->timestamp().is_zero())
    frame->set_timestamp(timestamp);

  if (frame->visible_rect() != visible_rect ||
      frame->timestamp() != timestamp) {
    gfx::Size natural_size = aspect_ratio_.GetNaturalSize(visible_rect);
    scoped_refptr<FrameResource> wrapped_frame =
        frame->CreateWrappingFrame(visible_rect, natural_size);
    wrapped_frame->set_timestamp(timestamp);

    frame = std::move(wrapped_frame);
  }

  frame->set_color_space(color_space.ToGfxColorSpace());

  output_cb_.Run(std::move(frame));
}

DmabufVideoFramePool* V4L2VideoDecoder::GetVideoFramePool() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(4);

  return client_->GetVideoFramePool();
}

void V4L2VideoDecoder::SetDmaIncoherentV4L2(bool incoherent) {
  incoherent_ = incoherent;
}

void V4L2VideoDecoder::SetState(State new_state) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3) << "Change state from " << static_cast<int>(state_) << " to "
            << static_cast<int>(new_state);

  if (state_ == new_state)
    return;
  if (state_ == State::kError) {
    DVLOGF(3) << "Already in kError state.";
    return;
  }

  // Check if the state transition is valid.
  switch (new_state) {
    case State::kUninitialized:
      VLOGF(1) << "Should not set to kUninitialized.";
      new_state = State::kError;
      break;

    case State::kInitialized:
      if ((state_ != State::kUninitialized) && (state_ != State::kDecoding)) {
        VLOGF(1) << "Can only transition to kInitialized from kUninitialized "
                    "or kDecoding";
        new_state = State::kError;
      }
      break;

    case State::kDecoding:
      break;

    case State::kFlushing:
      if (state_ != State::kDecoding) {
        VLOGF(1) << "kFlushing should only be set when kDecoding.";
        new_state = State::kError;
      }
      break;

    case State::kError:
      break;
  }

  // |StopStreamV4L2Queue()| can call |SetState()|.  Update |state_|
  // before calling so that calls to |SetState()| from
  // |StopStreamV4L2Queue()| return quickly.
  state_ = new_state;

  if (new_state == State::kError) {
    VLOGF(1) << "Error occurred, stopping queues.";
    if (!StopStreamV4L2Queue(true)) {
      LogAndRecordUMA(FROM_HERE,
                      V4l2VideoDecoderFunctions::kStopStreamV4L2Queue);
    }

    if (backend_)
      backend_->ClearPendingRequests(DecoderStatus::Codes::kFailed);
  }
}

void V4L2VideoDecoder::OnBackendError() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(2);

  SetState(State::kError);
}

bool V4L2VideoDecoder::IsDecoding() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_sequence_checker_);
  DVLOGF(3);

  return state_ == State::kDecoding;
}

}  // namespace media