// 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/video/video_encode_accelerator_adapter.h"

#include <algorithm>
#include <memory>
#include <string>

#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/run_until.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/viz/test/test_context_provider.h"
#include "media/base/bitrate.h"
#include "media/base/media_util.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/base/video_util.h"
#include "media/video/fake_video_encode_accelerator.h"
#include "media/video/gpu_video_accelerator_factories.h"
#include "media/video/mock_gpu_video_accelerator_factories.h"
#include "media/video/mock_video_encode_accelerator.h"
#include "media/video/video_encoder_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/color_space.h"

using ::testing::_;
using ::testing::AtLeast;
using ::testing::Field;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Values;
using ::testing::WithArgs;

namespace media {

class FlushHoldingVideoEncodeAccelerator : public FakeVideoEncodeAccelerator {
 public:
  explicit FlushHoldingVideoEncodeAccelerator(
      scoped_refptr<base::SequencedTaskRunner> task_runner,
      FlushCallback* out_held_callback)
      : FakeVideoEncodeAccelerator(std::move(task_runner)),
        out_held_callback_(out_held_callback) {}

  void Flush(FlushCallback flush_callback) override {
    flush_callback_ = std::move(flush_callback);
  }

  bool IsFlushSupported() override { return true; }

  void Destroy() override {
    *out_held_callback_ = std::move(flush_callback_);
    FakeVideoEncodeAccelerator::Destroy();
  }

 private:
  FlushCallback flush_callback_;
  raw_ptr<FlushCallback> out_held_callback_;
};

class VideoEncodeAcceleratorAdapterTest
    : public ::testing::TestWithParam<VideoPixelFormat> {
 public:
  VideoEncodeAcceleratorAdapterTest() = default;

  void SetUp() override {
    vea_runner_ = base::ThreadPool::CreateSequencedTaskRunner({});

    vea_ = new FakeVideoEncodeAccelerator(vea_runner_);
    sii_ = base::MakeRefCounted<gpu::TestSharedImageInterface>();
    gpu_factories_ =
        std::make_unique<MockGpuVideoAcceleratorFactories>(sii_.get());
    supported_profiles_ = {
        VideoEncodeAccelerator::SupportedProfile(
            profile_,
            /*max_resolution=*/gfx::Size(3840, 2160),
            /*max_framerate_numerator=*/30,
            /*max_framerate_denominator=*/1,
            /*rc_modes=*/VideoEncodeAccelerator::kConstantMode |
                VideoEncodeAccelerator::kVariableMode),
    };

    EXPECT_CALL(*gpu_factories_.get(),
                GetVideoEncodeAcceleratorSupportedProfiles())
        .WillRepeatedly(Return(supported_profiles_));
    EXPECT_CALL(*gpu_factories_.get(), DoCreateVideoEncodeAccelerator())
        .WillRepeatedly(Return(vea_.get()));
    EXPECT_CALL(*gpu_factories_.get(), GetTaskRunner())
        .WillRepeatedly(Return(vea_runner_));
    EXPECT_CALL(*sii_.get(), DoCreateSharedImage(_, _, _, _))
        .Times(testing::AnyNumber());

    auto media_log = std::make_unique<NullMediaLog>();
    callback_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
    vae_adapter_ = std::make_unique<VideoEncodeAcceleratorAdapter>(
        gpu_factories_.get(), media_log->Clone(), callback_runner_);
  }

  void TearDown() override {
    vea_runner_->DeleteSoon(FROM_HERE, std::move(vae_adapter_));
    RunUntilIdle();
  }

  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
  VideoEncodeAcceleratorAdapter* adapter() { return vae_adapter_.get(); }
  FakeVideoEncodeAccelerator* vea() { return vea_; }

  scoped_refptr<VideoFrame> CreateGreenGpuFrame(gfx::Size size,
                                                base::TimeDelta timestamp) {
    // Define shared image usage for a mappable shared image.
    constexpr auto si_usage = gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY |
                              gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
    constexpr auto buffer_usage =
        gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE;
#else
    constexpr auto buffer_usage = gfx::BufferUsage::GPU_READ_CPU_READ_WRITE;
#endif
    auto shared_image = sii_->CreateSharedImage(
        {viz::MultiPlaneFormat::kNV12, size, kYUVColorSpace,
         gpu::SharedImageUsageSet(si_usage),
         "VideoEncodeAcceleratorAdapterTest"},
        gpu::kNullSurfaceHandle, buffer_usage);
    if (!shared_image) {
      return nullptr;
    }
    auto scoped_mapping = shared_image->Map();
    if (!scoped_mapping) {
      return nullptr;
    }

    std::ranges::fill(scoped_mapping->GetMemoryForPlane(0), 0x96);
    std::ranges::fill(scoped_mapping->GetMemoryForPlane(1), 0x28);

    auto frame = VideoFrame::WrapMappableSharedImage(
        std::move(shared_image), sii_->GenVerifiedSyncToken(),
        base::NullCallback(), gfx::Rect(size), size, timestamp);
    frame->set_color_space(kYUVColorSpace);
    return frame;
  }

  scoped_refptr<VideoFrame> CreateGreenCpuFrame(gfx::Size size,
                                                base::TimeDelta timestamp) {
    auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size,
                                         gfx::Rect(size), size, timestamp);

    // Green I420 frame (Y:0x96, U:0x40, V:0x40)
    libyuv::I420Rect(frame->writable_data(VideoFrame::Plane::kY),
                     frame->stride(VideoFrame::Plane::kY),
                     frame->writable_data(VideoFrame::Plane::kU),
                     frame->stride(VideoFrame::Plane::kU),
                     frame->writable_data(VideoFrame::Plane::kV),
                     frame->stride(VideoFrame::Plane::kV),
                     0,                               // left
                     0,                               // top
                     frame->visible_rect().width(),   // right
                     frame->visible_rect().height(),  // bottom
                     0x96,                            // Y color
                     0x40,                            // U color
                     0x40);                           // V color

    frame->set_color_space(kYUVColorSpace);
    return frame;
  }

