910e62b5创建于 1月15日历史提交
// Copyright 2020 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 "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "gpu/config/gpu_driver_bug_workarounds.h"
#include "media/base/cdm_context.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/mock_media_log.h"
#include "media/base/status.h"
#include "media/base/video_decoder_config.h"
#include "media/gpu/chromeos/dmabuf_video_frame_pool.h"
#include "media/gpu/chromeos/frame_resource_converter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libdrm/src/include/drm/drm_fourcc.h"

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

using base::test::RunClosure;
using ::testing::_;
using ::testing::ByMove;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::TestWithParam;

namespace media {

using PixelLayoutCandidate = ImageProcessor::PixelLayoutCandidate;

MATCHER_P(MatchesStatusCode, status_code, "") {
  // media::Status doesn't provide an operator==(...), we add here a simple one.
  return arg.code() == status_code;
}

MATCHER_P(MatchesDecoderBuffer, buffer, "") {
  DCHECK(arg);
  return arg->MatchesForTesting(*buffer);
}

class FakeFrameResourceConverter : public FrameResourceConverter {
 public:
  static std::unique_ptr<FrameResourceConverter> Create() {
    return base::WrapUnique<FrameResourceConverter>(
        new FakeFrameResourceConverter());
  }

  FakeFrameResourceConverter(const FakeFrameResourceConverter&) = delete;
  FakeFrameResourceConverter& operator=(const FakeFrameResourceConverter&) =
      delete;

 private:
  FakeFrameResourceConverter() = default;
  ~FakeFrameResourceConverter() override = default;

  // FrameResourceConverter overrides.
  void ConvertFrameImpl(scoped_refptr<FrameResource> frame) override {
    // It is fine to output a nullptr VideoFrame. The output frames are not
    // checked.
    Output(nullptr);
  }
};

class MockVideoFramePool : public DmabufVideoFramePool {
 public:
  MockVideoFramePool() = default;
  ~MockVideoFramePool() override = default;

  // DmabufVideoFramePool implementation.
  MOCK_METHOD7(Initialize,
               CroStatus::Or<GpuBufferLayout>(const Fourcc&,
                                              const gfx::Size&,
                                              const gfx::Rect&,
                                              const gfx::Size&,
                                              size_t,
                                              bool,
                                              bool));
  MOCK_METHOD0(GetFrame, scoped_refptr<FrameResource>());
  MOCK_CONST_METHOD0(GetFrameStorageType, VideoFrame::StorageType());
  MOCK_METHOD0(IsExhausted, bool());
  MOCK_METHOD1(NotifyWhenFrameAvailable, void(base::OnceClosure));
  MOCK_METHOD0(ReleaseAllFrames, void());
  MOCK_METHOD0(GetGpuBufferLayout, std::optional<GpuBufferLayout>());

  bool IsFakeVideoFramePool() override { return true; }
};

class MockDecoder : public VideoDecoderMixin {
 public:
  MockDecoder()
      : VideoDecoderMixin(std::make_unique<MockMediaLog>(),
                          base::SequencedTaskRunner::GetCurrentDefault(),
                          base::WeakPtr<VideoDecoderMixin::Client>(nullptr)) {}
  ~MockDecoder() override = default;

  MOCK_METHOD6(Initialize,
               void(const VideoDecoderConfig&,
                    bool,
                    CdmContext*,
                    InitCB,
                    const PipelineOutputCB&,
                    const WaitingCB&));
  MOCK_METHOD2(Decode, void(scoped_refptr<DecoderBuffer>, DecodeCB));
  MOCK_METHOD1(Reset, void(base::OnceClosure));
  MOCK_METHOD0(ApplyResolutionChange, void());
  MOCK_METHOD0(NeedsTranscryption, bool());
  MOCK_METHOD1(AttachSecureBuffer, CroStatus(scoped_refptr<DecoderBuffer>&));
  MOCK_METHOD1(ReleaseSecureBuffer, void(uint64_t));
  MOCK_CONST_METHOD0(GetDecoderType, VideoDecoderType());
};

#if BUILDFLAG(IS_CHROMEOS)
constexpr uint8_t kEncryptedData[] = {1, 8, 9};
constexpr uint8_t kTranscryptedData[] = {9, 2, 4};
constexpr uint64_t kFakeSecureHandle = 75;
class MockChromeOsCdmContext : public chromeos::ChromeOsCdmContext {
 public:
  MockChromeOsCdmContext() : chromeos::ChromeOsCdmContext() {}
  ~MockChromeOsCdmContext() override = default;

  MOCK_METHOD3(GetHwKeyData,
               void(const DecryptConfig*,
                    const std::vector<uint8_t>&,
                    chromeos::ChromeOsCdmContext::GetHwKeyDataCB));
  MOCK_METHOD1(GetHwConfigData,
               void(chromeos::ChromeOsCdmContext::GetHwConfigDataCB));
  MOCK_METHOD1(GetScreenResolutions,
               void(chromeos::ChromeOsCdmContext::GetScreenResolutionsCB));
  MOCK_METHOD0(GetCdmContextRef, std::unique_ptr<CdmContextRef>());
  MOCK_CONST_METHOD0(UsingArcCdm, bool());
  MOCK_CONST_METHOD0(IsRemoteCdm, bool());
  MOCK_METHOD2(AllocateSecureBuffer,
               void(uint32_t,
                    chromeos::ChromeOsCdmContext::AllocateSecureBufferCB));
  MOCK_METHOD4(ParseEncryptedSliceHeader,
               void(uint64_t,
                    uint32_t,
                    const std::vector<uint8_t>&,
                    ParseEncryptedSliceHeaderCB));
};
// A real implementation of this class would actually hold onto a reference of
// the owner of the CdmContext to ensure it is not destructed before the
// CdmContextRef is destructed. For the tests here, we don't need to bother with
// that because the CdmContext is a class member declared before the
// VideoDecoderPipeline so the CdmContext will get destructed after what uses
// it.
class FakeCdmContextRef : public CdmContextRef {
 public:
  FakeCdmContextRef(CdmContext* cdm_context) : cdm_context_(cdm_context) {}
  ~FakeCdmContextRef() override = default;

