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

#include "media/mojo/mojom/video_encode_accelerator_mojom_traits.h"

#include "media/base/video_bitrate_allocation.h"
#include "media/base/video_types.h"
#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
#include "media/mojo/mojom/video_encoder_info_mojom_traits.h"
#include "media/video/video_encode_accelerator.h"
#include "media/video/video_encoder_info.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

TEST(SVCScalabilityModeTest, RoundTrip) {
  auto hw_supported_svc_modes =
      ::media::GetSupportedScalabilityModesByHWEncoderForTesting();
  for (::media::SVCScalabilityMode input_svc_mode : hw_supported_svc_modes) {
    SVCScalabilityMode output_svc_mode;
    ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::SVCScalabilityMode>(
        input_svc_mode, output_svc_mode));
    EXPECT_EQ(input_svc_mode, output_svc_mode);
  }
}

TEST(VideoEncodeAcceleratorSupportedProfile, RoundTrip) {
  ::media::VideoEncodeAccelerator::SupportedProfile input;
  input.profile = VP9PROFILE_PROFILE0;
  input.min_resolution = gfx::Size(64, 64);
  input.max_resolution = gfx::Size(4096, 4096);
  input.max_framerate_numerator = 30;
  input.max_framerate_denominator = 1;
  input.rate_control_modes = VideoEncodeAccelerator::kConstantMode |
                             VideoEncodeAccelerator::kVariableMode;
  input.scalability_modes.push_back(::media::SVCScalabilityMode::kL1T3);
  input.scalability_modes.push_back(::media::SVCScalabilityMode::kL3T3Key);
  input.scalability_modes.push_back(::media::SVCScalabilityMode::kS2T3);
  input.scalability_modes.push_back(::media::SVCScalabilityMode::kS3T1);

  ::media::VideoEncodeAccelerator::SupportedProfile output;
  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
              mojom::VideoEncodeAcceleratorSupportedProfile>(input, output));
  EXPECT_EQ(input, output);
}

TEST(VideoEncodeAcceleratorSupportedProfile, RoundTripChromaAndBitDepth) {
  ::media::VideoEncodeAccelerator::SupportedProfile input;
  input.profile = HEVCPROFILE_REXT;
  input.max_resolution = gfx::Size(1920, 1080);
  input.rate_control_modes = VideoEncodeAccelerator::kConstantMode;
  input.chroma_sampling = VideoChromaSampling::k422;
  input.bit_depth = 10;

  ::media::VideoEncodeAccelerator::SupportedProfile output;
  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
              mojom::VideoEncodeAcceleratorSupportedProfile>(input, output));
  EXPECT_EQ(input, output);
}

TEST(VideoEncoderInfoStructTraitTest, RoundTrip) {
  ::media::VideoEncoderInfo input;
  input.implementation_name = "FakeVideoEncodeAccelerator";
  // Set `frame_delay` but leave `input_capacity` empty.
  input.frame_delay = 3;
  // FPS allocation.
  for (size_t i = 0; i < ::media::VideoEncoderInfo::kMaxSpatialLayers; ++i)
    input.fps_allocation[i] = {5, 5, 10};
  // Resolution bitrate limits.
  input.resolution_rate_limits.push_back(::media::ResolutionRateLimit(
      gfx::Size(123, 456), 123456, 123456, 789012, 30, 1));
  input.resolution_rate_limits.push_back(::media::ResolutionRateLimit(
      gfx::Size(789, 1234), 1234567, 1234567, 7890123, 30, 1));
  // Other bool values.
  input.supports_native_handle = true;
  input.has_trusted_rate_controller = true;
  input.is_hardware_accelerated = true;
  input.supports_simulcast = true;
  input.supports_gpu_shared_images = true;
  input.gpu_supported_pixel_formats.push_back(
      ::media::VideoPixelFormat::PIXEL_FORMAT_NV12);
  input.gpu_supported_pixel_formats.push_back(
      ::media::VideoPixelFormat::PIXEL_FORMAT_BGRA);

  ::media::VideoEncoderInfo output = input;
  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::VideoEncoderInfo>(
      input, output));
  EXPECT_EQ(input, output);
}

TEST(VideoBitrateAllocationStructTraitTest, ConstantBitrate_RoundTrip) {
  ::media::VideoBitrateAllocation input_allocation;
  ASSERT_TRUE(input_allocation.SetBitrate(0, 0, 1000u));
  ASSERT_TRUE(input_allocation.SetBitrate(1, 0, 3500u));
  ASSERT_TRUE(input_allocation.SetBitrate(0, 1, 2000u));
  ASSERT_TRUE(input_allocation.SetBitrate(1, 1, 5000u));
  ::media::VideoBitrateAllocation output_allocation;

  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::VideoBitrateAllocation>(
          input_allocation, output_allocation));

  EXPECT_EQ(input_allocation, output_allocation);
}

