chromium/media/gpu/chromeos/video_decoder_pipeline_unittest.cc

// 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 "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_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)

RunClosure;
_;
ByMove;
InSequence;
Return;
StrictMock;
TestWithParam;

namespace media {

PixelLayoutCandidate;

MATCHER_P(MatchesStatusCode, status_code, "") {}

MATCHER_P(MatchesDecoderBuffer, buffer, "") {}

class MockVideoFramePool : public DmabufVideoFramePool {};

class MockDecoder : public VideoDecoderMixin {};

#if BUILDFLAG(IS_CHROMEOS_ASH)
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_ASH)

class MockImageProcessor : public ImageProcessor {};

struct DecoderPipelineTestParams {};

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

static_assert;

class VideoDecoderPipelineTest
    : public testing::TestWithParam<DecoderPipelineTestParams> {};

// Verifies the status code for several typical CreateDecoderFunctionCB cases.
TEST_P(VideoDecoderPipelineTest, Initialize) {}

const struct DecoderPipelineTestParams kDecoderPipelineTestParams[] =;

INSTANTIATE_TEST_SUITE_P();

// Verifies that trying to Initialize() with a non-supported config fails.
TEST_F(VideoDecoderPipelineTest, InitializeFailsDueToNotSupportedConfig) {}

// Verifies the Reset sequence.
TEST_F(VideoDecoderPipelineTest, Reset) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
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_ASH)

// Verifies the algorithm for choosing formats in PickDecoderOutputFormat works
// as expected.
TEST_F(VideoDecoderPipelineTest, PickDecoderOutputFormat) {}

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

// 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) &&
        // !BUILDFLAG(IS_CHROMEOS_LACROS)

// 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) {}
}  // namespace media