  CdmContext* GetCdmContext() override { return cdm_context_; }

 private:
  raw_ptr<CdmContext> cdm_context_;
};
#endif  // BUILDFLAG(IS_CHROMEOS)

class MockImageProcessor : public ImageProcessor {
 public:
  explicit MockImageProcessor(
      scoped_refptr<base::SequencedTaskRunner> client_task_runner)
      : ImageProcessor(nullptr,
                       client_task_runner,
                       /*backend_task_runner=*/
                       base::ThreadPool::CreateSequencedTaskRunner({})) {}

  MOCK_CONST_METHOD0(input_config, const ImageProcessorBackend::PortConfig&());
  MOCK_CONST_METHOD0(output_config, const ImageProcessorBackend::PortConfig&());
};

struct DecoderPipelineTestParams {
  // GTest params need to be copyable; hence we need here a RepeatingCallback
  // version of VideoDecoderPipeline::CreateDecoderFunctionCB.
  using RepeatingCreateDecoderFunctionCB = base::RepeatingCallback<
      VideoDecoderPipeline::CreateDecoderFunctionCB::RunType>;
  RepeatingCreateDecoderFunctionCB create_decoder_function_cb;
  DecoderStatus::Codes status_code;
};

constexpr gfx::Size kMinSupportedResolution(64, 64);
constexpr gfx::Size kMaxSupportedResolution(2048, 1088);
constexpr gfx::Size kCodedSize(128, 128);

static_assert(kMinSupportedResolution.width() <= kCodedSize.width() &&
                  kMinSupportedResolution.height() <= kCodedSize.height() &&
                  kCodedSize.width() <= kMaxSupportedResolution.width() &&
                  kCodedSize.height() <= kMaxSupportedResolution.height(),
              "kCodedSize must be within the supported resolutions.");

class VideoDecoderPipelineTest
    : public testing::TestWithParam<DecoderPipelineTestParams> {
 public:
  VideoDecoderPipelineTest()
      : config_(VideoCodec::kVP8,
                VP8PROFILE_ANY,
                VideoDecoderConfig::AlphaMode::kIsOpaque,
                VideoColorSpace(),
                kNoTransformation,
                kCodedSize,
                gfx::Rect(kCodedSize),
                kCodedSize,
                EmptyExtraData(),
                EncryptionScheme::kUnencrypted) {
    auto pool = std::make_unique<MockVideoFramePool>();
    pool_ = pool.get();
    decoder_ = base::WrapUnique(new VideoDecoderPipeline(
        VideoDecoderPipeline::DecoderReservation::Take(
            std::numeric_limits<int>::max()),
        gpu::GpuDriverBugWorkarounds(),
        base::SingleThreadTaskRunner::GetCurrentDefault(), std::move(pool),
        FakeFrameResourceConverter::Create(),
        VideoDecoderPipeline::DefaultPreferredRenderableFourccs(),
        std::make_unique<MockMediaLog>(),
        // This callback needs to be configured in the individual tests.
        base::BindOnce(&VideoDecoderPipelineTest::CreateNullMockDecoder),
        /*uses_oop_video_decoder=*/false,
        /*in_video_decoder_process=*/true));

    SetSupportedVideoDecoderConfigs({
        SupportedVideoDecoderConfig(
            /*profile_min,=*/VP8PROFILE_ANY,
            /*profile_max=*/VP9PROFILE_PROFILE0, kMinSupportedResolution,
            kMaxSupportedResolution,
            /*allow_encrypted=*/true,
            /*require_encrypted=*/false),
    });
  }
  ~VideoDecoderPipelineTest() override = default;

  void TearDown() override {
    pool_ = nullptr;
    VideoDecoderPipeline::DestroyAsync(std::move(decoder_));
    task_environment_.RunUntilIdle();
  }
  MOCK_METHOD1(OnInit, void(DecoderStatus));
  MOCK_METHOD1(OnOutput, void(scoped_refptr<VideoFrame>));
  MOCK_METHOD0(OnResetDone, void());
  MOCK_METHOD1(OnDecodeDone, void(DecoderStatus));
  MOCK_METHOD1(OnWaiting, void(WaitingReason));

  void SetCreateDecoderFunctionCB(VideoDecoderPipeline::CreateDecoderFunctionCB
                                      function) NO_THREAD_SAFETY_ANALYSIS {
    decoder_->create_decoder_function_cb_ = std::move(function);
  }

  void SetCreateImageProcessorCBForTesting(
      VideoDecoderPipeline::CreateImageProcessorCBForTesting function)
      NO_THREAD_SAFETY_ANALYSIS {
    decoder_->create_image_processor_cb_for_testing_ = std::move(function);
  }

  // Constructs |decoder_| with a given |create_decoder_function_cb| and
  // verifying |status_code| is received back in OnInit().
  void InitializeDecoder(
      VideoDecoderPipeline::CreateDecoderFunctionCB create_decoder_function_cb,
      DecoderStatus::Codes status_code,
      CdmContext* cdm_context = nullptr) {
    SetCreateDecoderFunctionCB(std::move(create_decoder_function_cb));

    base::RunLoop run_loop;
    EXPECT_CALL(*this, OnInit(MatchesStatusCode(status_code)))
        .WillOnce(RunClosure(run_loop.QuitClosure()));

    decoder_->Initialize(
        config_, false /* low_delay */, cdm_context,
        base::BindOnce(&VideoDecoderPipelineTest::OnInit,
                       base::Unretained(this)),
        base::BindRepeating(&VideoDecoderPipelineTest::OnOutput,
                            base::Unretained(this)),
        base::BindRepeating(&VideoDecoderPipelineTest::OnWaiting,
                            base::Unretained(this)));
    run_loop.Run();
    testing::Mock::VerifyAndClearExpectations(this);
  }

#if BUILDFLAG(IS_CHROMEOS)
  void InitializeForTranscrypt(bool vp9 = false) {
    decoder_->allow_encrypted_content_for_testing_ = true;
    if (vp9) {
      config_ = VideoDecoderConfig(
          VideoCodec::kVP9, VP9PROFILE_PROFILE0,
          VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(),
          kNoTransformation, kCodedSize, gfx::Rect(kCodedSize), kCodedSize,
          EmptyExtraData(), EncryptionScheme::kCenc);
    }
    EXPECT_CALL(cdm_context_, GetChromeOsCdmContext())
        .WillRepeatedly(Return(&chromeos_cdm_context_));
    EXPECT_CALL(cdm_context_, RegisterEventCB(_))
        .WillOnce([this](CdmContext::EventCB event_cb) {
          return event_callbacks_.Register(std::move(event_cb));
        });
    EXPECT_CALL(cdm_context_, GetDecryptor())
        .WillRepeatedly(Return(&decryptor_));
    EXPECT_CALL(chromeos_cdm_context_, GetCdmContextRef())
        .WillOnce(
            Return(ByMove(std::make_unique<FakeCdmContextRef>(&cdm_context_))));
    InitializeDecoder(
        base::BindOnce(
            &VideoDecoderPipelineTest::CreateGoodMockTranscryptDecoder),
        DecoderStatus::Codes::kOk, &cdm_context_);
    testing::Mock::VerifyAndClearExpectations(&chromeos_cdm_context_);
    testing::Mock::VerifyAndClearExpectations(&cdm_context_);
    // GetDecryptor() will be called again, so set that expectation.
    EXPECT_CALL(cdm_context_, GetDecryptor())
        .WillRepeatedly(Return(&decryptor_));
    encrypted_buffer_ = DecoderBuffer::CopyFrom(kEncryptedData);
    transcrypted_buffer_ = DecoderBuffer::CopyFrom(kTranscryptedData);
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  static std::unique_ptr<VideoDecoderMixin> CreateNullMockDecoder(
      std::unique_ptr<MediaLog> /* media_log */,
      scoped_refptr<base::SequencedTaskRunner> /* decoder_task_runner */,
      base::WeakPtr<VideoDecoderMixin::Client> /* client */) {
    return nullptr;
  }

  // Creates a MockDecoder with an EXPECT_CALL on Initialize that returns ok.
  static std::unique_ptr<VideoDecoderMixin> CreateGoodMockDecoder(
      std::unique_ptr<MediaLog> /* media_log */,
      scoped_refptr<base::SequencedTaskRunner> /* decoder_task_runner */,
      base::WeakPtr<VideoDecoderMixin::Client> /* client */) {
    std::unique_ptr<MockDecoder> decoder(new MockDecoder());
    EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
        .WillOnce(::testing::WithArgs<3>([](VideoDecoder::InitCB init_cb) {
          std::move(init_cb).Run(DecoderStatus::Codes::kOk);
        }));
    EXPECT_CALL(*decoder, NeedsTranscryption()).WillRepeatedly(Return(false));
    return std::move(decoder);
  }

  // Creates a MockDecoder with an EXPECT_CALL on Initialize that returns ok and
  // also indicates that it requires transcryption.
  static std::unique_ptr<VideoDecoderMixin> CreateGoodMockTranscryptDecoder(
      std::unique_ptr<MediaLog> /* media_log */,
      scoped_refptr<base::SequencedTaskRunner> /* decoder_task_runner */,
      base::WeakPtr<VideoDecoderMixin::Client> /* client */) {
    std::unique_ptr<MockDecoder> decoder(new MockDecoder());
    EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
        .WillOnce(::testing::WithArgs<3>([](VideoDecoder::InitCB init_cb) {
          std::move(init_cb).Run(DecoderStatus::Codes::kOk);
        }));
    EXPECT_CALL(*decoder, NeedsTranscryption()).WillRepeatedly(Return(true));
    return std::move(decoder);
  }

  // Creates a MockDecoder with an EXPECT_CALL on Initialize that returns error.
  static std::unique_ptr<VideoDecoderMixin> CreateBadMockDecoder(
      std::unique_ptr<MediaLog> /* media_log */,
      scoped_refptr<base::SequencedTaskRunner> /* decoder_task_runner */,
      base::WeakPtr<VideoDecoderMixin::Client> /* client */) {
    std::unique_ptr<MockDecoder> decoder(new MockDecoder());
    EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
        .WillOnce(::testing::WithArgs<3>([](VideoDecoder::InitCB init_cb) {
          std::move(init_cb).Run(DecoderStatus::Codes::kFailed);
        }));
    EXPECT_CALL(*decoder, NeedsTranscryption()).WillRepeatedly(Return(false));
    return std::move(decoder);
  }

  VideoDecoderMixin* GetUnderlyingDecoder() NO_THREAD_SAFETY_ANALYSIS {
    return decoder_->decoder_.get();
  }

  void SetSupportedVideoDecoderConfigs(
      const SupportedVideoDecoderConfigs& configs) {
    decoder_->supported_configs_for_testing_ = configs;
  }

  void DetachDecoderSequenceChecker() NO_THREAD_SAFETY_ANALYSIS {
    // |decoder_| will be destroyed on its |decoder_task_runner| via
    // DestroyAsync(). This will trip its |decoder_sequence_checker_| if it has
    // been pegged to the test task runner, e.g. in PickDecoderOutputFormat().
    // Since in that case we don't care about threading, just detach it.
    DETACH_FROM_SEQUENCE(decoder_->decoder_sequence_checker_);

    if (decoder_->image_processor_) {
      // |decoder_->image_processor_->sequence_checker_| is pegged to the test
      // task runner because |decoder_->image_processor_| is created when we
      // call PickDecoderOutputFormat() from test code.
      // |decoder_->image_processor_| is then destroyed on the decoder task
      // runner because of VideoDecoderPipeline::DestroyAsync(). Thus the need
      // for this detachment.
      DETACH_FROM_SEQUENCE(decoder_->image_processor_->sequence_checker_);
    }
  }

  void InvokeWaitingCB(WaitingReason reason) {
    decoder_->decoder_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(&VideoDecoderPipeline::OnDecoderWaiting,
                                  base::Unretained(decoder_.get()), reason));
  }

  bool DecoderHasImageProcessor() NO_THREAD_SAFETY_ANALYSIS {
    return !!decoder_->image_processor_;
  }

  scoped_refptr<base::SequencedTaskRunner> GetDecoderTaskRunner()
      NO_THREAD_SAFETY_ANALYSIS {
    return decoder_->decoder_task_runner_;
  }

  base::test::TaskEnvironment task_environment_;
  VideoDecoderConfig config_;

#if BUILDFLAG(IS_CHROMEOS)
  MockCdmContext cdm_context_;  // Keep this before |decoder_|.
  MockChromeOsCdmContext chromeos_cdm_context_;
  StrictMock<MockDecryptor> decryptor_;
  scoped_refptr<DecoderBuffer> encrypted_buffer_;
  scoped_refptr<DecoderBuffer> transcrypted_buffer_;
  media::CallbackRegistry<CdmContext::EventCB::RunType> event_callbacks_;
#endif  // BUILDFLAG(IS_CHROMEOS)
  std::unique_ptr<VideoDecoderPipeline> decoder_;
  raw_ptr<MockVideoFramePool> pool_;
};

// Verifies the status code for several typical CreateDecoderFunctionCB cases.
TEST_P(VideoDecoderPipelineTest, Initialize) {
  InitializeDecoder(base::BindOnce(GetParam().create_decoder_function_cb),
                    GetParam().status_code);

  EXPECT_EQ(GetParam().status_code == DecoderStatus::Codes::kOk,
            !!GetUnderlyingDecoder());
}

const struct DecoderPipelineTestParams kDecoderPipelineTestParams[] = {
    // A CreateDecoderFunctionCB that fails to Create() (i.e. returns a
    // null Decoder)
    {base::BindRepeating(&VideoDecoderPipelineTest::CreateNullMockDecoder),
     DecoderStatus::Codes::kFailedToCreateDecoder},

    // A CreateDecoderFunctionCB that works fine, i.e. Create()s and
    // Initialize()s correctly.
    {base::BindRepeating(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
     DecoderStatus::Codes::kOk},

#if BUILDFLAG(IS_CHROMEOS)
    // A CreateDecoderFunctionCB for transcryption, where Create() is ok, and
    // the decoder will Initialize OK, but then the pipeline will not create the
    // transcryptor due to a missing CdmContext. This will succeed if called
    // through InitializeForTranscrypt where a CdmContext is set.
    {base::BindRepeating(
         &VideoDecoderPipelineTest::CreateGoodMockTranscryptDecoder),
     DecoderStatus::Codes::kUnsupportedEncryptionMode},
#endif  // BUILDFLAG(IS_CHROMEOS)

    // A CreateDecoderFunctionCB that Create()s ok but fails to Initialize()
    // correctly.
    {base::BindRepeating(&VideoDecoderPipelineTest::CreateBadMockDecoder),
     DecoderStatus::Codes::kFailed},
};

INSTANTIATE_TEST_SUITE_P(All,
                         VideoDecoderPipelineTest,
                         testing::ValuesIn(kDecoderPipelineTestParams));

// Verifies that trying to Initialize() with a non-supported config fails.
TEST_F(VideoDecoderPipelineTest, InitializeFailsDueToNotSupportedConfig) {
  // Configure the supported configs to something that we know is not supported,
  // e.g. making the smallest supported resolution larger than the |config_|
  // we'll be requesting.
  SetSupportedVideoDecoderConfigs({SupportedVideoDecoderConfig(
      /*profile_min=*/config_.profile(),
      /*profile_max=*/config_.profile(),
      /*coded_size_min=*/config_.coded_size() + gfx::Size(1, 1),
      kMaxSupportedResolution,
      /*allow_encrypted=*/true,
      /*require_encrypted=*/false)});

  InitializeDecoder(
      base::BindOnce(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
      DecoderStatus::Codes::kUnsupportedConfig);
}

// Verifies the Reset sequence.
TEST_F(VideoDecoderPipelineTest, Reset) {
  InitializeDecoder(
      base::BindOnce(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
      DecoderStatus::Codes::kOk);

  // When we call Reset(), we expect GetUnderlyingDecoder()'s Reset() method to
  // be called, and when that method Run()s its argument closure, then
  // OnResetDone() is expected to be called.

  base::RunLoop run_loop;
  EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()), Reset(_))
      .WillOnce(::testing::WithArgs<0>(
          [](base::OnceClosure closure) { std::move(closure).Run(); }));

  EXPECT_CALL(*this, OnResetDone())
      .WillOnce(RunClosure(run_loop.QuitClosure()));

  decoder_->Reset(base::BindOnce(&VideoDecoderPipelineTest::OnResetDone,
                                 base::Unretained(this)));
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(VideoDecoderPipelineTest, TranscryptThenEos) {
  InitializeForTranscrypt();

  // First send in a DecoderBuffer.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([this](Decryptor::StreamType stream_type,
                         scoped_refptr<DecoderBuffer> encrypted,
                         Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kSuccess, transcrypted_buffer_);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(transcrypted_buffer_, _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();

  testing::Mock::VerifyAndClearExpectations(&decryptor_);
  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(this);

  // Now send in the EOS, this should not invoke Decrypt.
  scoped_refptr<DecoderBuffer> eos_buffer = DecoderBuffer::CreateEOSBuffer();
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(eos_buffer, _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
  }
  decoder_->Decode(eos_buffer,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
}

TEST_F(VideoDecoderPipelineTest, TranscryptReset) {
  InitializeForTranscrypt();
  scoped_refptr<DecoderBuffer> encrypted_buffer2 =
      DecoderBuffer::CopyFrom(base::span(kEncryptedData).subspan<1>());
  // Send in a buffer, but don't invoke the Decrypt callback so it stays as
  // pending. Then send in 2 more buffers so they are in the queue.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .Times(1);
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  decoder_->Decode(encrypted_buffer2,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  decoder_->Decode(encrypted_buffer2,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(&decryptor_);

  // Now when we reset, we should see 3 decode callbacks occur as well as the
  // reset callback.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Reset(_))
        .WillOnce([](base::OnceClosure closure) { std::move(closure).Run(); });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kAborted)))
        .Times(3);
    EXPECT_CALL(*this, OnResetDone()).Times(1);
  }
  decoder_->Reset(base::BindOnce(&VideoDecoderPipelineTest::OnResetDone,
                                 base::Unretained(this)));
  task_environment_.RunUntilIdle();
}

// Verifies that any decode calls from
// VideoDecoderPipeline::OnBufferTranscrypted() received while the underlying
// VideoDecoderMixin is performing a reset operation are aborted.
TEST_F(VideoDecoderPipelineTest, TranscryptDecodeDuringReset) {
  InitializeForTranscrypt();

  // First send in a buffer, which will go to the decryptor and hold on to that
  // callback.
  Decryptor::DecryptCB saved_decrypt_cb;
  {
    InSequence sequence;

    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([&saved_decrypt_cb](Decryptor::StreamType stream_type,
                                      scoped_refptr<DecoderBuffer> encrypted,
                                      Decryptor::DecryptCB decrypt_cb) {
          saved_decrypt_cb =
              base::BindPostTaskToCurrentDefault(std::move(decrypt_cb));
        });
  }

  // Reset the underlying decoder but don't invoke the reset callback yet. Save
  // it for later.
  base::OnceClosure saved_reset_cb;
  EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()), Reset(_))
      .WillOnce([&saved_reset_cb](base::OnceClosure closure) {
        saved_reset_cb = base::BindPostTaskToCurrentDefault(std::move(closure));
      });

  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  decoder_->Reset(base::BindOnce(&VideoDecoderPipelineTest::OnResetDone,
                                 base::Unretained(this)));
  task_environment_.RunUntilIdle();

  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(&decryptor_);