TEST(VideoBitrateAllocationStructTraitTest,
     VariableBitrate_PeakGreaterThanSum_RoundTrip) {
  ::media::VideoBitrateAllocation input_allocation(Bitrate::Mode::kVariable);
  input_allocation.SetBitrate(0, 0, 5000u);
  input_allocation.SetPeakBps(654321u);
  ::media::VideoBitrateAllocation output_allocation;

  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::VideoBitrateAllocation>(
          input_allocation, output_allocation));

  EXPECT_EQ(input_allocation, output_allocation);
}

TEST(VideoBitrateAllocationStructTraitTest,
     VariableBitrate_PeakEqualsSum_RoundTrip) {
  ::media::VideoBitrateAllocation input_allocation(Bitrate::Mode::kVariable);
  input_allocation.SetBitrate(0, 0, 5000u);
  input_allocation.SetPeakBps(5000u);
  ::media::VideoBitrateAllocation output_allocation;

  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::VideoBitrateAllocation>(
          input_allocation, output_allocation));

  EXPECT_EQ(input_allocation, output_allocation);
}

TEST(VideoBitrateAllocationStructTraitTest,
     VariableBitrate_InvalidTooLowPeak_Fails) {
  mojom::VideoBitrateAllocationPtr mojom_allocation =
      mojom::VideoBitrateAllocation::New();
  std::vector<uint32_t> bitrates = {1000u, 2500u, 3000u};
  mojom_allocation->bitrates = bitrates;
  mojom::VariableBitratePeakPtr mojom_peak = mojom::VariableBitratePeak::New();
  mojom_peak->bps = 6499u;  // invalid: must be >=6500u
  mojom_allocation->variable_bitrate_peak = std::move(mojom_peak);
  VideoBitrateAllocation output;

  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::VideoBitrateAllocation>(
          mojom_allocation, output));
}

TEST(VideoBitrateAllocationStructTraitTest,
     VariableBitrate_InvalidZeroPeak_Fails) {
  mojom::VideoBitrateAllocationPtr mojom_allocation =
      mojom::VideoBitrateAllocation::New();
  std::vector<uint32_t> bitrates = {0u};
  mojom_allocation->bitrates = bitrates;
  mojom::VariableBitratePeakPtr mojom_peak = mojom::VariableBitratePeak::New();
  mojom_peak->bps = 0u;
  mojom_allocation->variable_bitrate_peak = std::move(mojom_peak);
  VideoBitrateAllocation output;

  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::VideoBitrateAllocation>(
          mojom_allocation, output));
}

TEST(SpatialLayerStructTraitTest, RoundTrip) {
  ::media::VideoEncodeAccelerator::Config::SpatialLayer input_spatial_layer;
  input_spatial_layer.width = 320;
  input_spatial_layer.width = 180;
  input_spatial_layer.bitrate_bps = 12345678u;
  input_spatial_layer.framerate = 24u;
  input_spatial_layer.max_qp = 30u;
  input_spatial_layer.num_of_temporal_layers = 3u;
  ::media::VideoEncodeAccelerator::Config::SpatialLayer output_spatial_layer;
  ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::SpatialLayer>(
      input_spatial_layer, output_spatial_layer));
  EXPECT_EQ(input_spatial_layer, output_spatial_layer);
}

TEST(VideoEncodeAcceleratorConfigStructTraitTest, RoundTrip) {
  std::vector<::media::VideoEncodeAccelerator::Config::SpatialLayer>
      input_spatial_layers(3);
  constexpr gfx::Size kBaseSize(320, 180);
  constexpr uint32_t kBaseBitrateBps = 123456u;
  constexpr uint32_t kBaseFramerate = 24u;
  for (size_t i = 0; i < input_spatial_layers.size(); ++i) {
    input_spatial_layers[i].width =
        static_cast<int32_t>(kBaseSize.width() * (i + 1));
    input_spatial_layers[i].height =
        static_cast<int32_t>(kBaseSize.height() * (i + 1));
    input_spatial_layers[i].bitrate_bps = kBaseBitrateBps * (i + 1) / 2;
    input_spatial_layers[i].framerate = kBaseFramerate * 2 / (i + 1);
    input_spatial_layers[i].max_qp = 30 * (i + 1) / 2;
    input_spatial_layers[i].num_of_temporal_layers = 3 - i;
  }
  constexpr ::media::Bitrate kBitrate =
      ::media::Bitrate::ConstantBitrate(kBaseBitrateBps);

  ::media::VideoEncodeAccelerator::Config input_config(
      ::media::PIXEL_FORMAT_NV12, kBaseSize, ::media::VP9PROFILE_PROFILE0,
      kBitrate, kBaseFramerate,
      ::media::VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer,
      ::media::VideoEncodeAccelerator::Config::ContentType::kCamera);
  input_config.drop_frame_thresh_percentage = 30;
  input_config.spatial_layers = input_spatial_layers;
  input_config.inter_layer_pred = ::media::SVCInterLayerPredMode::kOnKeyPic;

  ::media::VideoEncodeAccelerator::Config output_config{};
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::VideoEncodeAcceleratorConfig>(
          input_config, output_config));
  EXPECT_EQ(input_config, output_config);
}