  scoped_refptr<VideoFrame> CreateGreenCpuFrameP010(gfx::Size size,
                                                    base::TimeDelta timestamp) {
    auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_P010LE, size,
                                         gfx::Rect(size), size, timestamp);

    // Green P010 frame (Y:0x96, UV:0x80)
    std::ranges::fill(frame->GetWritableVisiblePlaneData(VideoFrame::Plane::kY),
                      0x96);
    std::ranges::fill(
        frame->GetWritableVisiblePlaneData(VideoFrame::Plane::kUV), 0x80);

    frame->set_color_space(kYUVColorSpace);
    return frame;
  }

  scoped_refptr<VideoFrame> CreateGreenCpuFrameARGB(gfx::Size size,
                                                    base::TimeDelta timestamp) {
    auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_XRGB, size,
                                         gfx::Rect(size), size, timestamp);

    // Green XRGB frame (R:0x3B, G:0xD9, B:0x24)
    libyuv::ARGBRect(frame->writable_data(VideoFrame::Plane::kARGB),
                     frame->stride(VideoFrame::Plane::kARGB),
                     0,                               // left
                     0,                               // top
                     frame->visible_rect().width(),   // right
                     frame->visible_rect().height(),  // bottom
                     0x24D93B00);                     // V color

    frame->set_color_space(kRGBColorSpace);
    return frame;
  }

  scoped_refptr<VideoFrame> CreateGreenFrame(gfx::Size size,
                                             VideoPixelFormat format,
                                             base::TimeDelta timestamp) {
    switch (format) {
      case PIXEL_FORMAT_I420:
        return CreateGreenCpuFrame(size, timestamp);
      case PIXEL_FORMAT_NV12:
        return CreateGreenGpuFrame(size, timestamp);
      case PIXEL_FORMAT_XRGB:
        return CreateGreenCpuFrameARGB(size, timestamp);
      default:
        EXPECT_TRUE(false) << "not supported pixel format";
        return nullptr;
    }
  }

  gfx::ColorSpace ExpectedColorSpace(VideoPixelFormat src_format,
                                     VideoPixelFormat dst_format) {
    // Converting between YUV formats doesn't change the color space.
    if (IsYuvPlanar(src_format) && IsYuvPlanar(dst_format)) {
      return kYUVColorSpace;
    }

    // We now use matrix-based conversion, so RGB to YUV uses the source
    // frame's primary to select the matrix. For sRGB, this is BT.709.
    if (IsRGB(src_format) && IsYuvPlanar(dst_format)) {
      return kYUVFullColorSpace;
    }

    EXPECT_TRUE(false) << "unexpected formats: src=" << src_format
                       << ", dst=" << dst_format;
    return gfx::ColorSpace();
  }

  // Advertises a single profile with the given input pixel formats. The
  // expectation installed in SetUp() captured a copy of |supported_profiles_|,
  // so it has to be reinstalled rather than the vector merely mutated.
  void SetSupportedProfile(VideoCodecProfile profile,
                           std::vector<VideoPixelFormat> pixel_formats) {
    VideoEncodeAccelerator::SupportedProfile supported_profile(
        profile,
        /*max_resolution=*/gfx::Size(3840, 2160),
        /*max_framerate_numerator=*/30,
        /*max_framerate_denominator=*/1,
        /*rc_modes=*/VideoEncodeAccelerator::kConstantMode |
            VideoEncodeAccelerator::kVariableMode);
    supported_profile.gpu_supported_pixel_formats = std::move(pixel_formats);
    supported_profiles_ = {std::move(supported_profile)};
    profile_ = profile;
    EXPECT_CALL(*gpu_factories_.get(),
                GetVideoEncodeAcceleratorSupportedProfiles())
        .WillRepeatedly(Return(supported_profiles_));
  }

  VideoEncoder::EncoderStatusCB ValidatingStatusCB(
      base::Location loc = FROM_HERE) {
    struct CallEnforcer {
      bool called = false;
      std::string location;
      ~CallEnforcer() {
        EXPECT_TRUE(called) << "Callback created: " << location;
      }
    };
    auto enforcer = std::make_unique<CallEnforcer>();
    enforcer->location = loc.ToString();
    return base::BindLambdaForTesting(
        [this, enforcer{std::move(enforcer)}](EncoderStatus s) {
          EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
          EXPECT_TRUE(s.is_ok()) << " Callback created: " << enforcer->location
                                 << " Error: " << s.message();
          enforcer->called = true;
        });
  }

 protected:
  VideoCodecProfile profile_ = VP8PROFILE_ANY;
  static constexpr gfx::ColorSpace kRGBColorSpace =
      gfx::ColorSpace::CreateSRGB();
  static constexpr gfx::ColorSpace kYUVColorSpace =
      gfx::ColorSpace::CreateREC709();
  const gfx::ColorSpace kYUVFullColorSpace =
      gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT709,
                      gfx::ColorSpace::TransferID::SRGB,
                      gfx::ColorSpace::MatrixID::BT709,
                      gfx::ColorSpace::RangeID::FULL);
  std::vector<VideoEncodeAccelerator::SupportedProfile> supported_profiles_;
  base::test::TaskEnvironment task_environment_;
  raw_ptr<FakeVideoEncodeAccelerator, AcrossTasksDanglingUntriaged>
      vea_;  // owned by |vae_adapter_|
  scoped_refptr<gpu::TestSharedImageInterface> sii_;
  std::unique_ptr<MockGpuVideoAcceleratorFactories> gpu_factories_;
  std::unique_ptr<VideoEncodeAcceleratorAdapter> vae_adapter_;
  scoped_refptr<base::SequencedTaskRunner> vea_runner_;
  scoped_refptr<base::SequencedTaskRunner> callback_runner_;
};