  ASSERT_TRUE(saved_decrypt_cb);
  ASSERT_TRUE(saved_reset_cb);

  EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
              Decode(_, _))
      .Times(0);

  EXPECT_CALL(*this,
              OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kAborted)))
      .Times(1);

  std::move(saved_decrypt_cb).Run(Decryptor::kSuccess, transcrypted_buffer_);
  task_environment_.RunUntilIdle();

  testing::Mock::VerifyAndClearExpectations(this);

  EXPECT_CALL(*this, OnResetDone()).Times(1);

  std::move(saved_reset_cb).Run();
  task_environment_.RunUntilIdle();
}

// Verifies that if we get notified about a new decrypt key while we are
// performing a transcrypt that fails w/out a key, we immediately retry again.
TEST_F(VideoDecoderPipelineTest, TranscryptKeyAddedDuringTranscrypt) {
  InitializeForTranscrypt();
  // First send in a buffer, which will go to the decryptor and hold on to that
  // callback.
  Decryptor::DecryptCB saved_decrypt_cb;
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([&saved_decrypt_cb](Decryptor::StreamType stream_type,
                                      scoped_refptr<DecoderBuffer> encrypted,
                                      Decryptor::DecryptCB decrypt_cb) {
          saved_decrypt_cb =
              base::BindPostTaskToCurrentDefault(std::move(decrypt_cb));
        });
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(&decryptor_);

  // Now we invoke the CDM callback to indicate there is a new key available.
  event_callbacks_.Notify(CdmContext::Event::kHasAdditionalUsableKey);
  task_environment_.RunUntilIdle();

  // Now we have the decryptor callback return with kNoKey which should then
  // cause another call into the decryptor which we will have succeed and then
  // that should go through decoding. This should not invoke the waiting CB.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([this](Decryptor::StreamType stream_type,
                         scoped_refptr<DecoderBuffer> encrypted,
                         Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kSuccess, transcrypted_buffer_);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(transcrypted_buffer_, _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
  }
  EXPECT_CALL(*this, OnWaiting(_)).Times(0);
  std::move(saved_decrypt_cb).Run(Decryptor::kNoKey, nullptr);
  task_environment_.RunUntilIdle();
}