TEST(VideoEncodeAcceleratorConfigStructTraitTest, RoundTripVariableBitrate) {
  constexpr gfx::Size kBaseSize(320, 180);
  constexpr uint32_t kBaseBitrateBps = 123456u;
  constexpr uint32_t kMaximumBitrate = 999999u;
  const ::media::Bitrate kBitrate =
      ::media::Bitrate::VariableBitrate(kBaseBitrateBps, kMaximumBitrate);
  ::media::VideoEncodeAccelerator::Config input_config(
      ::media::PIXEL_FORMAT_NV12, kBaseSize, ::media::VP9PROFILE_PROFILE0,
      kBitrate, 30,
      ::media::VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer,
      ::media::VideoEncodeAccelerator::Config::ContentType::kCamera);
  input_config.manual_reference_buffer_control = true;

  ::media::VideoEncodeAccelerator::Config output_config{};
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::VideoEncodeAcceleratorConfig>(
          input_config, output_config));
  EXPECT_EQ(input_config, output_config);
}

TEST(VideoEncodeAcceleratorConfigStructTraitTest,
     NonEmptySpatialLayerWithManualReference_Rejected) {
  constexpr gfx::Size kBaseSize(320, 180);
  constexpr uint32_t kBaseBitrateBps = 123456u;
  const ::media::Bitrate kBitrate =
      ::media::Bitrate::ConstantBitrate(kBaseBitrateBps);

  ::media::VideoEncodeAccelerator::Config input_config(
      ::media::PIXEL_FORMAT_NV12, kBaseSize, ::media::VP9PROFILE_PROFILE0,
      kBitrate, 30,
      ::media::VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer,
      ::media::VideoEncodeAccelerator::Config::ContentType::kCamera);
  input_config.manual_reference_buffer_control = true;

  ::media::VideoEncodeAccelerator::Config::SpatialLayer spatial_layer;
  spatial_layer.width = kBaseSize.width();
  spatial_layer.height = kBaseSize.height();
  spatial_layer.bitrate_bps = kBaseBitrateBps;
  spatial_layer.framerate = 30;
  spatial_layer.num_of_temporal_layers = 1;
  input_config.spatial_layers.push_back(spatial_layer);

  ::media::VideoEncodeAccelerator::Config output_config{};
  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::VideoEncodeAcceleratorConfig>(
          input_config, output_config));
}

TEST(VideoEncodeAcceleratorConfigStructTraitTest,
     InterLayerPredNotOffWithManualReference_Rejected) {
  constexpr gfx::Size kBaseSize(320, 180);
  constexpr uint32_t kBaseBitrateBps = 123456u;
  const ::media::Bitrate kBitrate =
      ::media::Bitrate::ConstantBitrate(kBaseBitrateBps);

  ::media::VideoEncodeAccelerator::Config input_config(
      ::media::PIXEL_FORMAT_NV12, kBaseSize, ::media::VP9PROFILE_PROFILE0,
      kBitrate, 30,
      ::media::VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer,
      ::media::VideoEncodeAccelerator::Config::ContentType::kCamera);
  input_config.manual_reference_buffer_control = true;
  input_config.inter_layer_pred = ::media::SVCInterLayerPredMode::kOn;

  ::media::VideoEncodeAccelerator::Config output_config{};
  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::VideoEncodeAcceleratorConfig>(
          input_config, output_config));
}

TEST(VariableBitrateStructTraitTest, PeakZeroBps_Rejected) {
  mojom::VariableBitratePtr mojom_variable_bitrate =
      mojom::VariableBitrate::New();
  mojom_variable_bitrate->target_bps = 0u;
  mojom_variable_bitrate->peak_bps = 0u;
  Bitrate output;

  bool result = mojo::test::SerializeAndDeserialize<mojom::VariableBitrate>(
      mojom_variable_bitrate, output);
  EXPECT_FALSE(result);
}

TEST(VariableBitrateStructTraitTest, PeakLessThanTarget_Rejected) {
  mojom::VariableBitratePtr mojom_variable_bitrate =
      mojom::VariableBitrate::New();
  mojom_variable_bitrate->target_bps = 6000u;
  mojom_variable_bitrate->peak_bps = 5999u;
  Bitrate output;

  bool result = mojo::test::SerializeAndDeserialize<mojom::VariableBitrate>(
      mojom_variable_bitrate, output);
  EXPECT_FALSE(result);
}