TEST_F(VideoEncodeAcceleratorAdapterTest, PreInitialize) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        /*output_cb=*/base::DoNothing(), ValidatingStatusCB());
  RunUntilIdle();
}

TEST_F(VideoEncodeAcceleratorAdapterTest, InitializeAfterFirstFrame) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  auto pixel_format = PIXEL_FORMAT_I420;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);

  bool info_cb_called = false;
  VideoEncoder::EncoderInfoCB info_cb = base::BindLambdaForTesting(
      [&](const VideoEncoderInfo& info) { info_cb_called = true; });

  int outputs_count = 0;
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
#if BUILDFLAG(IS_FUCHSIA)
  expected_input_format = PIXEL_FORMAT_I420;
#endif
  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(keyframe, true);
        EXPECT_EQ(frame->format(), expected_input_format);
        EXPECT_EQ(frame->coded_size(), options.frame_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(profile_, options, std::move(info_cb),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));

  adapter()->Encode(frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();
  vea()->NotifyEncoderInfoChange(VideoEncoderInfo());
  RunUntilIdle();
  EXPECT_TRUE(info_cb_called);
  EXPECT_EQ(outputs_count, 1);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, TemporalSvc) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.scalability_mode = SVCScalabilityMode::kL1T3;
  int outputs_count = 0;
  auto pixel_format = PIXEL_FORMAT_I420;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        if (output.timestamp == base::Milliseconds(1))
          EXPECT_EQ(output.temporal_id, 1);
        else if (output.timestamp == base::Milliseconds(2))
          EXPECT_EQ(output.temporal_id, 1);
        else if (output.timestamp == base::Milliseconds(3))
          EXPECT_EQ(output.temporal_id, 2);
        else if (output.timestamp == base::Milliseconds(4))
          EXPECT_EQ(output.temporal_id, 2);

        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        BitstreamBufferMetadata result(1, keyframe, frame->timestamp());
        if (frame->timestamp() == base::Milliseconds(1)) {
          result.h264 = H264Metadata();
          result.h264->temporal_idx = 1;
        } else if (frame->timestamp() == base::Milliseconds(2)) {
          result.vp8 = Vp8Metadata();
          result.vp8->temporal_idx = 1;
        } else if (frame->timestamp() == base::Milliseconds(3)) {
          result.vp9 = Vp9Metadata();
          result.vp9->temporal_idx = 2;
        } else if (frame->timestamp() == base::Milliseconds(4)) {
          result.svc_generic = SVCGenericMetadata();
          result.svc_generic->temporal_idx = 2;
        }
        return result;
      }));
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame1 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  auto frame2 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(2));
  auto frame3 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(3));
  auto frame4 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(4));

  adapter()->Encode(frame1, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();
  adapter()->Encode(frame2, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();
  adapter()->Encode(frame3, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();
  adapter()->Encode(frame4, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();
  EXPECT_EQ(outputs_count, 4);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, FlushDuringInitialize) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  int outputs_count = 0;
  auto pixel_format = PIXEL_FORMAT_I420;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
#if BUILDFLAG(IS_FUCHSIA)
  expected_input_format = PIXEL_FORMAT_I420;
#endif

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(keyframe, true);
        EXPECT_EQ(frame->format(), expected_input_format);
        EXPECT_EQ(frame->coded_size(), options.frame_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  adapter()->Encode(frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  adapter()->Flush(base::BindLambdaForTesting([&](EncoderStatus s) {
    EXPECT_TRUE(s.is_ok());
    EXPECT_EQ(outputs_count, 1);
  }));
  RunUntilIdle();
}

TEST_F(VideoEncodeAcceleratorAdapterTest, HeldFlushCallbackAfterDestroy) {
  // Delete the default vea_ allocated in SetUp since we are replacing it.
  delete vea_.ExtractAsDangling();

  VideoEncodeAccelerator::FlushCallback held_flush_callback;
  auto* flush_holding_vea =
      new FlushHoldingVideoEncodeAccelerator(vea_runner_, &held_flush_callback);
  vea_ = flush_holding_vea;
  EXPECT_CALL(*gpu_factories_.get(), DoCreateVideoEncodeAccelerator())
      .WillRepeatedly(Return(flush_holding_vea));

  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);

  // Wait 1: Initialize
  base::RunLoop init_run_loop;
  adapter()->Initialize(
      profile_, options, /*info_cb=*/base::DoNothing(),
      /*output_cb=*/base::DoNothing(),
      base::BindLambdaForTesting([&](EncoderStatus status) {
        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
        EXPECT_TRUE(status.is_ok());
        init_run_loop.Quit();
      }));
  init_run_loop.Run();

  auto frame = CreateGreenFrame(options.frame_size, PIXEL_FORMAT_I420,
                                base::Milliseconds(1));

  // Block vea_runner_ to ensure Encode and Flush are both posted before either
  // runs.
  base::WaitableEvent event;
  vea_runner_->PostTask(
      FROM_HERE, base::BindOnce(
                     [](base::WaitableEvent* e) {
                       base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
                       e->Wait();
                     },
                     base::Unretained(&event)));

  // Start Encode but don't wait yet
  base::RunLoop encode_run_loop;
  adapter()->Encode(
      frame, VideoEncoder::EncodeOptions(true),
      base::BindLambdaForTesting([&](EncoderStatus status) {
        EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence());
        EXPECT_TRUE(status.is_ok());
        encode_run_loop.Quit();
      }));

  bool flush_called = false;
  adapter()->Flush(base::BindLambdaForTesting([&](EncoderStatus status) {
    flush_called = true;
    EXPECT_TRUE(status.is_ok());
  }));

  // Unblock vea_runner_ now that both are posted.
  event.Signal();

  // Wait for vea_runner_ to process Encode and Flush, which will post
  // the Encode completion callback to the main thread.
  {
    base::RunLoop run_loop;
    vea_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
                                  run_loop.QuitClosure());
    run_loop.Run();
  }

  // Now run the main thread loop until the Encode callback executes.
  encode_run_loop.Run();
  EXPECT_FALSE(flush_called);

  // Wait 4: Deletion
  vea_runner_->DeleteSoon(FROM_HERE, std::move(vae_adapter_));
  {
    base::RunLoop run_loop;
    vea_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
                                  run_loop.QuitClosure());
    run_loop.Run();
  }

  // Wait 5: Running the held callback
  vea_runner_->PostTask(FROM_HERE,
                        base::BindOnce(std::move(held_flush_callback), true));
  {
    base::RunLoop run_loop;
    vea_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
                                  run_loop.QuitClosure());
    run_loop.Run();
  }
}