// Verifies that if we have a condition where we need to retry a pending
// transcrypt task that it doesn't try to reacquire a secure buffer if it
// already has one.
TEST_F(VideoDecoderPipelineTest, RetryDoesntReattachSecureBuffer) {
  InitializeForTranscrypt();
  // First send in a buffer, which will go to the decryptor and hold on to that
  // callback.
  Decryptor::DecryptCB saved_decrypt_cb;
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce([](scoped_refptr<DecoderBuffer>& buffer) {
          buffer->WritableSideData().secure_handle = kFakeSecureHandle;
          return CroStatus::Codes::kOk;
        });
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([&saved_decrypt_cb](Decryptor::StreamType stream_type,
                                      scoped_refptr<DecoderBuffer> encrypted,
                                      Decryptor::DecryptCB decrypt_cb) {
          saved_decrypt_cb =
              base::BindPostTaskToCurrentDefault(std::move(decrypt_cb));
        });
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(&decryptor_);

  // Now we invoke the CDM callback to indicate there is a new key available.
  event_callbacks_.Notify(CdmContext::Event::kHasAdditionalUsableKey);
  task_environment_.RunUntilIdle();

  // Now we have the decryptor callback return with kNoKey which should then
  // cause another call into the decryptor which we will have succeed and then
  // that should go through decoding. This should not invoke the call to attach
  // a secure buffer, but after it's done decoding it should invoke the call to
  // release the secure buffer.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(_))
        .Times(0);
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([this](Decryptor::StreamType stream_type,
                         scoped_refptr<DecoderBuffer> encrypted,
                         Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kSuccess, transcrypted_buffer_);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(transcrypted_buffer_, _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                ReleaseSecureBuffer(kFakeSecureHandle))
        .Times(1);
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
  }
  EXPECT_CALL(*this, OnWaiting(_)).Times(0);
  std::move(saved_decrypt_cb).Run(Decryptor::kNoKey, nullptr);
  task_environment_.RunUntilIdle();
}