TEST(BitstreamBufferMetadataTraitTest, RoundTrip) {
  BitstreamBufferMetadata input_metadata;
  input_metadata.payload_size_bytes = 1234;
  input_metadata.key_frame = true;
  input_metadata.timestamp = base::Milliseconds(123456);
  BitstreamBufferMetadata output_metadata;
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);

  H264Metadata h264;
  h264.temporal_idx = 1;
  h264.layer_sync = true;
  input_metadata.h264 = h264;
  output_metadata = BitstreamBufferMetadata();
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);
  input_metadata.h264.reset();

  Vp8Metadata vp8;
  vp8.non_reference = true;
  vp8.temporal_idx = 1;
  vp8.layer_sync = false;
  input_metadata.vp8 = vp8;
  output_metadata = BitstreamBufferMetadata();
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);
  input_metadata.vp8.reset();

  SVCGenericMetadata svc_generic;
  svc_generic.follow_svc_spec = true;
  svc_generic.temporal_idx = 2;
  svc_generic.spatial_idx = 1;
  svc_generic.refresh_flags = 0b11111111;
  svc_generic.reference_flags = 0b00000001;
  input_metadata.svc_generic = svc_generic;
  output_metadata = BitstreamBufferMetadata();
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);
  input_metadata.svc_generic.reset();

  Vp9Metadata vp9;
  vp9.inter_pic_predicted = true;
  vp9.temporal_up_switch = true;
  vp9.referenced_by_upper_spatial_layers = true;
  vp9.reference_lower_spatial_layers = true;
  vp9.end_of_picture = false;
  vp9.temporal_idx = 2;
  vp9.spatial_idx = 0;
  vp9.spatial_layer_resolutions = {gfx::Size(320, 180), gfx::Size(640, 360)};
  vp9.p_diffs = {0, 1};
  input_metadata.vp9 = vp9;
  output_metadata = BitstreamBufferMetadata();
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);

  input_metadata =
      BitstreamBufferMetadata::CreateForDropFrame(base::Milliseconds(123456),
                                                  /*spatial_idx=*/1u, false);
  CHECK(input_metadata.drop);
  output_metadata = BitstreamBufferMetadata();
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);
}

TEST(BitstreamBufferMetadataTraitTest, YuvPsnrRoundTrip) {
  BitstreamBufferMetadata input_metadata;
  input_metadata.payload_size_bytes = 1234;
  input_metadata.key_frame = true;
  input_metadata.timestamp = base::Milliseconds(123456);

  // Full PSNR channels
  YuvPsnr yuv_psnr;
  yuv_psnr.y = 40.1;
  yuv_psnr.u = 41.2;
  yuv_psnr.v = 42.3;
  input_metadata.yuv_psnr = yuv_psnr;
  BitstreamBufferMetadata output_metadata;
  ASSERT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
  EXPECT_EQ(input_metadata, output_metadata);
}

TEST(BitstreamBufferMetadataTraitTest, YuvPsnrValidation) {
  BitstreamBufferMetadata input_metadata;
  input_metadata.payload_size_bytes = 1234;
  input_metadata.key_frame = true;
  input_metadata.timestamp = base::Milliseconds(123456);

  // Negative values should fail.
  input_metadata.yuv_psnr = YuvPsnr{.y = -1.0, .u = 41.2, .v = 42.3};
  BitstreamBufferMetadata output_metadata;
  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));

  input_metadata.yuv_psnr = YuvPsnr{.y = 40.0, .u = -0.5, .v = 42.0};
  output_metadata = BitstreamBufferMetadata();
  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));

  // Reject NotANumber.
  input_metadata.yuv_psnr = YuvPsnr{
      .y = std::numeric_limits<double>::quiet_NaN(), .u = 41.2, .v = 42.3};
  output_metadata = BitstreamBufferMetadata();
  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));

  // Positive infinity should be allowed (bit perfect match).
  input_metadata.yuv_psnr = YuvPsnr{
      .y = std::numeric_limits<double>::infinity(), .u = 41.2, .v = 42.3};
  output_metadata = BitstreamBufferMetadata();
  EXPECT_TRUE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));

  // Negative infinity should be rejected (same as any negative).
  input_metadata.yuv_psnr = YuvPsnr{
      .y = -std::numeric_limits<double>::infinity(), .u = 41.2, .v = 42.3};
  output_metadata = BitstreamBufferMetadata();
  EXPECT_FALSE(
      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
          input_metadata, output_metadata));
}

}  // namespace media