TEST_F(VideoEncodeAcceleratorAdapterTest, InitializationError) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  int outputs_count = 0;
  auto pixel_format = PIXEL_FORMAT_I420;
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput, std::optional<VideoEncoder::CodecDescription>) {
        outputs_count++;
      });

  VideoEncoder::EncoderStatusCB expect_error_done_cb =
      base::BindLambdaForTesting(
          [&](EncoderStatus s) { EXPECT_FALSE(s.is_ok()); });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_TRUE(false) << "should never come here";
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(
      VIDEO_CODEC_PROFILE_UNKNOWN, options, /*info_cb=*/base::DoNothing(),
      std::move(output_cb), base::BindLambdaForTesting([](EncoderStatus s) {
        EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderUnsupportedProfile);
      }));

  auto frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  adapter()->Encode(frame, VideoEncoder::EncodeOptions(true),
                    std::move(expect_error_done_cb));
  RunUntilIdle();
  EXPECT_EQ(outputs_count, 0);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, EncodingError) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  int outputs_count = 0;
  auto pixel_format = PIXEL_FORMAT_I420;
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput, std::optional<VideoEncoder::CodecDescription>) {
        outputs_count++;
      });

  VideoEncoder::EncoderStatusCB expect_error_done_cb =
      base::BindLambdaForTesting(
          [&](EncoderStatus s) { EXPECT_FALSE(s.is_ok()); });

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  vea()->SetWillEncodingSucceed(false);

  auto frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  adapter()->Encode(frame, VideoEncoder::EncodeOptions(true),
                    std::move(expect_error_done_cb));
  RunUntilIdle();
  EXPECT_EQ(outputs_count, 0);
}