// Verifies that if we don't have the key during transcrypt, the WaitingCB is
// invoked and then it retries again when we notify it of the new key.
TEST_F(VideoDecoderPipelineTest, TranscryptNoKeyWaitRetry) {
  InitializeForTranscrypt();
  // First send in a buffer, which will go to the decryptor and indicate there
  // is no key. This should also invoke the WaitingCB.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([](Decryptor::StreamType stream_type,
                     scoped_refptr<DecoderBuffer> encrypted,
                     Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kNoKey, nullptr);
        });
    EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(1);
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(&decryptor_);
  testing::Mock::VerifyAndClearExpectations(this);

  // Now we invoke the CDM callback to indicate there is a new key available.
  // This should invoke the decryptor again which we will have succeed and
  // complete the decode operation.
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([this](Decryptor::StreamType stream_type,
                         scoped_refptr<DecoderBuffer> encrypted,
                         Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kSuccess, transcrypted_buffer_);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(transcrypted_buffer_, _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
  }
  event_callbacks_.Notify(CdmContext::Event::kHasAdditionalUsableKey);
  task_environment_.RunUntilIdle();
}

TEST_F(VideoDecoderPipelineTest, TranscryptError) {
  InitializeForTranscrypt();
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo, encrypted_buffer_, _))
        .WillOnce([](Decryptor::StreamType stream_type,
                     scoped_refptr<DecoderBuffer> encrypted,
                     Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kError, nullptr);
        });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kFailed)));
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
}

TEST_F(VideoDecoderPipelineTest, SecureBufferFailure) {
  InitializeForTranscrypt();
  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(encrypted_buffer_))
        .WillOnce(Return(CroStatus::Codes::kUnableToAllocateSecureBuffer));
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kFailed)));
  }
  decoder_->Decode(encrypted_buffer_,
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();
}

#if BUILDFLAG(USE_V4L2_CODEC)
TEST_F(VideoDecoderPipelineTest, SplitVp9Superframe) {
  InitializeForTranscrypt(true);

  // This one requires specially crafted DecoderBuffer data so that the frame
  // split occurs. The superframe (which contains 2 frames) gets sent into the
  // pipeline for decoding, it then goes into the transcryptor...but then before
  // it gets sent for decrypt + decode it should get split into the 2 separate
  // frames.

  constexpr uint8_t kEncryptedSuperframe[] = {
      // Frame 0
      // Clear data
      1,
      2,
      3,
      4,
      // Encrypted Data (one block to cause IV increment)
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      11,
      12,
      13,
      14,
      15,
      16,
      // Frame 1
      // Clear data
      5,
      6,
      7,
      8,
      9,
      10,
      // Encrypted Data (must be at least a block size)
      17,
      18,
      19,
      20,
      21,
      22,
      23,
      24,
      25,
      26,
      27,
      28,
      29,
      30,
      31,
      32,
      // Superframe marker (2 frames, mag 1)
      0xc1,
      // Frame sizes (1 byte each)
      0x14,
      0x16,
      // Superframe marker (2 frames, mag 1)
      0xc1,
  };
  constexpr uint8_t kEncryptedFrame0[] = {
      // Clear data
      1,
      2,
      3,
      4,
      // Encrypted Data (one block to cause IV increment)
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      11,
      12,
      13,
      14,
      15,
      16,
  };
  constexpr uint8_t kEncryptedFrame1[] = {
      // Clear data
      5,
      6,
      7,
      8,
      9,
      10,
      // Encrypted Data (must be at least a block size)
      17,
      18,
      19,
      20,
      21,
      22,
      23,
      24,
      25,
      26,
      27,
      28,
      29,
      30,
      31,
      32,
  };

  scoped_refptr<DecoderBuffer> superframe_buffer =
      DecoderBuffer::CopyFrom(kEncryptedSuperframe);
  superframe_buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
      "fakekey", std::string(16, '0'),
      {SubsampleEntry(4, 16), SubsampleEntry(6, 16), SubsampleEntry(4, 0)}));

  std::string iv(16, '0');
  scoped_refptr<DecoderBuffer> frame0_buffer =
      DecoderBuffer::CopyFrom(kEncryptedFrame0);
  frame0_buffer->set_decrypt_config(
      DecryptConfig::CreateCencConfig("fakekey", iv, {SubsampleEntry(4, 16)}));

  scoped_refptr<DecoderBuffer> frame1_buffer =
      DecoderBuffer::CopyFrom(kEncryptedFrame1);
  // The IV should be incremented by one.
  iv[15]++;
  frame1_buffer->set_decrypt_config(
      DecryptConfig::CreateCencConfig("fakekey", iv, {SubsampleEntry(6, 16)}));

  {
    InSequence sequence;
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(MatchesDecoderBuffer(frame0_buffer)))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo,
                                    MatchesDecoderBuffer(frame0_buffer), _))
        .WillOnce([&frame0_buffer](Decryptor::StreamType stream_type,
                                   scoped_refptr<DecoderBuffer> encrypted,
                                   Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kSuccess, frame0_buffer);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(MatchesDecoderBuffer(frame0_buffer), _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                AttachSecureBuffer(MatchesDecoderBuffer(frame1_buffer)))
        .WillOnce(Return(CroStatus::Codes::kOk));
    EXPECT_CALL(decryptor_, Decrypt(Decryptor::kVideo,
                                    MatchesDecoderBuffer(frame1_buffer), _))
        .WillOnce([&frame1_buffer](Decryptor::StreamType stream_type,
                                   scoped_refptr<DecoderBuffer> encrypted,
                                   Decryptor::DecryptCB decrypt_cb) {
          std::move(decrypt_cb).Run(Decryptor::kSuccess, frame1_buffer);
        });
    EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()),
                Decode(MatchesDecoderBuffer(frame1_buffer), _))
        .WillOnce([](scoped_refptr<DecoderBuffer> transcrypted,
                     VideoDecoderMixin::DecodeCB decode_cb) {
          std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
        });
    EXPECT_CALL(*this,
                OnDecodeDone(MatchesStatusCode(DecoderStatus::Codes::kOk)));
  }
  decoder_->Decode(std::move(superframe_buffer),
                   base::BindOnce(&VideoDecoderPipelineTest::OnDecodeDone,
                                  base::Unretained(this)));
  task_environment_.RunUntilIdle();

  testing::Mock::VerifyAndClearExpectations(&decryptor_);
  testing::Mock::VerifyAndClearExpectations(
      reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()));
  testing::Mock::VerifyAndClearExpectations(this);
}
#endif  // BUILDFLAG(USE_V4L2_CODEC)
#endif  // BUILDFLAG(IS_CHROMEOS)