TEST_P(VideoEncodeAcceleratorAdapterTest, TwoFramesResize) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  int outputs_count = 0;
  gfx::Size small_size(480, 320);
  gfx::Size large_size(800, 600);
  auto pixel_format = GetParam();
  auto small_frame =
      CreateGreenFrame(small_size, pixel_format, base::Milliseconds(1));
  auto large_frame =
      CreateGreenFrame(large_size, pixel_format, base::Milliseconds(2));

  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
#if BUILDFLAG(IS_FUCHSIA)
  expected_input_format = PIXEL_FORMAT_I420;
#endif
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, expected_input_format);
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->format(), expected_input_format);
        EXPECT_EQ(frame->coded_size(), options.frame_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  adapter()->Encode(small_frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  adapter()->Encode(large_frame, VideoEncoder::EncodeOptions(false),
                    ValidatingStatusCB());
  RunUntilIdle();
  EXPECT_EQ(outputs_count, 2);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, AutomaticResizeSupport) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  int outputs_count = 0;
  gfx::Size small_size(480, 320);
  auto pixel_format = PIXEL_FORMAT_NV12;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->coded_size(), small_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  vea()->SupportResize();
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame1 =
      CreateGreenFrame(small_size, pixel_format, base::Milliseconds(1));
  auto frame2 =
      CreateGreenFrame(small_size, pixel_format, base::Milliseconds(2));
  adapter()->Encode(frame1, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  adapter()->Encode(frame2, VideoEncoder::EncodeOptions(false),
                    ValidatingStatusCB());
  RunUntilIdle();
  EXPECT_EQ(outputs_count, 2);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, ManualReferenceControl) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.manual_reference_buffer_control = true;
  int outputs_count = 0;
  auto pixel_format = PIXEL_FORMAT_NV12;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame1 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  auto frame2 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(2));

  VideoEncoder::EncodeOptions encode_opts;
  encode_opts.reference_buffers = {};
  encode_opts.update_buffer = 0;
  adapter()->Encode(frame1, encode_opts, ValidatingStatusCB());

  encode_opts.reference_buffers = {0};
  encode_opts.update_buffer = std::nullopt;
  adapter()->Encode(frame2, encode_opts, ValidatingStatusCB());
  RunUntilIdle();
  EXPECT_EQ(outputs_count, 2);
}