// Verifies the algorithm for choosing formats in PickDecoderOutputFormat works
// as expected.
TEST_F(VideoDecoderPipelineTest, PickDecoderOutputFormat) {
  constexpr gfx::Size kSize(320, 240);
  constexpr gfx::Rect kVisibleRect(320, 240);
  constexpr size_t kNumCodecReferenceFrames = 4u;
  constexpr uint64_t kModifier = ~DRM_FORMAT_MOD_LINEAR;

  const struct {
    std::vector<PixelLayoutCandidate> input_candidates;
    PixelLayoutCandidate expected_chosen_candidate;
  } test_vectors[] = {
      // Easy cases: one candidate that is supported, should be chosen.
      {{PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
      {{PixelLayoutCandidate{Fourcc(Fourcc::YV12), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::YV12), kSize, kModifier}},
      {{PixelLayoutCandidate{Fourcc(Fourcc::P010), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::P010), kSize, kModifier}},
      // Two candidates, both supported: pick as per implementation.
      {{PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier},
        PixelLayoutCandidate{Fourcc(Fourcc::YV12), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
      {{PixelLayoutCandidate{Fourcc(Fourcc::YV12), kSize, kModifier},
        PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
      {{PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier},
        PixelLayoutCandidate{Fourcc(Fourcc::P010), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
      // Two candidates, only one supported, the supported one should be picked.
      {{PixelLayoutCandidate{Fourcc(Fourcc::YU16), kSize, kModifier},
        PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::NV12), kSize, kModifier}},
      {{PixelLayoutCandidate{Fourcc(Fourcc::YU16), kSize, kModifier},
        PixelLayoutCandidate{Fourcc(Fourcc::YV12), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::YV12), kSize, kModifier}},
      {{PixelLayoutCandidate{Fourcc(Fourcc::YU16), kSize, kModifier},
        PixelLayoutCandidate{Fourcc(Fourcc::P010), kSize, kModifier}},
       PixelLayoutCandidate{Fourcc(Fourcc::P010), kSize, kModifier}}};

  for (const auto& test_vector : test_vectors) {
    const Fourcc& expected_fourcc =
        test_vector.expected_chosen_candidate.fourcc;
    const gfx::Size& expected_coded_size =
        test_vector.expected_chosen_candidate.size;
    std::vector<ColorPlaneLayout> planes(
        VideoFrame::NumPlanes(expected_fourcc.ToVideoPixelFormat()));
    EXPECT_CALL(
        *pool_,
        Initialize(expected_fourcc, expected_coded_size, kVisibleRect,
                   /*natural_size=*/kVisibleRect.size(),
                   /*max_num_frames=*/::testing::Gt(kNumCodecReferenceFrames),
                   /*use_protected=*/false,
                   /*use_linear_buffers=*/false))
        .WillOnce(Return(*GpuBufferLayout::Create(
            expected_fourcc, expected_coded_size, std::move(planes),
            /*modifier=*/kModifier)));
    auto status_or_chosen_candidate = decoder_->PickDecoderOutputFormat(
        test_vector.input_candidates, kVisibleRect,
        /*decoder_natural_size=*/kVisibleRect.size(),
        /*output_size=*/std::nullopt,
        /*num_codec_reference_frames=*/kNumCodecReferenceFrames,
        /*use_protected=*/false, /*need_aux_frame_pool=*/false, std::nullopt);
    ASSERT_TRUE(status_or_chosen_candidate.has_value());
    const PixelLayoutCandidate chosen_candidate =
        std::move(status_or_chosen_candidate).value();
    EXPECT_EQ(test_vector.expected_chosen_candidate, chosen_candidate)
        << " expected: "
        << test_vector.expected_chosen_candidate.fourcc.ToString()
        << ", actual: " << chosen_candidate.fourcc.ToString();
    EXPECT_FALSE(DecoderHasImageProcessor());
    testing::Mock::VerifyAndClearExpectations(pool_);
  }
  DetachDecoderSequenceChecker();
}

// These tests only work on non-linux vaapi systems, since on linux there is no
// support for different modifiers.
#if BUILDFLAG(USE_VAAPI) && !BUILDFLAG(IS_LINUX)

// Verifies the algorithm for choosing formats in PickDecoderOutputFormat works
// as expected when the pool returns linear buffers. It should allocate an image
// processor in those cases.
TEST_F(VideoDecoderPipelineTest, PickDecoderOutputFormatLinearModifier) {
  constexpr gfx::Size kSize(320, 240);
  constexpr gfx::Rect kVisibleRect(320, 240);
  constexpr size_t kNumCodecReferenceFrames = 4u;
  const Fourcc kFourcc(Fourcc::NV12);

  auto image_processor =
      std::make_unique<MockImageProcessor>(GetDecoderTaskRunner());
  ImageProcessorBackend::PortConfig port_config(
      Fourcc(Fourcc::NV12), gfx::Size(320, 240), {}, gfx::Rect(320, 240), {});
  EXPECT_CALL(*image_processor, input_config())
      .WillRepeatedly(testing::ReturnRef(port_config));
  EXPECT_CALL(*image_processor, output_config())
      .WillRepeatedly(testing::ReturnRef(port_config));

  base::MockCallback<VideoDecoderPipeline::CreateImageProcessorCBForTesting>
      image_processor_cb;
  EXPECT_CALL(image_processor_cb, Run(_, _, kSize, _))
      .WillOnce(Return(testing::ByMove(std::move(image_processor))));
  SetCreateImageProcessorCBForTesting(image_processor_cb.Get());

  // Modifier should be the linear format.
  GpuBufferLayout gpu_buffer_layout = *GpuBufferLayout::Create(
      kFourcc, kSize,
      std::vector<ColorPlaneLayout>(
          VideoFrame::NumPlanes(kFourcc.ToVideoPixelFormat())),
      /*modifier=*/DRM_FORMAT_MOD_LINEAR);
  EXPECT_CALL(*pool_, Initialize(_, _, _, _, _, _, _))
      .WillRepeatedly(Return(gpu_buffer_layout));

  PixelLayoutCandidate candidate{Fourcc(Fourcc::NV12), kSize,
                                 /*modifier=*/~DRM_FORMAT_MOD_LINEAR};
  auto status_or_chosen_candidate = decoder_->PickDecoderOutputFormat(
      {candidate}, kVisibleRect,
      /*decoder_natural_size=*/kVisibleRect.size(),
      /*output_size=*/std::nullopt,
      /*num_codec_reference_frames=*/kNumCodecReferenceFrames,
      /*use_protected=*/false, /*need_aux_frame_pool=*/false, std::nullopt);

  EXPECT_TRUE(status_or_chosen_candidate.has_value());
  // Main concern is that the image processor was set.
  EXPECT_TRUE(DecoderHasImageProcessor());
  DetachDecoderSequenceChecker();
}

// Verifies the algorithm for choosing formats in PickDecoderOutputFormat works
// as expected when the frame pool returns buffers that have an unsupported
// modifier.
TEST_F(VideoDecoderPipelineTest, PickDecoderOutputFormatUnsupportedModifier) {
  constexpr gfx::Size kSize(320, 240);
  constexpr gfx::Rect kVisibleRect(320, 240);
  constexpr size_t kNumCodecReferenceFrames = 4u;
  const Fourcc kFourcc(Fourcc::NV12);

  // Modifier is *not* the linear format.
  GpuBufferLayout gpu_buffer_layout = *GpuBufferLayout::Create(
      kFourcc, kSize,
      std::vector<ColorPlaneLayout>(
          VideoFrame::NumPlanes(kFourcc.ToVideoPixelFormat())),
      /*modifier=*/~DRM_FORMAT_MOD_LINEAR);
  EXPECT_CALL(*pool_, Initialize(_, _, _, _, _, _, _))
      .WillRepeatedly(Return(gpu_buffer_layout));

  // Make sure the modifier mismatches the |gpu_buffer_layout|'s
  constexpr uint64_t modifier = ~DRM_FORMAT_MOD_LINEAR + 1;
  PixelLayoutCandidate candidate{Fourcc(Fourcc::NV12), kSize, modifier};
  auto status_or_chosen_candidate = decoder_->PickDecoderOutputFormat(
      {candidate}, kVisibleRect,
      /*decoder_natural_size=*/kVisibleRect.size(),
      /*output_size=*/std::nullopt,
      /*num_codec_reference_frames=*/kNumCodecReferenceFrames,
      /*use_protected=*/false, /*need_aux_frame_pool=*/false, std::nullopt);

  EXPECT_FALSE(status_or_chosen_candidate.has_value());
  EXPECT_FALSE(DecoderHasImageProcessor());
  DetachDecoderSequenceChecker();
}

#endif  // BUILDFLAG(USE_VAAPI) && !BUILDFLAG(IS_LINUX)

// Verifies that ReleaseAllFrames is called on the frame pool when we receive
// the kDecoderStateLost event through the waiting callback. This can occur
// during protected content playback on Intel.
TEST_F(VideoDecoderPipelineTest, RebuildFramePoolsOnStateLost) {
  InitializeDecoder(
      base::BindOnce(&VideoDecoderPipelineTest::CreateGoodMockDecoder),
      DecoderStatus::Codes::kOk);

  // Simulate the waiting callback from the decoder for kDecoderStateLost.
  EXPECT_CALL(*this, OnWaiting(media::WaitingReason::kDecoderStateLost));
  InvokeWaitingCB(media::WaitingReason::kDecoderStateLost);
  task_environment_.RunUntilIdle();

  // Invoke Reset() as a client would do, and we then expect that to invoke the
  // method to rebuild the frame pool.
  EXPECT_CALL(*reinterpret_cast<MockDecoder*>(GetUnderlyingDecoder()), Reset(_))
      .WillOnce(::testing::WithArgs<0>(
          [](base::OnceClosure closure) { std::move(closure).Run(); }));
  EXPECT_CALL(*this, OnResetDone());
  EXPECT_CALL(*pool_, ReleaseAllFrames());

  decoder_->Reset(base::BindOnce(&VideoDecoderPipelineTest::OnResetDone,
                                 base::Unretained(this)));
  task_environment_.RunUntilIdle();
}
}  // namespace media