TEST_P(VideoEncodeAcceleratorAdapterTest, RunWithAllPossibleInputConversions) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  int outputs_count = 0;
  gfx::Size small_size(480, 320);
  gfx::Size same_size = options.frame_size;
  gfx::Size large_size(800, 600);
  int frames_to_encode = 33;
  auto pixel_format = GetParam();
  auto input_kind =
      (pixel_format == PIXEL_FORMAT_NV12)
          ? VideoEncodeAcceleratorAdapter::InputBufferKind::GpuMemBuf
          : VideoEncodeAcceleratorAdapter::InputBufferKind::CpuMemBuf;
  adapter()->SetInputBufferPreferenceForTesting(input_kind);

  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
  if (input_kind != VideoEncodeAcceleratorAdapter::InputBufferKind::GpuMemBuf) {
#if BUILDFLAG(IS_FUCHSIA)
    expected_input_format = PIXEL_FORMAT_I420;
#endif
  }

  constexpr auto get_source_format = [](int i) {
    // Every 4 frames switch between the 3 supported formats.
    const int rem = i % 12;
    auto format = PIXEL_FORMAT_XRGB;
    if (rem < 4)
      format = PIXEL_FORMAT_I420;
    else if (rem < 8)
      format = PIXEL_FORMAT_NV12;
    return format;
  };

  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        VideoPixelFormat source_frame_format = get_source_format(outputs_count);
        const gfx::ColorSpace expected_color_space =
            ExpectedColorSpace(source_frame_format, expected_input_format);
        EXPECT_EQ(output.color_space, expected_color_space);
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->format(), expected_input_format);
        EXPECT_EQ(frame->coded_size(), options.frame_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  for (int frame_index = 0; frame_index < frames_to_encode; frame_index++) {
    gfx::Size size;
    if (frame_index % 4 == 0)
      size = large_size;
    else if (frame_index % 4 == 1)
      size = small_size;
    else
      size = same_size;

    const auto format = get_source_format(frame_index);
    bool key = frame_index % 9 == 0;
    auto frame =
        CreateGreenFrame(size, format, base::Milliseconds(frame_index));
    adapter()->Encode(frame, VideoEncoder::EncodeOptions(key),
                      ValidatingStatusCB());
  }

  RunUntilIdle();
  EXPECT_EQ(outputs_count, frames_to_encode);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, DroppedFrame) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  auto pixel_format = PIXEL_FORMAT_I420;
  std::vector<base::TimeDelta> output_timestamps;
  std::vector<base::TimeDelta> dropped_output_timestamps;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        if (output.data.empty()) {
          dropped_output_timestamps.push_back(output.timestamp);
          return;
        }
        EXPECT_EQ(output.color_space, expected_color_space);
        output_timestamps.push_back(output.timestamp);
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        size_t size = keyframe ? 1 : 0;  // Drop non-key frame
        return BitstreamBufferMetadata(size, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame1 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  auto frame2 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(2));
  auto frame3 =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(3));
  adapter()->Encode(frame1, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  adapter()->Encode(frame2, VideoEncoder::EncodeOptions(false),
                    ValidatingStatusCB());
  adapter()->Encode(frame3, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();
  ASSERT_EQ(output_timestamps.size(), 2u);
  EXPECT_EQ(output_timestamps[0], base::Milliseconds(1));
  EXPECT_EQ(output_timestamps[1], base::Milliseconds(3));
  ASSERT_EQ(dropped_output_timestamps.size(), 1u);
  EXPECT_EQ(dropped_output_timestamps[0], base::Milliseconds(2));
}

TEST_F(VideoEncodeAcceleratorAdapterTest,
       ChangeOptions_ChangeVariableBitrateSmokeTest) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.bitrate = Bitrate::VariableBitrate(1111u, 2222u);
  auto pixel_format = PIXEL_FORMAT_I420;
  int output_count_before_change = 0;
  int output_count_after_change = 0;
  const gfx::ColorSpace expected_color_space =
      ExpectedColorSpace(pixel_format, pixel_format);
  VideoEncoder::OutputCB first_output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        output_count_before_change++;
      });
  VideoEncoder::OutputCB second_output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.color_space, expected_color_space);
        output_count_after_change++;
      });

  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
#if BUILDFLAG(IS_FUCHSIA)
  expected_input_format = PIXEL_FORMAT_I420;
#endif
  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(keyframe, true);
        EXPECT_EQ(frame->format(), expected_input_format);
        EXPECT_EQ(frame->coded_size(), options.frame_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(first_output_cb), ValidatingStatusCB());
  // We must encode one frame before we can change options.
  auto first_frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  adapter()->Encode(first_frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();

  options.bitrate = Bitrate::VariableBitrate(12345u, 23456u);
  adapter()->ChangeOptions(
      options, std::move(second_output_cb),
      base::BindLambdaForTesting([&](EncoderStatus s) {
        auto second_frame = CreateGreenFrame(options.frame_size, pixel_format,
                                             base::Milliseconds(2));
        adapter()->Encode(second_frame, VideoEncoder::EncodeOptions(true),
                          ValidatingStatusCB());
      }));
  RunUntilIdle();

  EXPECT_EQ(output_count_before_change, 1);
  EXPECT_EQ(output_count_after_change, 1);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, ChangeOptions_ChangeFrameSize) {
  VideoEncoder::Options options;
  auto first_frame_size = gfx::Size(640, 480);
  auto second_frame_size = gfx::Size(1280, 720);
  auto pixel_format = PIXEL_FORMAT_I420;
  int output_count_before_change = 0;
  int output_count_after_change = 0;
  options.frame_size = first_frame_size;
  options.bitrate = Bitrate::VariableBitrate(1111u, 2222u);

  VideoEncoder::OutputCB first_output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.encoded_size, first_frame_size);
        output_count_before_change++;
      });
  VideoEncoder::OutputCB second_output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.encoded_size, second_frame_size);
        output_count_after_change++;
      });

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(first_output_cb), ValidatingStatusCB());
  auto first_frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  adapter()->Encode(first_frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();

  options.frame_size = second_frame_size;
  adapter()->ChangeOptions(
      options, std::move(second_output_cb),
      base::BindLambdaForTesting([&](EncoderStatus s) {
        EXPECT_TRUE(s.is_ok());
        auto second_frame = CreateGreenFrame(options.frame_size, pixel_format,
                                             base::Milliseconds(2));
        adapter()->Encode(second_frame, VideoEncoder::EncodeOptions(true),
                          ValidatingStatusCB());
      }));
  RunUntilIdle();

  EXPECT_EQ(output_count_before_change, 1);
  EXPECT_EQ(output_count_after_change, 1);
}

TEST_F(VideoEncodeAcceleratorAdapterTest,
       ChangeOptions_ChangeFrameSizeNotSupported) {
  VideoEncoder::Options options;
  auto first_frame_size = gfx::Size(640, 480);
  auto second_frame_size = gfx::Size(1280, 720);
  auto pixel_format = PIXEL_FORMAT_I420;
  int output_count_before_change = 0;
  int output_count_after_change = 0;
  options.frame_size = first_frame_size;
  options.bitrate = Bitrate::VariableBitrate(1111u, 2222u);

  VideoEncoder::OutputCB first_output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.encoded_size, first_frame_size);
        output_count_before_change++;
      });
  VideoEncoder::OutputCB second_output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput output,
          std::optional<VideoEncoder::CodecDescription>) {
        EXPECT_EQ(output.encoded_size, second_frame_size);
        output_count_after_change++;
      });

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(first_output_cb), ValidatingStatusCB());
  auto first_frame =
      CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1));
  adapter()->Encode(first_frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  RunUntilIdle();

  vea()->SetSupportFrameSizeChange(false);
  options.frame_size = second_frame_size;
  adapter()->ChangeOptions(options, std::move(second_output_cb),
                           base::BindLambdaForTesting([](EncoderStatus s) {
                             EXPECT_FALSE(s.is_ok());
                           }));

  EXPECT_EQ(output_count_before_change, 1);
  EXPECT_EQ(output_count_after_change, 0);
}

TEST_F(VideoEncodeAcceleratorAdapterTest, ConvertsCpuP010ToSessionFormat) {
  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);

  int outputs_count = 0;
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput, std::optional<VideoEncoder::CodecDescription>) {
        outputs_count++;
      });

  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
#if BUILDFLAG(IS_FUCHSIA)
  expected_input_format = PIXEL_FORMAT_I420;
#endif
  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(keyframe, true);
        EXPECT_EQ(frame->format(), expected_input_format);
        EXPECT_EQ(frame->coded_size(), options.frame_size);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());

  auto frame =
      CreateGreenCpuFrameP010(options.frame_size, base::Milliseconds(1));
  ASSERT_TRUE(frame);
  adapter()->Encode(frame, VideoEncoder::EncodeOptions(true),
                    ValidatingStatusCB());
  EXPECT_TRUE(base::test::RunUntil([&]() { return outputs_count == 1; }));
}

// AV1 profile 0 covers both 8 and 10 bit, so unlike HEVC the profile alone
// doesn't say which one was asked for. The input pixel format is what carries
// that to the platform encoder, hence P010 for a 10 bit request.
TEST_F(VideoEncodeAcceleratorAdapterTest, 10bAv1UsesP010InputFormat) {
  SetSupportedProfile(AV1PROFILE_PROFILE_MAIN,
                      {PIXEL_FORMAT_NV12, PIXEL_FORMAT_P010LE});

  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.bit_depth = 10;

  int outputs_count = 0;
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput, std::optional<VideoEncoder::CodecDescription>) {
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->format(), PIXEL_FORMAT_P010LE);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());
  adapter()->Encode(
      CreateGreenCpuFrame(options.frame_size, base::Milliseconds(1)),
      VideoEncoder::EncodeOptions(true), ValidatingStatusCB());
  EXPECT_TRUE(base::test::RunUntil([&]() { return outputs_count == 1; }));
}

TEST_F(VideoEncodeAcceleratorAdapterTest, 8bAv1KeepsDefaultInputFormat) {
  VideoPixelFormat expected_input_format = PIXEL_FORMAT_NV12;
#if BUILDFLAG(IS_FUCHSIA)
  expected_input_format = PIXEL_FORMAT_I420;
#endif
  // The default format has to be advertised as well, otherwise the adapter
  // rejects the config outright instead of falling back to it.
  SetSupportedProfile(AV1PROFILE_PROFILE_MAIN,
                      {expected_input_format, PIXEL_FORMAT_P010LE});

  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.bit_depth = 8;

  int outputs_count = 0;
  VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
      [&](VideoEncoderOutput, std::optional<VideoEncoder::CodecDescription>) {
        outputs_count++;
      });

  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->format(), expected_input_format);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        std::move(output_cb), ValidatingStatusCB());
  adapter()->Encode(
      CreateGreenCpuFrame(options.frame_size, base::Milliseconds(1)),
      VideoEncoder::EncodeOptions(true), ValidatingStatusCB());
  EXPECT_TRUE(base::test::RunUntil([&]() { return outputs_count == 1; }));
}

TEST_F(VideoEncodeAcceleratorAdapterTest, 10bAv1RejectedWithoutP010) {
  SetSupportedProfile(AV1PROFILE_PROFILE_MAIN, {PIXEL_FORMAT_NV12});

  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.bit_depth = 10;

  bool done_called = false;
  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        /*output_cb=*/base::DoNothing(),
                        base::BindLambdaForTesting([&](EncoderStatus status) {
                          done_called = true;
                          EXPECT_EQ(
                              status.code(),
                              EncoderStatus::Codes::kEncoderUnsupportedConfig);
                        }));
  RunUntilIdle();
  EXPECT_TRUE(done_called);
}

TEST_F(VideoEncodeAcceleratorAdapterTest,
       MatchesChromaAndBitDepthNotFirstRextEntry) {
  profile_ = HEVCPROFILE_REXT;
  VideoEncodeAccelerator::SupportedProfile eight_bit_422(
      profile_, gfx::Size(3840, 2160), 30, 1,
      VideoEncodeAccelerator::kConstantMode |
          VideoEncodeAccelerator::kVariableMode);
  eight_bit_422.gpu_supported_pixel_formats = {PIXEL_FORMAT_NV16};
  eight_bit_422.chroma_sampling = VideoChromaSampling::k422;
  eight_bit_422.bit_depth = 8;

  VideoEncodeAccelerator::SupportedProfile ten_bit_444 = eight_bit_422;
  ten_bit_444.gpu_supported_pixel_formats = {PIXEL_FORMAT_P410LE};
  ten_bit_444.chroma_sampling = VideoChromaSampling::k444;
  ten_bit_444.bit_depth = 10;
  supported_profiles_ = {eight_bit_422, ten_bit_444};
  EXPECT_CALL(*gpu_factories_.get(),
              GetVideoEncodeAcceleratorSupportedProfiles())
      .WillRepeatedly(Return(supported_profiles_));

  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.subsampling = VideoChromaSampling::k444;
  options.bit_depth = 10;
  adapter()->SetInputBufferPreferenceForTesting(
      VideoEncodeAcceleratorAdapter::InputBufferKind::CpuMemBuf);

  int outputs_count = 0;
  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->format(), PIXEL_FORMAT_P410LE);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        base::BindLambdaForTesting(
                            [&](VideoEncoderOutput,
                                std::optional<VideoEncoder::CodecDescription>) {
                              outputs_count++;
                            }),
                        ValidatingStatusCB());

  adapter()->Encode(
      CreateGreenCpuFrame(options.frame_size, base::Milliseconds(1)),
      VideoEncoder::EncodeOptions(true), ValidatingStatusCB());
  EXPECT_TRUE(base::test::RunUntil([&]() { return outputs_count == 1; }));
}

TEST_F(VideoEncodeAcceleratorAdapterTest,
       RextDestDoesNotNeedGpuSupportedFormats) {
  profile_ = HEVCPROFILE_REXT;
  VideoEncodeAccelerator::SupportedProfile rext(
      profile_, gfx::Size(3840, 2160), 30, 1,
      VideoEncodeAccelerator::kConstantMode |
          VideoEncodeAccelerator::kVariableMode);
  rext.chroma_sampling = VideoChromaSampling::k422;
  rext.bit_depth = 8;
  supported_profiles_ = {rext};
  EXPECT_CALL(*gpu_factories_.get(),
              GetVideoEncodeAcceleratorSupportedProfiles())
      .WillRepeatedly(Return(supported_profiles_));

  VideoEncoder::Options options;
  options.frame_size = gfx::Size(640, 480);
  options.subsampling = VideoChromaSampling::k422;
  options.bit_depth = 8;

  int outputs_count = 0;
  vea()->SetEncodingCallback(base::BindLambdaForTesting(
      [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
        EXPECT_EQ(frame->format(), PIXEL_FORMAT_NV16);
        return BitstreamBufferMetadata(1, keyframe, frame->timestamp());
      }));

  adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
                        base::BindLambdaForTesting(
                            [&](VideoEncoderOutput,
                                std::optional<VideoEncoder::CodecDescription>) {
                              outputs_count++;
                            }),
                        ValidatingStatusCB());

  adapter()->Encode(
      CreateGreenCpuFrame(options.frame_size, base::Milliseconds(1)),
      VideoEncoder::EncodeOptions(true), ValidatingStatusCB());
  adapter()->Encode(
      CreateGreenGpuFrame(options.frame_size, base::Milliseconds(2)),
      VideoEncoder::EncodeOptions(false), ValidatingStatusCB());
  EXPECT_TRUE(base::test::RunUntil([&]() { return outputs_count == 2; }));
}

INSTANTIATE_TEST_SUITE_P(VideoEncodeAcceleratorAdapterTest,
                         VideoEncodeAcceleratorAdapterTest,
                         ::testing::Values(PIXEL_FORMAT_I420,
                                           PIXEL_FORMAT_NV12,
                                           PIXEL_FORMAT_XRGB));

}  // namespace media
