/*
 *  Copyright (c) 2016 The WebM project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include <array>
#include <cmath>
#include <cassert>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <vector>

#include "gtest/gtest.h"
#include "test/acm_random.h"
#include "test/video_source.h"
#include "test/y4m_video_source.h"
#include "test/yuv_video_source.h"

#include "./vpx_config.h"
#include "vpx/vp8cx.h"
#include "vpx/vpx_codec.h"
#include "vpx/vpx_encoder.h"
#include "vpx/vpx_image.h"
#include "vpx_mem/vpx_mem.h"

#if CONFIG_VP9_ENCODER
#include "vp9/encoder/vp9_encoder.h"
#include "vp9/encoder/vp9_firstpass_stats.h"
#endif

namespace {

vpx_codec_iface_t *kCodecIfaces[] = {
#if CONFIG_VP8_ENCODER
  &vpx_codec_vp8_cx_algo,
#endif
#if CONFIG_VP9_ENCODER
  &vpx_codec_vp9_cx_algo,
#endif
};

bool IsVP9(vpx_codec_iface_t *iface) {
  static const char kVP9Name[] = "WebM Project VP9";
  return strncmp(kVP9Name, vpx_codec_iface_name(iface), sizeof(kVP9Name) - 1) ==
         0;
}

void *Memset16(void *dest, int val, size_t length) {
  uint16_t *dest16 = reinterpret_cast<uint16_t *>(dest);
  for (size_t i = 0; i < length; i++) {
    *dest16++ = val;
  }
  return dest;
}

vpx_image_t *CreateImage(vpx_bit_depth_t bit_depth, vpx_img_fmt_t fmt,
                         unsigned int width, unsigned int height,
                         libvpx_test::ACMRandom *rng = nullptr) {
  assert(fmt != VPX_IMG_FMT_NV12);
  if (bit_depth > VPX_BITS_8) {
    fmt = static_cast<vpx_img_fmt_t>(fmt | VPX_IMG_FMT_HIGHBITDEPTH);
  }
  vpx_image_t *image = vpx_img_alloc(nullptr, fmt, width, height, 1);
  if (!image) return image;

  auto get_val = [bit_depth, &rng]() {
    if (rng != nullptr) {
      switch (bit_depth) {
        case VPX_BITS_8: return static_cast<int>(rng->Rand8());
        case VPX_BITS_10: return static_cast<int>(rng->Rand10());
        case VPX_BITS_12: return static_cast<int>(rng->Rand12());
      }
    }
    return 1 << (bit_depth - 1);
  };
  const int val_y = get_val();
  const int val_u = get_val();
  const int val_v = get_val();
  const unsigned int uv_h =
      (image->d_h + image->y_chroma_shift) >> image->y_chroma_shift;
  const unsigned int uv_w =
      (image->d_w + image->x_chroma_shift) >> image->x_chroma_shift;
  if (bit_depth > VPX_BITS_8) {
    for (unsigned int i = 0; i < image->d_h; ++i) {
      Memset16(image->planes[0] + i * image->stride[0], val_y, image->d_w);
    }
    for (unsigned int i = 0; i < uv_h; ++i) {
      Memset16(image->planes[1] + i * image->stride[1], val_u, uv_w);
      Memset16(image->planes[2] + i * image->stride[2], val_v, uv_w);
    }
  } else {
    for (unsigned int i = 0; i < image->d_h; ++i) {
      memset(image->planes[0] + i * image->stride[0], val_y, image->d_w);
    }
    for (unsigned int i = 0; i < uv_h; ++i) {
      memset(image->planes[1] + i * image->stride[1], val_u, uv_w);
      memset(image->planes[2] + i * image->stride[2], val_v, uv_w);
    }
  }

  return image;
}

void InitCodec(vpx_codec_iface_t &iface, int width, int height,
               vpx_codec_ctx_t *enc, vpx_codec_enc_cfg_t *cfg) {
  cfg->g_w = width;
  cfg->g_h = height;
  cfg->g_lag_in_frames = 0;
  cfg->g_pass = VPX_RC_ONE_PASS;
  ASSERT_EQ(vpx_codec_enc_init(enc, &iface, cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control_(enc, VP8E_SET_CPUUSED, 2), VPX_CODEC_OK);
}

// Encodes 1 frame of size |cfg.g_w| x |cfg.g_h| setting |enc|'s configuration
// to |cfg|.
void EncodeWithConfig(const vpx_codec_enc_cfg_t &cfg, vpx_codec_ctx_t *enc) {
  libvpx_test::DummyVideoSource video;
  video.SetSize(cfg.g_w, cfg.g_h);
  video.Begin();
  EXPECT_EQ(vpx_codec_enc_config_set(enc, &cfg), VPX_CODEC_OK)
      << vpx_codec_error_detail(enc);

  EXPECT_EQ(vpx_codec_encode(enc, video.img(), video.pts(), video.duration(),
                             /*flags=*/0, VPX_DL_GOOD_QUALITY),
            VPX_CODEC_OK)
      << vpx_codec_error_detail(enc);
}

TEST(EncodeAPI, InvalidParams) {
  uint8_t buf[1] = { 0 };
  vpx_image_t img;
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  EXPECT_EQ(&img, vpx_img_wrap(&img, VPX_IMG_FMT_I420, 1, 1, 1, buf));

  EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
            vpx_codec_enc_init(nullptr, nullptr, nullptr, 0));
  EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
            vpx_codec_enc_init(&enc, nullptr, nullptr, 0));
  EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
            vpx_codec_encode(nullptr, nullptr, 0, 0, 0, 0));
  EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
            vpx_codec_encode(nullptr, &img, 0, 0, 0, 0));
  EXPECT_EQ(VPX_CODEC_INVALID_PARAM, vpx_codec_destroy(nullptr));
  EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
            vpx_codec_enc_config_default(nullptr, nullptr, 0));
  EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
            vpx_codec_enc_config_default(nullptr, &cfg, 0));
  EXPECT_NE(vpx_codec_error(nullptr), nullptr);

  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
              vpx_codec_enc_init(nullptr, iface, nullptr, 0));
    EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
              vpx_codec_enc_init(&enc, iface, nullptr, 0));
    EXPECT_EQ(VPX_CODEC_INVALID_PARAM,
              vpx_codec_enc_config_default(iface, &cfg, 1));

    EXPECT_EQ(VPX_CODEC_OK, vpx_codec_enc_config_default(iface, &cfg, 0));
    EXPECT_EQ(VPX_CODEC_OK, vpx_codec_enc_init(&enc, iface, &cfg, 0));
    EXPECT_EQ(VPX_CODEC_OK, vpx_codec_encode(&enc, nullptr, 0, 0, 0, 0));

    EXPECT_EQ(VPX_CODEC_OK, vpx_codec_destroy(&enc));
  }
}

TEST(EncodeAPI, InvalidUVStrides) {
  static const std::vector<vpx_img_fmt_t> kVp8ImageFormats = {
    VPX_IMG_FMT_YV12, VPX_IMG_FMT_I420, VPX_IMG_FMT_NV12
  };
  static const std::vector<vpx_img_fmt_t> kVp9ImageFormats = {
    VPX_IMG_FMT_YV12,   VPX_IMG_FMT_I420,   VPX_IMG_FMT_I422,
    VPX_IMG_FMT_I444,   VPX_IMG_FMT_I440,   VPX_IMG_FMT_NV12,
    VPX_IMG_FMT_I42016, VPX_IMG_FMT_I42216, VPX_IMG_FMT_I44416,
    VPX_IMG_FMT_I44016
  };
  struct UVStride {
    int u_stride;
    int v_stride;
  };
  constexpr int kWidth = 64;
  constexpr int kHeight = 64;
  static constexpr std::array<UVStride, 4> kUVStrides = {
    UVStride{ kWidth, kWidth - 1 }, UVStride{ kWidth, kWidth + 1 },
    UVStride{ kWidth - 1, kWidth }, UVStride{ kWidth + 1, kWidth }
  };
  vpx_image_t img;
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  // Allocate a buffer large enough for a non-subsampled, high bitdepth image.
  auto buf = std::make_unique<uint8_t[]>(kWidth * kHeight * 3 * 2);

  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, /*usage=*/0),
              VPX_CODEC_OK);

    for (const auto img_fmt :
         IsVP9(iface) ? kVp9ImageFormats : kVp8ImageFormats) {
      const bool high_bit_depth =
          (img_fmt & VPX_IMG_FMT_HIGHBITDEPTH) == VPX_IMG_FMT_HIGHBITDEPTH;
      if (high_bit_depth &&
          !(vpx_codec_get_caps(iface) & VPX_CODEC_CAP_HIGHBITDEPTH)) {
        break;
      }
      ASSERT_EQ(vpx_img_wrap(&img, img_fmt, kWidth, kHeight, /*stride_align=*/1,
                             buf.get()),
                &img)
          << "Unable to wrap vpx_image for format: " << img_fmt;
      const bool is_420 = img.x_chroma_shift == 1 && img.y_chroma_shift == 1;
      // In profiles 0 and 2, only 4:2:0 format is allowed. In profiles 1 and 3,
      // all other subsampling formats are allowed. In profiles 0 and 1, only
      // bit depth 8 is allowed. In profiles 2 and 3, only bit depths 10 and 12
      // are allowed.
      cfg.g_profile = 2 * high_bit_depth + !is_420;
      cfg.g_w = kWidth;
      cfg.g_h = kHeight;
      cfg.g_bit_depth = high_bit_depth ? VPX_BITS_10 : VPX_BITS_8;
      ASSERT_EQ(
          vpx_codec_enc_init(&enc, iface, &cfg,
                             high_bit_depth ? VPX_CODEC_USE_HIGHBITDEPTH : 0),
          VPX_CODEC_OK)
          << " high_bit_depth: " << high_bit_depth;

      for (const auto uv_stride : kUVStrides) {
        const UVStride orig = { img.stride[VPX_PLANE_U],
                                img.stride[VPX_PLANE_V] };
        img.stride[VPX_PLANE_U] = uv_stride.u_stride;
        img.stride[VPX_PLANE_V] = uv_stride.v_stride;
        EXPECT_EQ(vpx_codec_encode(&enc, &img, /*pts=*/0, /*duration=*/1,
                                   /*flags=*/0, VPX_DL_REALTIME),
                  VPX_CODEC_INVALID_PARAM)
            << "Error: " << vpx_codec_error_detail(&enc)
            << ", format: " << img_fmt << ", U stride: " << uv_stride.u_stride
            << ", V stride: " << uv_stride.v_stride;

        // Ensure the encoder can recover when given valid strides.
        img.stride[VPX_PLANE_U] = orig.u_stride;
        img.stride[VPX_PLANE_V] = orig.v_stride;
        EXPECT_EQ(vpx_codec_encode(&enc, &img, /*pts=*/0, /*duration=*/1,
                                   /*flags=*/0, VPX_DL_REALTIME),
                  VPX_CODEC_OK)
            << "Error: " << vpx_codec_error_detail(&enc)
            << ", format: " << img_fmt << ", U stride: " << orig.u_stride
            << ", V stride: " << orig.v_stride;
      }

      EXPECT_EQ(
          vpx_codec_encode(&enc, /*img=*/nullptr, /*pts=*/0, /*duration=*/0,
                           /*flags=*/0, VPX_DL_REALTIME),
          VPX_CODEC_OK);
      EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
    }
  }
}

TEST(EncodeAPI, InvalidImageFormats) {
  EXPECT_EQ(vpx_img_alloc(/*img=*/nullptr, VPX_IMG_FMT_NONE, /*d_w=*/32,
                          /*d_h=*/32, /*align=*/1),
            nullptr);
  EXPECT_EQ(vpx_img_alloc(/*img=*/nullptr,
                          static_cast<vpx_img_fmt_t>(VPX_IMG_FMT_NONE - 1),
                          /*d_w=*/32, /*d_h=*/32, /*align=*/1),
            nullptr);
  EXPECT_EQ(vpx_img_alloc(/*img=*/nullptr,
                          static_cast<vpx_img_fmt_t>(VPX_IMG_FMT_NV12 + 1),
                          /*d_w=*/32, /*d_h=*/32, /*align=*/1),
            nullptr);
  EXPECT_EQ(vpx_img_alloc(/*img=*/nullptr,
                          static_cast<vpx_img_fmt_t>(VPX_IMG_FMT_I44016 + 1),
                          /*d_w=*/32, /*d_h=*/32, /*align=*/1),
            nullptr);
}

TEST(EncodeAPI, HighBitDepthCapability) {
// VP8 should not claim VP9 HBD as a capability.
#if CONFIG_VP8_ENCODER
  const vpx_codec_caps_t vp8_caps = vpx_codec_get_caps(&vpx_codec_vp8_cx_algo);
  EXPECT_EQ(vp8_caps & VPX_CODEC_CAP_HIGHBITDEPTH, 0);
#endif

#if CONFIG_VP9_ENCODER
  const vpx_codec_caps_t vp9_caps = vpx_codec_get_caps(&vpx_codec_vp9_cx_algo);
#if CONFIG_VP9_HIGHBITDEPTH
  EXPECT_EQ(vp9_caps & VPX_CODEC_CAP_HIGHBITDEPTH, VPX_CODEC_CAP_HIGHBITDEPTH);
#else
  EXPECT_EQ(vp9_caps & VPX_CODEC_CAP_HIGHBITDEPTH, 0);
#endif
#endif
}

#if CONFIG_VP8_ENCODER
TEST(EncodeAPI, ImageSizeSetting) {
  const int width = 711;
  const int height = 360;
  const int bps = 12;
  vpx_image_t img;
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  uint8_t *img_buf = reinterpret_cast<uint8_t *>(
      calloc(width * height * bps / 8, sizeof(*img_buf)));
  vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &cfg, 0);

  cfg.g_w = width;
  cfg.g_h = height;

  vpx_img_wrap(&img, VPX_IMG_FMT_I420, width, height, 1, img_buf);

  vpx_codec_enc_init(&enc, vpx_codec_vp8_cx(), &cfg, 0);

  EXPECT_EQ(VPX_CODEC_OK, vpx_codec_encode(&enc, &img, 0, 1, 0, 0));

  free(img_buf);

  vpx_codec_destroy(&enc);
}

// Verifies the fix for a float-cast-overflow in vp8_change_config().
//
// Causes cpi->framerate to become the largest possible value (10,000,000) in
// VP8 by setting cfg.g_timebase to 1/10000000 and passing a duration of 1 to
// vpx_codec_encode().
TEST(EncodeAPI, HugeFramerateVp8) {
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = 271;
  cfg.g_h = 1080;
  cfg.g_timebase.num = 1;
  // Largest value (VP8's TICKS_PER_SEC) such that frame duration is nonzero (1
  // tick).
  cfg.g_timebase.den = 10000000;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  cfg.rc_end_usage = VPX_CBR;

  vpx_codec_ctx_t enc;
  // Before we encode the first frame, cpi->framerate is set to a guess (the
  // reciprocal of cfg.g_timebase). If this guess doesn't seem reasonable
  // (> 180), cpi->framerate is set to 30.
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, -12), VPX_CODEC_OK);

  vpx_image_t *const image =
      vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
  ASSERT_NE(image, nullptr);

  for (unsigned int i = 0; i < image->d_h; ++i) {
    memset(image->planes[0] + i * image->stride[0], 128, image->d_w);
  }
  const unsigned int uv_h = (image->d_h + 1) / 2;
  const unsigned int uv_w = (image->d_w + 1) / 2;
  for (unsigned int i = 0; i < uv_h; ++i) {
    memset(image->planes[1] + i * image->stride[1], 128, uv_w);
    memset(image->planes[2] + i * image->stride[2], 128, uv_w);
  }

  // Encode a frame.
  // Up to this point cpi->framerate is 30. Now pass a duration of only 1. This
  // causes cpi->framerate to become 10,000,000.
  ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Change to the same config. Since cpi->framerate is now huge, when it is
  // used to calculate raw_target_rate (bit rate of uncompressed frames), the
  // result is likely to overflow an unsigned int.
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// A test that reproduces https://crbug.com/webm/1831.
TEST(EncodeAPI, RandomPixelsVp8) {
  // Initialize libvpx encoder
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.rc_target_bitrate = 2000;
  cfg.g_w = 1280;
  cfg.g_h = 720;

  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Generate random frame data and encode
  libvpx_test::RandomVideoSource video;
  video.SetSize(cfg.g_w, cfg.g_h);
  video.SetImageFormat(VPX_IMG_FMT_I420);
  video.Begin();
  ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), video.duration(),
                             /*flags=*/0, VPX_DL_BEST_QUALITY),
            VPX_CODEC_OK);

  // Destroy libvpx encoder
  vpx_codec_destroy(&enc);
}

// Large static_threshold: issue: 505902433.
TEST(EncodeAPI, LargeStaticThresholdVp8) {
  // Initialize libvpx encoder
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.rc_target_bitrate = 2000;
  cfg.g_w = 1280;
  cfg.g_h = 720;

  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, -8), VPX_CODEC_OK);

  // 131071 = 1024 << 7 - 1: max value for static_threshold.
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_STATIC_THRESHOLD, 131072),
            VPX_CODEC_INVALID_PARAM);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_STATIC_THRESHOLD, 131071),
            VPX_CODEC_OK);

  // Generate random frame data and encode
  libvpx_test::RandomVideoSource video;
  video.SetSize(cfg.g_w, cfg.g_h);
  video.SetImageFormat(VPX_IMG_FMT_I420);
  video.Begin();
  ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), video.duration(),
                             /*flags=*/0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Destroy libvpx encoder
  vpx_codec_destroy(&enc);
}

TEST(EncodeAPI, ChangeToL1T3AndSetBitrateVp8) {
  // Initialize libvpx encoder
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_threads = 1;
  cfg.g_profile = 0;
  cfg.g_w = 1;
  cfg.g_h = 64;
  cfg.g_bit_depth = VPX_BITS_8;
  cfg.g_input_bit_depth = 8;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 1000000;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  cfg.rc_dropframe_thresh = 0;  // Don't drop frames
  cfg.rc_resize_allowed = 0;
  cfg.rc_end_usage = VPX_VBR;
  cfg.rc_target_bitrate = 10;
  cfg.rc_min_quantizer = 2;
  cfg.rc_max_quantizer = 58;
  cfg.kf_mode = VPX_KF_AUTO;
  cfg.kf_min_dist = 0;
  cfg.kf_max_dist = 10000;

  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, -6), VPX_CODEC_OK);

  // Generate random frame data and encode
  uint8_t img[1 * 64 * 3 / 2];
  libvpx_test::ACMRandom rng;
  for (size_t i = 0; i < sizeof(img); ++i) {
    img[i] = rng.Rand8();
  }
  vpx_image_t img_wrapper;
  ASSERT_EQ(
      vpx_img_wrap(&img_wrapper, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1, img),
      &img_wrapper);
  vpx_enc_frame_flags_t flags = VPX_EFLAG_FORCE_KF;
  ASSERT_EQ(
      vpx_codec_encode(&enc, &img_wrapper, 0, 500000, flags, VPX_DL_REALTIME),
      VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_encode(&enc, nullptr, -1, 0, 0, 0), VPX_CODEC_OK);

  cfg.rc_target_bitrate = 4294967;
  // Set the scalability mode to L1T3.
  cfg.ts_number_layers = 3;
  cfg.ts_periodicity = 4;
  cfg.ts_layer_id[0] = 0;
  cfg.ts_layer_id[1] = 2;
  cfg.ts_layer_id[2] = 1;
  cfg.ts_layer_id[3] = 2;
  cfg.ts_rate_decimator[0] = 4;
  cfg.ts_rate_decimator[1] = 2;
  cfg.ts_rate_decimator[2] = 1;
  // Bitrate allocation L0: 50% L1: 20% L2: 30%
  cfg.layer_target_bitrate[0] = cfg.ts_target_bitrate[0] =
      50 * cfg.rc_target_bitrate / 100;
  cfg.layer_target_bitrate[1] = cfg.ts_target_bitrate[1] =
      70 * cfg.rc_target_bitrate / 100;
  cfg.layer_target_bitrate[2] = cfg.ts_target_bitrate[2] =
      cfg.rc_target_bitrate;
  cfg.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_0212;
  cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_TEMPORAL_LAYER_ID, 2),
            VPX_CODEC_OK);

  constexpr vpx_enc_frame_flags_t VP8_UPDATE_NOTHING =
      VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_LAST;
  // Layer 2: only reference last frame, no updates
  // It only depends on layer 0
  flags = VP8_UPDATE_NOTHING | VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF;
  ASSERT_EQ(
      vpx_codec_encode(&enc, &img_wrapper, 0, 500000, flags, VPX_DL_REALTIME),
      VPX_CODEC_OK);

  // Destroy libvpx encoder
  vpx_codec_destroy(&enc);
}

// Bug: 526737981.
TEST(EncodeAPI, SetReferenceSizeValidationVp8) {
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 128;
  cfg.g_h = 128;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  vpx_image_t raw;
  ASSERT_NE(vpx_img_alloc(&raw, VPX_IMG_FMT_I420, 128, 128, 1), nullptr);
  memset(raw.img_data, 128, raw.stride[0] * raw.d_h * 3 / 2);
  ASSERT_EQ(vpx_codec_encode(&enc, &raw, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Create a larger reference frame.
  vpx_image_t ref_img;
  ASSERT_NE(vpx_img_alloc(&ref_img, VPX_IMG_FMT_I420, 256, 256, 1), nullptr);

  vpx_ref_frame_t ref_frame;
  ref_frame.frame_type = VP8_LAST_FRAME;
  ref_frame.img = ref_img;

  // Setting the reference frame with incorrect size must fail.
  EXPECT_EQ(vpx_codec_control(&enc, VP8_SET_REFERENCE, &ref_frame),
            VPX_CODEC_INVALID_PARAM);

  // Copying reference frame with incorrect size must also fail.
  EXPECT_EQ(vpx_codec_control(&enc, VP8_COPY_REFERENCE, &ref_frame),
            VPX_CODEC_INVALID_PARAM);

  vpx_img_free(&raw);
  vpx_img_free(&ref_img);
  EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// Emulates the WebCodecs VideoEncoder interface.
class VP8Encoder {
 public:
  explicit VP8Encoder(int speed) : speed_(speed) {}
  ~VP8Encoder();

  void Configure(unsigned int threads, unsigned int width, unsigned int height,
                 vpx_rc_mode end_usage, vpx_enc_deadline_t deadline);
  void Encode(bool key_frame);

 private:
  const int speed_;
  bool initialized_ = false;
  vpx_codec_enc_cfg_t cfg_;
  vpx_codec_ctx_t enc_;
  int frame_index_ = 0;
  vpx_enc_deadline_t deadline_ = 0;
};

VP8Encoder::~VP8Encoder() {
  if (initialized_) {
    EXPECT_EQ(vpx_codec_destroy(&enc_), VPX_CODEC_OK);
  }
}

void VP8Encoder::Configure(unsigned int threads, unsigned int width,
                           unsigned int height, vpx_rc_mode end_usage,
                           vpx_enc_deadline_t deadline) {
  deadline_ = deadline;

  if (!initialized_) {
    vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg_, /*usage=*/0),
              VPX_CODEC_OK);
    cfg_.g_threads = threads;
    cfg_.g_w = width;
    cfg_.g_h = height;
    cfg_.g_timebase.num = 1;
    cfg_.g_timebase.den = 1000 * 1000;  // microseconds
    cfg_.g_pass = VPX_RC_ONE_PASS;
    cfg_.g_lag_in_frames = 0;
    cfg_.rc_end_usage = end_usage;
    cfg_.rc_min_quantizer = 2;
    cfg_.rc_max_quantizer = 58;
    ASSERT_EQ(vpx_codec_enc_init(&enc_, iface, &cfg_, 0), VPX_CODEC_OK);
    ASSERT_EQ(vpx_codec_control(&enc_, VP8E_SET_CPUUSED, speed_), VPX_CODEC_OK);
    initialized_ = true;
    return;
  }

  cfg_.g_threads = threads;
  cfg_.g_w = width;
  cfg_.g_h = height;
  cfg_.rc_end_usage = end_usage;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc_, &cfg_), VPX_CODEC_OK)
      << vpx_codec_error_detail(&enc_);
}

void VP8Encoder::Encode(bool key_frame) {
  assert(initialized_);
  const vpx_codec_cx_pkt_t *pkt;
  vpx_image_t *image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg_.g_w, cfg_.g_h);
  ASSERT_NE(image, nullptr);
  const vpx_enc_frame_flags_t flags = key_frame ? VPX_EFLAG_FORCE_KF : 0;
  ASSERT_EQ(vpx_codec_encode(&enc_, image, frame_index_, 1, flags, deadline_),
            VPX_CODEC_OK);
  ++frame_index_;
  vpx_codec_iter_t iter = nullptr;
  while ((pkt = vpx_codec_get_cx_data(&enc_, &iter)) != nullptr) {
    ASSERT_EQ(pkt->kind, VPX_CODEC_CX_FRAME_PKT);
    if (key_frame) {
      ASSERT_EQ(pkt->data.frame.flags & VPX_FRAME_IS_KEY, VPX_FRAME_IS_KEY);
    }
  }
  vpx_img_free(image);
}

// This is the reproducer testcase for crbug.com/324459561. However,
// just running this test is not enough to reproduce the bug. We also
// need to send signals to the test.
TEST(EncodeAPI, Chromium324459561) {
  VP8Encoder encoder(-12);

  encoder.Configure(11, 1685, 652, VPX_CBR, VPX_DL_REALTIME);

  encoder.Encode(true);
  encoder.Encode(true);
  encoder.Encode(true);

  encoder.Configure(0, 1685, 1, VPX_VBR, VPX_DL_REALTIME);
}

TEST(EncodeAPI, VP8GlobalHeaders) {
  constexpr int kWidth = 320;
  constexpr int kHeight = 240;

  vpx_codec_enc_cfg_t cfg = {};
  struct Encoder {
    ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
    vpx_codec_ctx_t ctx = {};
  } enc;

  ASSERT_EQ(vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &cfg, 0),
            VPX_CODEC_OK);
  ASSERT_NO_FATAL_FAILURE(
      InitCodec(*vpx_codec_vp8_cx(), kWidth, kHeight, &enc.ctx, &cfg));
  EXPECT_EQ(vpx_codec_get_global_headers(&enc.ctx), nullptr);
  EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc.ctx));
  EXPECT_EQ(vpx_codec_get_global_headers(&enc.ctx), nullptr);
}

// Encode a few frames for 2 temporal layers realtime mode.
// Set duration to be very large on first frame, much smaller
// on second frames, with the timestamp (pts) parameter very
// inconsistent with the duration (i.e, pts != prev_pts + duration).
// This reproduces the issue found in the bug: 431520320.
TEST(EncodeAPI, Vp8ChromiumIssue431520320) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 320;
  cfg.g_h = 240;
  cfg.g_lag_in_frames = 0;
  cfg.rc_target_bitrate = 500;

  // 2-layers, 2-frame period.
  int ids[2] = { 0, 1 };
  cfg.ts_periodicity = 2;
  cfg.ts_number_layers = 2;
  cfg.ts_rate_decimator[0] = 2;
  cfg.ts_rate_decimator[1] = 1;
  cfg.ts_target_bitrate[0] = 300;
  cfg.ts_target_bitrate[1] = 500;
  memcpy(cfg.ts_layer_id, ids, sizeof(ids));

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  // Encode first frame.
  ASSERT_EQ(
      vpx_codec_encode(&enc, image, 0, /*duration=*/800000, 0, VPX_DL_REALTIME),
      VPX_CODEC_OK);

  // Encode second frame.
  ASSERT_EQ(vpx_codec_encode(&enc, image, 40000, /*duration=*/40000, 0,
                             VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Encode third frame.
  ASSERT_EQ(vpx_codec_encode(&enc, image, 80000, /*duration=*/40000, 0,
                             VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, AomediaIssue3509VbrMinSection2PercentVP8) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 1920;
  cfg.g_h = 1080;
  cfg.g_lag_in_frames = 0;
  cfg.rc_target_bitrate = 1000000;
  // Set this to more than 1 percent to cause a signed integer overflow in the
  // multiplication cpi->av_per_frame_bandwidth *
  // cpi->oxcf.two_pass_vbrmin_section in vp8_new_framerate() if the
  // multiplication is done in the `int` type.
  cfg.rc_2pass_vbr_minsection_pct = 2;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  // Encode frame.
  // `duration` can go as high as 300, but the UBSan error is gone if
  // `duration` is 301 or higher.
  ASSERT_EQ(
      vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME),
      VPX_CODEC_OK);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, AomediaIssue3509VbrMinSection101PercentVP8) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 1920;
  cfg.g_h = 1080;
  cfg.g_lag_in_frames = 0;
  cfg.rc_target_bitrate = 1000000;
  // Set this to more than 100 percent to cause an error when vbr_min_bits is
  // cast to `int` in vp8_new_framerate() if vbr_min_bits is not clamped to
  // INT_MAX.
  cfg.rc_2pass_vbr_minsection_pct = 101;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  // Encode frame.
  // `duration` can go as high as 300, but the UBSan error is gone if
  // `duration` is 301 or higher.
  ASSERT_EQ(
      vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME),
      VPX_CODEC_OK);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// VP8 realtime mode does not support lag in frames.
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, OssFuzz69100) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 64;
  cfg.g_h = 64;
  cfg.g_lag_in_frames = 25;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 6240592;
  cfg.rc_target_bitrate = 1202607620;
  cfg.kf_max_dist = 24377;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 1), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_ARNR_MAXFRAMES, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_ARNR_STRENGTH, 3), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control_(&enc, VP8E_SET_ARNR_TYPE, 3),
            VPX_CODEC_OK);  // deprecated
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_NOISE_SENSITIVITY, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_TOKEN_PARTITIONS, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_STATIC_THRESHOLD, 0),
            VPX_CODEC_OK);

  libvpx_test::RandomVideoSource video;
  video.set_limit(30);
  video.SetSize(cfg.g_w, cfg.g_h);
  video.SetImageFormat(VPX_IMG_FMT_I420);
  video.Begin();
  do {
    ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), video.duration(),
                               /*flags=*/0, VPX_DL_GOOD_QUALITY),
              VPX_CODEC_OK);
    video.Next();
  } while (video.img() != nullptr);

  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}
#endif  // !CONFIG_REALTIME_ONLY

void EncodeOssFuzz69906(int cpu_used, vpx_enc_deadline_t deadline) {
  char str[80];
  snprintf(str, sizeof(str), "cpu_used: %d deadline: %d", cpu_used,
           static_cast<int>(deadline));
  SCOPED_TRACE(str);

  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 4097;
  cfg.g_h = 16;
  cfg.rc_target_bitrate = 1237084865;
  cfg.kf_max_dist = 4336;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, cpu_used), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_ARNR_MAXFRAMES, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_ARNR_STRENGTH, 3), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control_(&enc, VP8E_SET_ARNR_TYPE, 3),
            VPX_CODEC_OK);  // deprecated
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_NOISE_SENSITIVITY, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_TOKEN_PARTITIONS, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_STATIC_THRESHOLD, 0),
            VPX_CODEC_OK);

  libvpx_test::Y4mVideoSource video("repro-oss-fuzz-69906.y4m", /*start=*/0,
                                    /*limit=*/3);
  video.Begin();
  do {
    ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), video.duration(),
                               /*flags=*/0, deadline),
              VPX_CODEC_OK);
    video.Next();
  } while (video.img() != nullptr);

  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, OssFuzz69906) {
  // Note the original bug report was for speed 1, good quality. The remainder
  // of the settings are for added coverage.
  for (int cpu_used = 0; cpu_used <= 5; ++cpu_used) {
    EncodeOssFuzz69906(cpu_used, VPX_DL_GOOD_QUALITY);
  }

  for (int cpu_used = -16; cpu_used <= -5; ++cpu_used) {
    EncodeOssFuzz69906(cpu_used, VPX_DL_REALTIME);
  }
}

TEST(EncodeAPI, OssFuzz471723682) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 253;
  cfg.g_h = 252;
  cfg.rc_target_bitrate = 2611345883;
  cfg.kf_max_dist = 16515082;
  cfg.g_timebase.num = 89;
  cfg.g_timebase.den = 655613;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.rc_dropframe_thresh = 0;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 1), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_ARNR_MAXFRAMES, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_ARNR_STRENGTH, 3), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control_(&enc, VP8E_SET_ARNR_TYPE, 3),
            VPX_CODEC_OK);  // deprecated
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_NOISE_SENSITIVITY, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_TOKEN_PARTITIONS, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_STATIC_THRESHOLD, 0),
            VPX_CODEC_OK);

  libvpx_test::YUVVideoSource video(
      "repro-oss-fuzz-471723682.yuv", VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h,
      /*rate_numerator=*/786684, /*rate_denominator=*/4980772,
      /*start=*/0,
      /*limit=*/8);
  video.Begin();
  do {
    ASSERT_EQ(vpx_codec_encode(&enc, video.img(), /*pts=*/0, /*duration=*/46640,
                               /*flags=*/0, VPX_DL_GOOD_QUALITY),
              VPX_CODEC_OK);
    video.Next();
  } while (video.img() != nullptr);

  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, Vp8TotalrateOverflow) {
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 1156;
  cfg.g_h = 700;
  cfg.g_threads = 8;
  cfg.g_pass = VPX_RC_ONE_PASS;

  vpx_codec_ctx_t codec;
  ASSERT_EQ(vpx_codec_enc_init(&codec, iface, &cfg, 0), VPX_CODEC_OK);

  libvpx_test::ACMRandom rng;
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, &rng);
  ASSERT_NE(image, nullptr);

  EXPECT_EQ(vpx_codec_encode(&codec, image, 0, 1, 0, VPX_DL_GOOD_QUALITY),
            VPX_CODEC_OK);

  vpx_img_free(image);
  EXPECT_EQ(vpx_codec_destroy(&codec), VPX_CODEC_OK);
}

// Bug: 526739171.
TEST(EncodeAPI, Vp8InvalidTemporalLayerId) {
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 64;
  cfg.g_h = 64;

  cfg.ts_number_layers = 2;
  cfg.ts_periodicity = 2;
  cfg.ts_target_bitrate[0] = 500;
  cfg.ts_target_bitrate[1] = 1000;
  cfg.ts_rate_decimator[0] = 2;
  cfg.ts_rate_decimator[1] = 1;

  cfg.ts_layer_id[0] = 0;
  cfg.ts_layer_id[1] = 2;  // Invalid, must be < ts_number_layers (2)

  EXPECT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_INVALID_PARAM);
}

// Bug: 526739171.
TEST(EncodeAPI, Vp8InvalidTemporalLayerPeriodicity) {
  vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 64;
  cfg.g_h = 64;

  cfg.ts_number_layers = 2;
  cfg.ts_periodicity = 0;  // Invalid, must be >= 1

  EXPECT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_INVALID_PARAM);
}
#endif  // CONFIG_VP8_ENCODER

// Set up 2 spatial streams with 2 temporal layers per stream, and generate
// invalid configuration by setting the temporal layer rate allocation
// (ts_target_bitrate[]) to 0 for both layers. This should fail independent of
// CONFIG_MULTI_RES_ENCODING.
TEST(EncodeAPI, MultiResEncode) {
  const int width = 1280;
  const int height = 720;
  const int width_down = width / 2;
  const int height_down = height / 2;
  const int target_bitrate = 1000;
  const int framerate = 30;

  for (const auto *iface : kCodecIfaces) {
    vpx_codec_ctx_t enc[2];
    vpx_codec_enc_cfg_t cfg[2];
    vpx_rational_t dsf[2] = { { 2, 1 }, { 2, 1 } };

    memset(enc, 0, sizeof(enc));

    for (int i = 0; i < 2; i++) {
      vpx_codec_enc_config_default(iface, &cfg[i], 0);
    }

    /* Highest-resolution encoder settings */
    cfg[0].g_w = width;
    cfg[0].g_h = height;
    cfg[0].rc_dropframe_thresh = 0;
    cfg[0].rc_end_usage = VPX_CBR;
    cfg[0].rc_resize_allowed = 0;
    cfg[0].rc_min_quantizer = 2;
    cfg[0].rc_max_quantizer = 56;
    cfg[0].rc_undershoot_pct = 100;
    cfg[0].rc_overshoot_pct = 15;
    cfg[0].rc_buf_initial_sz = 500;
    cfg[0].rc_buf_optimal_sz = 600;
    cfg[0].rc_buf_sz = 1000;
    cfg[0].g_error_resilient = 1; /* Enable error resilient mode */
    cfg[0].g_lag_in_frames = 0;

    cfg[0].kf_mode = VPX_KF_AUTO;
    cfg[0].kf_min_dist = 3000;
    cfg[0].kf_max_dist = 3000;

    cfg[0].rc_target_bitrate = target_bitrate; /* Set target bitrate */
    cfg[0].g_timebase.num = 1;                 /* Set fps */
    cfg[0].g_timebase.den = framerate;

    cfg[1] = cfg[0];
    cfg[1].rc_target_bitrate = 500;
    cfg[1].g_w = width_down;
    cfg[1].g_h = height_down;

    for (int i = 0; i < 2; i++) {
      cfg[i].ts_number_layers = 2;
      cfg[i].ts_periodicity = 2;
      cfg[i].ts_rate_decimator[0] = 2;
      cfg[i].ts_rate_decimator[1] = 1;
      cfg[i].ts_layer_id[0] = 0;
      cfg[i].ts_layer_id[1] = 1;
      // Invalid parameters.
      cfg[i].ts_target_bitrate[0] = 0;
      cfg[i].ts_target_bitrate[1] = 0;
    }

    // VP9 should report incapable, VP8 invalid for all configurations.
    EXPECT_EQ(IsVP9(iface) ? VPX_CODEC_INCAPABLE : VPX_CODEC_INVALID_PARAM,
              vpx_codec_enc_init_multi(&enc[0], iface, &cfg[0], 2, 0, &dsf[0]));

    for (int i = 0; i < 2; i++) {
      vpx_codec_destroy(&enc[i]);
    }
  }
}

#if CONFIG_VP8_ENCODER && CONFIG_MULTI_RES_ENCODING
// Regression test for an off-by-one in vpx_codec_encode()'s multi-resolution
// loop: when the *first* iteration (highest-index encoder, smallest stream)
// returns non-OK, `break` skips `ctx--`, the post-loop `ctx++` advances ctx to
// one past the caller's array, and SAVE_STATUS(ctx, res) writes ctx->err out of
// bounds. With a heap-allocated 3-element array (matching WebRTC's
// std::vector<vpx_codec_ctx_t> encoders_), ASan reports a 4-byte
// heap-buffer-overflow WRITE at +16 past a 168-byte region.
// Bug: 513405023.
TEST(EncodeAPI, MultiResEncodeFirstIterFailOOB) {
  constexpr int kNumEnc = 3;
  // Heap-allocate exactly kNumEnc contexts, mirroring WebRTC's
  // encoders_.reserve(3); encoders_.resize(3);
  std::vector<vpx_codec_ctx_t> enc;
  enc.reserve(kNumEnc);
  enc.resize(kNumEnc);
  memset(enc.data(), 0, sizeof(vpx_codec_ctx_t) * kNumEnc);

  vpx_codec_enc_cfg_t cfg[kNumEnc];
  vpx_rational_t dsf[kNumEnc] = { { 2, 1 }, { 2, 1 }, { 1, 1 } };
  const int w[kNumEnc] = { 320, 160, 80 };
  const int h[kNumEnc] = { 240, 120, 60 };

  for (int i = 0; i < kNumEnc; ++i) {
    ASSERT_EQ(vpx_codec_enc_config_default(&vpx_codec_vp8_cx_algo, &cfg[i], 0),
              VPX_CODEC_OK);
    cfg[i].g_w = w[i];
    cfg[i].g_h = h[i];
    cfg[i].g_lag_in_frames = 0;
    cfg[i].rc_end_usage = VPX_CBR;
    cfg[i].rc_resize_allowed = 0;
    cfg[i].rc_target_bitrate = 300 >> i;
    cfg[i].g_timebase.num = 1;
    cfg[i].g_timebase.den = 30;
  }

  ASSERT_EQ(vpx_codec_enc_init_multi(enc.data(), &vpx_codec_vp8_cx_algo, cfg,
                                     kNumEnc, 0, dsf),
            VPX_CODEC_OK);

  // Contiguous image array; vpx_codec_encode walks img += num_enc-1.
  vpx_image_t imgs[kNumEnc];
  for (int i = 0; i < kNumEnc; ++i) {
    ASSERT_NE(vpx_img_alloc(&imgs[i], VPX_IMG_FMT_I420, w[i], h[i], 1),
              nullptr);
    memset(imgs[i].img_data, 128, imgs[i].stride[0] * h[i] * 3 / 2);
  }

  // Force the FIRST loop iteration (i = num_enc-1, ctx = &enc[2]) to fail in
  // validate_img() before any ctx-- runs: set an unsupported format on the
  // highest-index image. validate_img returns VPX_CODEC_INVALID_PARAM, the
  // loop breaks, ctx++ advances to &enc[3], and SAVE_STATUS writes
  // (&enc[3])->err — a 4-byte heap OOB write at offset 16 past the vector's
  // 168-byte backing store.
  imgs[kNumEnc - 1].fmt = VPX_IMG_FMT_I444;

  // Under ASan this line reports:
  //   heap-buffer-overflow WRITE of size 4 ... in vpx_codec_encode
  //   0 bytes to the right of (or 16 past) 168-byte region.
  EXPECT_EQ(vpx_codec_encode(enc.data(), imgs, /*pts=*/0, /*duration=*/1,
                             /*flags=*/0, VPX_DL_REALTIME),
            VPX_CODEC_INVALID_PARAM);

  for (int i = 0; i < kNumEnc; ++i) vpx_img_free(&imgs[i]);
  for (int i = kNumEnc - 1; i >= 0; --i) vpx_codec_destroy(&enc[i]);
}
#endif  // CONFIG_VP8_ENCODER && CONFIG_MULTI_RES_ENCODING

TEST(EncodeAPI, SetRoi) {
  static struct {
    vpx_codec_iface_t *iface;
    int ctrl_id;
  } kCodecs[] = {
#if CONFIG_VP8_ENCODER
    { &vpx_codec_vp8_cx_algo, VP8E_SET_ROI_MAP },
#endif
#if CONFIG_VP9_ENCODER
    { &vpx_codec_vp9_cx_algo, VP9E_SET_ROI_MAP },
#endif
  };
  constexpr int kWidth = 64;
  constexpr int kHeight = 64;

  for (const auto &codec : kCodecs) {
    SCOPED_TRACE(vpx_codec_iface_name(codec.iface));
    vpx_codec_ctx_t enc;
    vpx_codec_enc_cfg_t cfg;

    EXPECT_EQ(vpx_codec_enc_config_default(codec.iface, &cfg, 0), VPX_CODEC_OK);
    cfg.g_w = kWidth;
    cfg.g_h = kHeight;
    EXPECT_EQ(vpx_codec_enc_init(&enc, codec.iface, &cfg, 0), VPX_CODEC_OK);

    vpx_roi_map_t roi = {};
    uint8_t roi_map[kWidth * kHeight] = {};
    if (IsVP9(codec.iface)) {
      roi.rows = (cfg.g_w + 7) >> 3;
      roi.cols = (cfg.g_h + 7) >> 3;
    } else {
      roi.rows = (cfg.g_w + 15) >> 4;
      roi.cols = (cfg.g_h + 15) >> 4;
    }
    EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), VPX_CODEC_OK);

    roi.roi_map = roi_map;
    // VP8 only. This value isn't range checked.
    roi.static_threshold[1] = 1000;
    roi.static_threshold[2] = UINT_MAX / 2 + 1;
    roi.static_threshold[3] = UINT_MAX;

    for (const auto delta : { -63, -1, 0, 1, 63 }) {
      for (int i = 0; i < 8; ++i) {
        roi.delta_q[i] = delta;
        roi.delta_lf[i] = delta;
        // VP9 only.
        roi.skip[i] ^= 1;
        roi.ref_frame[i] = (roi.ref_frame[i] + 1) % 4;
        EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), VPX_CODEC_OK);
      }
    }

    vpx_codec_err_t expected_error;
    for (const auto delta : { -64, 64, INT_MIN, INT_MAX }) {
      expected_error = VPX_CODEC_INVALID_PARAM;
      for (int i = 0; i < 8; ++i) {
        roi.delta_q[i] = delta;
        // The max segment count for VP8 is 4, the remainder of the entries are
        // ignored.
        if (i >= 4 && !IsVP9(codec.iface)) expected_error = VPX_CODEC_OK;

        EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error)
            << "delta_q[" << i << "]: " << delta;
        roi.delta_q[i] = 0;

        roi.delta_lf[i] = delta;
        EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error)
            << "delta_lf[" << i << "]: " << delta;
        roi.delta_lf[i] = 0;
      }
    }

    // VP8 should ignore skip[] and ref_frame[] values.
    expected_error =
        IsVP9(codec.iface) ? VPX_CODEC_INVALID_PARAM : VPX_CODEC_OK;
    for (const auto skip : { -2, 2, INT_MIN, INT_MAX }) {
      for (int i = 0; i < 8; ++i) {
        roi.skip[i] = skip;
        EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error)
            << "skip[" << i << "]: " << skip;
        roi.skip[i] = 0;
      }
    }

    // VP9 allows negative values to be used to disable segmentation.
    for (int ref_frame = -3; ref_frame < 0; ++ref_frame) {
      for (int i = 0; i < 8; ++i) {
        roi.ref_frame[i] = ref_frame;
        EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), VPX_CODEC_OK)
            << "ref_frame[" << i << "]: " << ref_frame;
        roi.ref_frame[i] = 0;
      }
    }

    for (const auto ref_frame : { 4, INT_MIN, INT_MAX }) {
      for (int i = 0; i < 8; ++i) {
        roi.ref_frame[i] = ref_frame;
        EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error)
            << "ref_frame[" << i << "]: " << ref_frame;
        roi.ref_frame[i] = 0;
      }
    }

    EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}

TEST(EncodeAPI, ConfigChangeThreadCount) {
  constexpr int kWidth = 1920;
  constexpr int kHeight = 1080;

  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    for (int i = 0; i < (IsVP9(iface) ? 2 : 1); ++i) {
      vpx_codec_enc_cfg_t cfg = {};
      struct Encoder {
        ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
        vpx_codec_ctx_t ctx = {};
      } enc;

      ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
      EXPECT_NO_FATAL_FAILURE(
          InitCodec(*iface, kWidth, kHeight, &enc.ctx, &cfg));
      if (IsVP9(iface)) {
        EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_TILE_COLUMNS, 6),
                  VPX_CODEC_OK);
        EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_ROW_MT, i),
                  VPX_CODEC_OK);
      }

      for (const auto threads : { 1, 4, 8, 6, 2, 1 }) {
        cfg.g_threads = threads;
        EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc.ctx))
            << "iteration: " << i << " threads: " << threads;
      }
    }
  }
}

TEST(EncodeAPI, ConfigResizeChangeThreadCount) {
  constexpr int kInitWidth = 1024;
  constexpr int kInitHeight = 1024;

  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    for (int i = 0; i < (IsVP9(iface) ? 2 : 1); ++i) {
      vpx_codec_enc_cfg_t cfg = {};
      struct Encoder {
        ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
        vpx_codec_ctx_t ctx = {};
      } enc;

      ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
      // Start in threaded mode to ensure resolution and thread related
      // allocations are updated correctly across changes in resolution and
      // thread counts. See https://crbug.com/1486441.
      cfg.g_threads = 4;
      EXPECT_NO_FATAL_FAILURE(
          InitCodec(*iface, kInitWidth, kInitHeight, &enc.ctx, &cfg));
      if (IsVP9(iface)) {
        EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_TILE_COLUMNS, 6),
                  VPX_CODEC_OK);
        EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_ROW_MT, i),
                  VPX_CODEC_OK);
      }

      cfg.g_w = 1000;
      cfg.g_h = 608;
      EXPECT_EQ(vpx_codec_enc_config_set(&enc.ctx, &cfg), VPX_CODEC_OK)
          << vpx_codec_error_detail(&enc.ctx);

      cfg.g_w = 1000;
      cfg.g_h = 720;

      for (const auto threads : { 1, 4, 8, 6, 2, 1 }) {
        cfg.g_threads = threads;
        EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc.ctx))
            << "iteration: " << i << " threads: " << threads;
      }
    }
  }
}

TEST(EncodeAPI, ConfigResizeBiggerAfterInit) {
  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    vpx_codec_enc_cfg_t cfg;
    vpx_codec_ctx_t enc;

    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
    EXPECT_NO_FATAL_FAILURE(InitCodec(*iface, 1, 1, &enc, &cfg));

    cfg.g_w = 1920;
    cfg.g_h = 1;
    EXPECT_EQ(vpx_codec_enc_config_set(&enc, &cfg),
              IsVP9(iface) ? VPX_CODEC_OK : VPX_CODEC_INVALID_PARAM);

    EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}

TEST(EncodeAPI, ConfigResizeBiggerAfterEncode) {
  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    vpx_codec_enc_cfg_t cfg;
    vpx_codec_ctx_t enc;

    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
    EXPECT_NO_FATAL_FAILURE(InitCodec(*iface, 1, 1, &enc, &cfg));
    EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc));

    cfg.g_w = 1920;
    cfg.g_h = 1;
    EXPECT_EQ(vpx_codec_enc_config_set(&enc, &cfg),
              IsVP9(iface) ? VPX_CODEC_OK : VPX_CODEC_INVALID_PARAM);

    cfg.g_w = 1920;
    cfg.g_h = 1080;
    EXPECT_EQ(vpx_codec_enc_config_set(&enc, &cfg),
              IsVP9(iface) ? VPX_CODEC_OK : VPX_CODEC_INVALID_PARAM);

    EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}

TEST(EncodeAPI, PtsSmallerThanInitialPts) {
  for (const auto *iface : kCodecIfaces) {
    // Initialize libvpx encoder.
    vpx_codec_ctx_t enc;
    vpx_codec_enc_cfg_t cfg;

    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

    ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

    // Create input image.
    vpx_image_t *const image =
        CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
    ASSERT_NE(image, nullptr);

    // Encode frame.
    ASSERT_EQ(vpx_codec_encode(&enc, image, 12, 1, 0, VPX_DL_BEST_QUALITY),
              VPX_CODEC_OK);
    ASSERT_EQ(vpx_codec_encode(&enc, image, 13, 1, 0, VPX_DL_BEST_QUALITY),
              VPX_CODEC_OK);
    // pts (10) is smaller than the initial pts (12).
    ASSERT_EQ(vpx_codec_encode(&enc, image, 10, 1, 0, VPX_DL_BEST_QUALITY),
              VPX_CODEC_INVALID_PARAM);

    // Free resources.
    vpx_img_free(image);
    ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}

TEST(EncodeAPI, PtsOrDurationTooBig) {
  for (const auto *iface : kCodecIfaces) {
    // Initialize libvpx encoder.
    vpx_codec_ctx_t enc;
    vpx_codec_enc_cfg_t cfg;

    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

    ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

    // Create input image.
    vpx_image_t *const image =
        CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
    ASSERT_NE(image, nullptr);

    // Encode frame.
    ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_BEST_QUALITY),
              VPX_CODEC_OK);
#if ULONG_MAX > INT64_MAX
    // duration is too big.
    ASSERT_EQ(vpx_codec_encode(&enc, image, 0, (1ul << 63), 0, 2),
              VPX_CODEC_INVALID_PARAM);
#endif
    // pts, when converted to ticks, is too big.
    ASSERT_EQ(vpx_codec_encode(&enc, image, INT64_MAX / 1000000 + 1, 1, 0,
                               VPX_DL_BEST_QUALITY),
              VPX_CODEC_INVALID_PARAM);
#if ULONG_MAX > INT64_MAX
    // duration is too big.
    ASSERT_EQ(
        vpx_codec_encode(&enc, image, 0, (1ul << 63), 0, VPX_DL_BEST_QUALITY),
        VPX_CODEC_INVALID_PARAM);
    // pts + duration is too big.
    ASSERT_EQ(
        vpx_codec_encode(&enc, image, 1, INT64_MAX, 0, VPX_DL_BEST_QUALITY),
        VPX_CODEC_INVALID_PARAM);
#endif
    // pts + duration, when converted to ticks, is too big.
#if ULONG_MAX > INT64_MAX
    ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 0xbd6b566b15c7, 0,
                               VPX_DL_BEST_QUALITY),
              VPX_CODEC_INVALID_PARAM);
#endif
    ASSERT_EQ(vpx_codec_encode(&enc, image, INT64_MAX / 1000000, 1, 0,
                               VPX_DL_BEST_QUALITY),
              VPX_CODEC_INVALID_PARAM);

    // Free resources.
    vpx_img_free(image);
    ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}

TEST(EncodeAPI, PerFramePsnr) {
  for (const auto *iface : kCodecIfaces) {
    SCOPED_TRACE(vpx_codec_iface_name(iface));
    vpx_codec_enc_cfg_t cfg;
    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
    cfg.g_lag_in_frames = 0;

    vpx_codec_ctx_t enc;
    ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

    vpx_image_t *const image =
        CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
    ASSERT_NE(image, nullptr);

    vpx_enc_frame_flags_t psnr_flags = VPX_EFLAG_CALCULATE_PSNR;
    ASSERT_EQ(vpx_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1,
                               psnr_flags, VPX_DL_REALTIME),
              VPX_CODEC_OK);

    const vpx_codec_cx_pkt_t *pkt;
    vpx_codec_iter_t iter = nullptr;
    bool had_psnr = false;
    while ((pkt = vpx_codec_get_cx_data(&enc, &iter)) != nullptr) {
      if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
        ASSERT_EQ(pkt->kind, VPX_CODEC_PSNR_PKT);
        had_psnr = true;
      }
    }
    EXPECT_TRUE(had_psnr);

    vpx_enc_frame_flags_t no_psnr_flags = 0;
    ASSERT_EQ(vpx_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1,
                               no_psnr_flags, VPX_DL_REALTIME),
              VPX_CODEC_OK);

    iter = nullptr;
    had_psnr = false;
    while ((pkt = vpx_codec_get_cx_data(&enc, &iter)) != nullptr) {
      if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
        ASSERT_EQ(pkt->kind, VPX_CODEC_PSNR_PKT);
        had_psnr = true;
      }
    }
#if CONFIG_INTERNAL_STATS
    // CONFIG_INTERNAL_STATS unconditionally generates PSNR.
    EXPECT_TRUE(had_psnr);
#else
    EXPECT_FALSE(had_psnr);
#endif  // CONFIG_INTERNAL_STATS

    // Free resources.
    vpx_img_free(image);
    ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}

#if CONFIG_VP9_ENCODER
// Frame size needed to trigger the overflow exceeds the max buffer allowed on
// 32-bit systems defined by VPX_MAX_ALLOCABLE_MEMORY
#if VPX_ARCH_X86_64 || VPX_ARCH_AARCH64
TEST(EncodeAPI, ConfigLargeTargetBitrateVp9) {
#ifdef CHROMIUM
  GTEST_SKIP() << "Under Chromium's configuration the allocator is unable"
                  "to provide the space required for the frames below.";
#else
  constexpr int kWidth = 12383;
  constexpr int kHeight = 8192;
  constexpr auto *iface = &vpx_codec_vp9_cx_algo;
  SCOPED_TRACE(vpx_codec_iface_name(iface));
  vpx_codec_enc_cfg_t cfg = {};
  struct Encoder {
    ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
    vpx_codec_ctx_t ctx = {};
  } enc;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  // The following setting will cause avg_frame_bandwidth in rate control to be
  // larger than INT_MAX
  cfg.rc_target_bitrate = INT_MAX;
  // Framerate 0.1 (equivalent to timebase 10) is the smallest framerate allowed
  // by libvpx
  cfg.g_timebase.den = 1;
  cfg.g_timebase.num = 10;
  EXPECT_NO_FATAL_FAILURE(InitCodec(*iface, kWidth, kHeight, &enc.ctx, &cfg))
      << "target bitrate: " << cfg.rc_target_bitrate << " framerate: "
      << static_cast<double>(cfg.g_timebase.den) / cfg.g_timebase.num;
#endif  // defined(CHROMIUM)
}
#endif  // VPX_ARCH_X86_64 || VPX_ARCH_AARCH64

// Emulates the WebCodecs VideoEncoder interface.
class VP9Encoder {
 public:
  explicit VP9Encoder(int speed)
      : speed_(speed), row_mt_(0), bit_depth_(VPX_BITS_8),
        fmt_(VPX_IMG_FMT_I420) {}
  // The image format `fmt` must not have the VPX_IMG_FMT_HIGHBITDEPTH bit set.
  // If bit_depth > 8, we will set the VPX_IMG_FMT_HIGHBITDEPTH bit before
  // passing the image format to vpx_img_alloc().
  VP9Encoder(int speed, unsigned int row_mt, vpx_bit_depth_t bit_depth,
             vpx_img_fmt_t fmt)
      : speed_(speed), row_mt_(row_mt), bit_depth_(bit_depth), fmt_(fmt) {}
  ~VP9Encoder();

  void Configure(unsigned int threads, unsigned int width, unsigned int height,
                 vpx_rc_mode end_usage, vpx_enc_deadline_t deadline);
  // If `rng` is non-null, use it to generate the image values for encoding.
  // Otherwise the midpoint of the configured bitdepth is used.
  void Encode(bool key_frame, libvpx_test::ACMRandom *rng = nullptr);

 private:
  const int speed_;
  const unsigned int row_mt_;
  const vpx_bit_depth_t bit_depth_;
  const vpx_img_fmt_t fmt_;
  bool initialized_ = false;
  vpx_codec_enc_cfg_t cfg_;
  vpx_codec_ctx_t enc_;
  int frame_index_ = 0;
  vpx_enc_deadline_t deadline_ = 0;
};

VP9Encoder::~VP9Encoder() {
  if (initialized_) {
    EXPECT_EQ(vpx_codec_destroy(&enc_), VPX_CODEC_OK);
  }
}

void VP9Encoder::Configure(unsigned int threads, unsigned int width,
                           unsigned int height, vpx_rc_mode end_usage,
                           vpx_enc_deadline_t deadline) {
  deadline_ = deadline;

  if (!initialized_) {
    ASSERT_EQ(fmt_ & VPX_IMG_FMT_HIGHBITDEPTH, 0);
    const bool high_bit_depth = bit_depth_ > VPX_BITS_8;
    const bool is_420 = fmt_ == VPX_IMG_FMT_I420;
    vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
    ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg_, /*usage=*/0),
              VPX_CODEC_OK);
    cfg_.g_threads = threads;
    // In profiles 0 and 2, only 4:2:0 format is allowed. In profiles 1 and 3,
    // all other subsampling formats are allowed. In profiles 0 and 1, only bit
    // depth 8 is allowed. In profiles 2 and 3, only bit depths 10 and 12 are
    // allowed.
    cfg_.g_profile = 2 * high_bit_depth + !is_420;
    cfg_.g_w = width;
    cfg_.g_h = height;
    cfg_.g_bit_depth = bit_depth_;
    cfg_.g_input_bit_depth = bit_depth_;
    cfg_.g_timebase.num = 1;
    cfg_.g_timebase.den = 1000 * 1000;  // microseconds
    cfg_.g_pass = VPX_RC_ONE_PASS;
    cfg_.g_lag_in_frames = 0;
    cfg_.rc_end_usage = end_usage;
    cfg_.rc_min_quantizer = 2;
    cfg_.rc_max_quantizer = 58;
    ASSERT_EQ(
        vpx_codec_enc_init(&enc_, iface, &cfg_,
                           high_bit_depth ? VPX_CODEC_USE_HIGHBITDEPTH : 0),
        VPX_CODEC_OK);
    ASSERT_EQ(vpx_codec_control(&enc_, VP8E_SET_CPUUSED, speed_), VPX_CODEC_OK);
    ASSERT_EQ(vpx_codec_control(&enc_, VP9E_SET_ROW_MT, row_mt_), VPX_CODEC_OK);
    initialized_ = true;
    return;
  }

  cfg_.g_threads = threads;
  cfg_.g_w = width;
  cfg_.g_h = height;
  cfg_.rc_end_usage = end_usage;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc_, &cfg_), VPX_CODEC_OK)
      << vpx_codec_error_detail(&enc_);
}

void VP9Encoder::Encode(bool key_frame, libvpx_test::ACMRandom *rng) {
  assert(initialized_);
  const vpx_codec_cx_pkt_t *pkt;
  vpx_image_t *image = CreateImage(bit_depth_, fmt_, cfg_.g_w, cfg_.g_h, rng);
  ASSERT_NE(image, nullptr);
  const vpx_enc_frame_flags_t frame_flags = key_frame ? VPX_EFLAG_FORCE_KF : 0;
  ASSERT_EQ(
      vpx_codec_encode(&enc_, image, frame_index_, 1, frame_flags, deadline_),
      VPX_CODEC_OK);
  ++frame_index_;
  vpx_codec_iter_t iter = nullptr;
  while ((pkt = vpx_codec_get_cx_data(&enc_, &iter)) != nullptr) {
    ASSERT_EQ(pkt->kind, VPX_CODEC_CX_FRAME_PKT);
  }
  vpx_img_free(image);
}

// This is a test case from clusterfuzz.
TEST(EncodeAPI, PrevMiCheckNullptr) {
  VP9Encoder encoder(0);
  encoder.Configure(0, 1554, 644, VPX_VBR, VPX_DL_REALTIME);

  // First step: encode, without forcing KF.
  encoder.Encode(false);
  // Second step: change config
  encoder.Configure(0, 1131, 644, VPX_CBR, VPX_DL_GOOD_QUALITY);
  // Third step: encode, without forcing KF
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/310477034.
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, MultipleChangeConfigResize) {
  VP9Encoder encoder(3);

  // Set initial config.
  encoder.Configure(3, 41, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode first frame.
  encoder.Encode(true);

  // Change config.
  encoder.Configure(16, 31, 1, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Change config again.
  encoder.Configure(0, 17, 1, VPX_CBR, VPX_DL_REALTIME);

  // Encode 2nd frame with new config, set delta frame.
  encoder.Encode(false);

  // Encode 3rd frame with same config, set delta frame.
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/310663186.
// Encode set of frames while varying the deadline on the fly from
// good to realtime to best and back to realtime.
TEST(EncodeAPI, DynamicDeadlineChange) {
  // Use realtime speed: 5 to 9.
  VP9Encoder encoder(5);

  // Set initial config, in particular set deadline to GOOD mode.
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 1st frame.
  encoder.Encode(true);

  // Encode 2nd frame, delta frame.
  encoder.Encode(false);

  // Change config: change deadline to REALTIME.
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode 3rd frame with new config, set key frame.
  encoder.Encode(true);

  // Encode 4th frame with same config, delta frame.
  encoder.Encode(false);

  // Encode 5th frame with same config, key frame.
  encoder.Encode(true);

  // Change config: change deadline to BEST.
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_BEST_QUALITY);

  // Encode 6th frame with new config, set delta frame.
  encoder.Encode(false);

  // Change config: change deadline to REALTIME.
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode 7th frame with new config, set delta frame.
  encoder.Encode(false);

  // Encode 8th frame with new config, set key frame.
  encoder.Encode(true);

  // Encode 9th frame with new config, set delta frame.
  encoder.Encode(false);
}

TEST(EncodeAPI, Buganizer310340241) {
  VP9Encoder encoder(-6);

  // Set initial config, in particular set deadline to GOOD mode.
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 1st frame.
  encoder.Encode(true);

  // Encode 2nd frame, delta frame.
  encoder.Encode(false);

  // Change config: change deadline to REALTIME.
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode 3rd frame with new config, set key frame.
  encoder.Encode(true);
}

// This is a test case from clusterfuzz: based on b/312517065.
TEST(EncodeAPI, Buganizer312517065) {
  VP9Encoder encoder(4);
  encoder.Configure(0, 1060, 437, VPX_CBR, VPX_DL_REALTIME);
  encoder.Encode(true);
  encoder.Configure(10, 33, 437, VPX_VBR, VPX_DL_GOOD_QUALITY);
  encoder.Encode(false);
  encoder.Configure(6, 327, 269, VPX_VBR, VPX_DL_GOOD_QUALITY);
  encoder.Configure(15, 1060, 437, VPX_CBR, VPX_DL_REALTIME);
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/311489136.
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, Buganizer311489136) {
  VP9Encoder encoder(1);

  // Set initial config.
  encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode first frame.
  encoder.Encode(true);

  // Change config.
  encoder.Configure(3, 1678, 202, VPX_CBR, VPX_DL_GOOD_QUALITY);

  // Encode 2nd frame with new config, set delta frame.
  encoder.Encode(false);

  // Change config again.
  encoder.Configure(8, 1037, 476, VPX_CBR, VPX_DL_REALTIME);

  // Encode 3rd frame with new config, set delta frame.
  encoder.Encode(false);

  // Change config again.
  encoder.Configure(0, 580, 620, VPX_CBR, VPX_DL_GOOD_QUALITY);

  // Encode 4th frame with same config, set delta frame.
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/312656387.
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, Buganizer312656387) {
  VP9Encoder encoder(1);

  // Set initial config.
  encoder.Configure(16, 1, 1024, VPX_CBR, VPX_DL_REALTIME);

  // Change config.
  encoder.Configure(15, 1, 1024, VPX_VBR, VPX_DL_REALTIME);

  // Encode first frame.
  encoder.Encode(true);

  // Change config again.
  encoder.Configure(14, 1, 595, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 2nd frame with new config.
  encoder.Encode(true);

  // Change config again.
  encoder.Configure(2, 1, 1024, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 3rd frame with new config, set delta frame.
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/310329177.
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, Buganizer310329177) {
  VP9Encoder encoder(6);

  // Set initial config.
  encoder.Configure(10, 41, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode first frame.
  encoder.Encode(true);

  // Change config.
  encoder.Configure(16, 1, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode 2nd frame with new config, set delta frame.
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/311394513.
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, Buganizer311394513) {
  VP9Encoder encoder(-7);

  // Set initial config.
  encoder.Configure(0, 5, 9, VPX_VBR, VPX_DL_REALTIME);

  // Encode first frame.
  encoder.Encode(false);

  // Change config.
  encoder.Configure(5, 2, 1, VPX_VBR, VPX_DL_REALTIME);

  // Encode 2nd frame with new config.
  encoder.Encode(true);
}

TEST(EncodeAPI, Buganizer311985118) {
  VP9Encoder encoder(0);

  // Set initial config, in particular set deadline to GOOD mode.
  encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 1st frame.
  encoder.Encode(false);

  // Change config: change threads and width.
  encoder.Configure(0, 1574, 620, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Change config: change threads, width and height.
  encoder.Configure(16, 837, 432, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 2nd frame.
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/314857577.
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, Buganizer314857577) {
  VP9Encoder encoder(4);

  // Set initial config.
  encoder.Configure(12, 1060, 437, VPX_VBR, VPX_DL_REALTIME);

  // Encode first frame.
  encoder.Encode(false);

  // Change config.
  encoder.Configure(16, 1060, 1, VPX_CBR, VPX_DL_REALTIME);

  // Encode 2nd frame with new config.
  encoder.Encode(false);

  // Encode 3rd frame with new config.
  encoder.Encode(true);

  // Change config.
  encoder.Configure(15, 33, 437, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 4th frame with new config.
  encoder.Encode(true);

  // Encode 5th frame with new config.
  encoder.Encode(false);

  // Change config.
  encoder.Configure(5, 327, 269, VPX_VBR, VPX_DL_REALTIME);

  // Change config.
  encoder.Configure(15, 1060, 437, VPX_CBR, VPX_DL_REALTIME);

  // Encode 6th frame with new config.
  encoder.Encode(false);

  // Encode 7th frame with new config.
  encoder.Encode(false);

  // Change config.
  encoder.Configure(4, 1060, 437, VPX_VBR, VPX_DL_REALTIME);

  // Encode 8th frame with new config.
  encoder.Encode(false);
}

TEST(EncodeAPI, Buganizer312875957PredBufferStride) {
  VP9Encoder encoder(-1);

  encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_REALTIME);
  encoder.Encode(true);
  encoder.Encode(false);
  encoder.Configure(0, 456, 486, VPX_VBR, VPX_DL_REALTIME);
  encoder.Encode(true);
  encoder.Configure(0, 1678, 620, VPX_CBR, 1000000);
  encoder.Encode(false);
  encoder.Encode(false);
}

// This is a test case from clusterfuzz: based on b/311294795
// Encode a few frames with multiple change config calls
// with different frame sizes.
TEST(EncodeAPI, Buganizer311294795) {
  VP9Encoder encoder(1);

  // Set initial config.
  encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_REALTIME);

  // Encode first frame.
  encoder.Encode(false);

  // Change config.
  encoder.Configure(16, 632, 620, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 2nd frame with new config
  encoder.Encode(true);

  // Change config.
  encoder.Configure(16, 1678, 342, VPX_VBR, VPX_DL_GOOD_QUALITY);

  // Encode 3rd frame with new config.
  encoder.Encode(false);

  // Change config.
  encoder.Configure(0, 1574, 618, VPX_VBR, VPX_DL_REALTIME);
  // Encode more frames with new config.
  encoder.Encode(false);
  encoder.Encode(false);
}

// Test case to capture assert issue triggered in
// vp9_bitstream.c for good_quality, speed 1, lossless;
// See comment#22 in issue:433941753.
TEST(EncodeAPI, AssertIssueGoodQualitySpeed1Lossless) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = 1540;
  cfg.g_h = 838;
  cfg.g_profile = 0;
  cfg.g_bit_depth = VPX_BITS_8;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 10000;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  cfg.rc_end_usage = VPX_VBR;
  cfg.g_threads = 1;
  cfg.rc_target_bitrate = 10000;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_LOSSLESS, 1), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 1), VPX_CODEC_OK);
  libvpx_test::RandomVideoSource video;
  video.SetSize(cfg.g_w, cfg.g_h);
  video.SetImageFormat(VPX_IMG_FMT_I420);
  video.set_limit(20);
  video.Begin();
  do {
    ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), video.duration(),
                               0, VPX_DL_GOOD_QUALITY),
              VPX_CODEC_OK);
    video.Next();
  } while (video.img() != nullptr);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

void FillPlane(uint8_t *plane, int stride, int width, int height,
               uint8_t value) {
  for (int y = 0; y < height; ++y) {
    memset(plane + y * stride, value, width);
  }
}

// Test case to capture ioc in vp9_caq_select_segment()
// in b/475394382
TEST(EncodeAPI, Buganizer475394382) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 41;
  cfg.g_h = 43;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 30;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 2;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_AQ_MODE, 2), VPX_CODEC_OK);

  cfg.rc_target_bitrate = 141452;
  cfg.g_lag_in_frames = 0;
  vpx_codec_enc_config_set(&enc, &cfg);

  vpx_image_t *img =
      vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 16);
  ASSERT_NE(img, nullptr);

  FillPlane(img->planes[VPX_PLANE_Y], img->stride[VPX_PLANE_Y], cfg.g_w,
            cfg.g_h, 0x80);
  FillPlane(img->planes[VPX_PLANE_U], img->stride[VPX_PLANE_U],
            (cfg.g_w + 1) / 2, (cfg.g_h + 1) / 2, 0x40);
  FillPlane(img->planes[VPX_PLANE_V], img->stride[VPX_PLANE_V],
            (cfg.g_w + 1) / 2, (cfg.g_h + 1) / 2, 0xC0);

  static constexpr std::array<vpx_enc_frame_flags_t, 3> frame_flags = {
    VPX_EFLAG_FORCE_KF, VP8_EFLAG_NO_REF_LAST | VP8_EFLAG_NO_REF_GF, 0
  };

  int frame_idx = 0;
  for (const auto flags : frame_flags) {
    vpx_codec_encode(&enc, img, frame_idx, 1, flags, VPX_DL_GOOD_QUALITY);
    vpx_codec_iter_t iter = nullptr;
    while (vpx_codec_get_cx_data(&enc, &iter)) {
    }
    ++frame_idx;
  }

  vpx_img_free(img);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, Buganizer317105128) {
  VP9Encoder encoder(-9);
  encoder.Configure(0, 1, 1, VPX_CBR, VPX_DL_GOOD_QUALITY);
  encoder.Configure(16, 1920, 1, VPX_CBR, VPX_DL_REALTIME);
}

TEST(EncodeAPI, Buganizer319964497) {
  VP9Encoder encoder(7);
  encoder.Configure(/*threads=*/1, /*width=*/320, /*height=*/240, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Encode(/*key_frame=*/true);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/1, /*width=*/1, /*height=*/1, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/1, /*width=*/2, /*height=*/2, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
}

TEST(EncodeAPI, Buganizer329088759RowMT0) {
  VP9Encoder encoder(8, 0, VPX_BITS_8, VPX_IMG_FMT_I444);
  encoder.Configure(/*threads=*/8, /*width=*/1686, /*height=*/398, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/0, /*width=*/1686, /*height=*/1, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/0, /*width=*/1482, /*height=*/113, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/0, /*width=*/881, /*height=*/59, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Configure(/*threads=*/13, /*width=*/1271, /*height=*/385, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/2, /*width=*/1, /*height=*/62, VPX_VBR,
                    VPX_DL_REALTIME);
}

TEST(EncodeAPI, Buganizer329088759RowMT1) {
  VP9Encoder encoder(8, 1, VPX_BITS_8, VPX_IMG_FMT_I444);
  encoder.Configure(/*threads=*/8, /*width=*/1686, /*height=*/398, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Encode(/*key_frame=*/false);
  // Needs to set threads to non-zero to repro the issue.
  encoder.Configure(/*threads=*/2, /*width=*/1686, /*height=*/1, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/2, /*width=*/1482, /*height=*/113, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/2, /*width=*/881, /*height=*/59, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Configure(/*threads=*/13, /*width=*/1271, /*height=*/385, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/2, /*width=*/1, /*height=*/62, VPX_VBR,
                    VPX_DL_REALTIME);
}

TEST(EncodeAPI, Buganizer331086799) {
  VP9Encoder encoder(6, 1, VPX_BITS_8, VPX_IMG_FMT_I420);
  encoder.Configure(0, 1385, 1, VPX_CBR, VPX_DL_REALTIME);
  encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME);
  encoder.Encode(false);
  encoder.Configure(16, 1385, 1, VPX_VBR, VPX_DL_GOOD_QUALITY);
  encoder.Encode(false);
  encoder.Encode(false);
  encoder.Configure(0, 1, 1, VPX_CBR, VPX_DL_REALTIME);
  encoder.Encode(true);
}

TEST(EncodeAPI, Buganizer331108729) {
  VP9Encoder encoder(1, 1, VPX_BITS_8, VPX_IMG_FMT_I422);
  encoder.Configure(0, 1919, 260, VPX_VBR, VPX_DL_REALTIME);
  encoder.Configure(9, 440, 1, VPX_CBR, VPX_DL_GOOD_QUALITY);
  encoder.Encode(true);
  encoder.Configure(8, 1919, 260, VPX_VBR, VPX_DL_REALTIME);
  encoder.Encode(false);
}

TEST(EncodeAPI, Buganizer331108922BitDepth8) {
  VP9Encoder encoder(9, 1, VPX_BITS_8, VPX_IMG_FMT_I420);
  encoder.Configure(/*threads=*/1, /*width=*/1, /*height=*/1080, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/0, /*width=*/1, /*height=*/1080, VPX_CBR,
                    VPX_DL_GOOD_QUALITY);
  encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/394, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/798, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
}

// Encode some frames, flip from BEST_QUALITY to REALTIME after 2 frames.
// This test is taken from the code snippet in issue:441668134.
TEST(EncodeAPI, Buganizer441668134) {
  // Get VP9 encoder interface.
  vpx_codec_iface_t *iface = vpx_codec_vp9_cx();
  // Initialize encoder configuration with default values.
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_lag_in_frames = 0;
  cfg.rc_max_quantizer = 0;
  unsigned long init_flags = 0;
  vpx_codec_ctx_t ctx;
  ASSERT_EQ(vpx_codec_enc_init(&ctx, iface, &cfg, init_flags), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control_(&ctx, VP8E_SET_CPUUSED, 9), 0);
  ASSERT_EQ(vpx_codec_control_(&ctx, VP9E_SET_DELTA_Q_UV, -15), 0);
  // Image allocation.
  vpx_img_fmt_t img_fmt = VPX_IMG_FMT_I420;
  vpx_image_t *img = vpx_img_alloc(nullptr, img_fmt, cfg.g_w, cfg.g_h, 32);
  for (unsigned int y = 0; y < img->d_h; y++) {
    for (unsigned int x = 0; x < img->d_w; x++) {
      img->planes[0][y * img->stride[0] + x] = ((x ^ y) * 127) & 0xFF;
    }
  }
  const unsigned int uv_height = (img->d_h + 1) >> 1;
  for (int i : { VPX_PLANE_U, VPX_PLANE_V }) {
    memset(img->planes[i], 0, img->stride[i] * uv_height);
  }
  // Encode some frames.
  int num_frames = 6;
  static constexpr int kChoices[6] = { 1, 1, 0, 0, 0, 0 };
  for (int frame = 0; frame < num_frames; frame++) {
    vpx_enc_deadline_t deadline = VPX_DL_REALTIME;
    uint8_t dl_choice = kChoices[frame];
    if (dl_choice == 1) deadline = VPX_DL_BEST_QUALITY;
    // Encode frame.
    ASSERT_EQ(vpx_codec_encode(&ctx, img, frame, 1, 0, deadline), VPX_CODEC_OK);
  }
  vpx_img_free(img);
  vpx_codec_destroy(&ctx);
}

// Encode a few frames, with realtime mode and tile_rows set to 1,
// with row-mt enabled. This triggers an assertion in vp9_bitstream.c (in
// function write_modes()), as in the issue:442105459. In this test it happens
// on very first encoded frame since lag_in_frames = 0. Issue is due to enabling
// TILE_ROWS, with number of tile_rows more than the number of superblocks.
// This test sets 2 tile_rows with height corresponding to 1 superblock (sb).
TEST(EncodeAPI, Buganizer442105459_2RowTiles) {
  // Initialize VP9 encoder interface
  vpx_codec_iface_t *iface = vpx_codec_vp9_cx();
  // Get default encoder configuration
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  // Configure encoder
  cfg.g_w = 946u;
  cfg.g_h = 64u;  // 1 sb row, 2 tile_rows set below.
  cfg.g_threads = 1;
  cfg.g_profile = 0;
  cfg.g_bit_depth = VPX_BITS_8;
  // Rate control targeting deeper encoding paths
  cfg.rc_target_bitrate = 100;
  cfg.rc_min_quantizer = 0;
  cfg.rc_max_quantizer = 0;
  cfg.rc_end_usage = VPX_VBR;
  cfg.ss_number_layers = 1;
  cfg.g_lag_in_frames = 0;
  // Initialize encoder context
  vpx_codec_ctx_t ctx;
  ASSERT_EQ(vpx_codec_enc_init(&ctx, iface, &cfg, 0), VPX_CODEC_OK);
  // Set control parameters
  vpx_codec_control_(&ctx, VP8E_SET_CPUUSED, -5);
  vpx_codec_control_(&ctx, VP9E_SET_TILE_ROWS, 1);
  vpx_codec_control_(&ctx, VP9E_SET_TILE_COLUMNS, 1);
  vpx_codec_control_(&ctx, VP9E_SET_ROW_MT, 1);
  // Image format selection
  vpx_img_fmt_t img_fmt = VPX_IMG_FMT_I420;
  // Allocate image with varied alignment
  vpx_image_t *img = vpx_img_alloc(nullptr, img_fmt, cfg.g_w, cfg.g_h, 1);
  for (unsigned int y = 0; y < img->d_h; y++) {
    for (unsigned int x = 0; x < img->d_w; x++) {
      img->planes[0][y * img->stride[0] + x] = ((x ^ y) * 127) & 0xFF;
    }
  }
  const unsigned int uv_height = (img->d_h + 1) >> 1;
  for (int i : { VPX_PLANE_U, VPX_PLANE_V }) {
    memset(img->planes[i], 0, img->stride[i] * uv_height);
  }
  // Encode with dynamic configuration changes
  int num_frames = 2;
  // Per-frame constants captured from the original run (indices consumed per
  // frame)
  const vpx_codec_pts_t frame_pts_mul[] = { 33333UL, 33333UL };
  const unsigned long frame_durations[] = { 33333UL, 33333UL };
  const vpx_enc_deadline_t frame_deadlines[] = { VPX_DL_REALTIME,
                                                 VPX_DL_REALTIME };
  for (int frame = 0; frame < num_frames; frame++) {
    // Encode frame
    vpx_codec_pts_t pts = frame * frame_pts_mul[frame];
    unsigned long duration = frame_durations[frame];
    vpx_enc_deadline_t deadline = frame_deadlines[frame];
    ASSERT_EQ(vpx_codec_encode(&ctx, img, pts, duration, /*flags*/ 0, deadline),
              VPX_CODEC_OK);
  }
  // Flush encoder.
  ASSERT_EQ(vpx_codec_encode(&ctx, nullptr, 0, 0, 0, VPX_DL_REALTIME), 0);
  // Get remaining data
  vpx_codec_iter_t iter = nullptr;
  while (vpx_codec_get_cx_data(&ctx, &iter) != nullptr) {
    // Process remaining packets
  }
  vpx_img_free(img);
  vpx_codec_destroy(&ctx);
}

// Encode a few frames, with realtime mode and tile_rows set to 1,
// with row-mt enabled. This triggers an assertion in vp9_bitstream.c (in
// function write_modes()), as in the issue:442105459. In this test it happens
// on very first encoded frame since lag_in_frames = 0. Issue is due to enabling
// TILE_ROWS, with number of tile_rows more than the number of superblocks.
// This test sets 4 tile_rows with height corresponding to 3 superblocks.
TEST(EncodeAPI, Buganizer442105459_4RowTiles) {
  // Initialize VP9 encoder interface
  vpx_codec_iface_t *iface = vpx_codec_vp9_cx();
  // Get default encoder configuration
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  // Configure encoder
  cfg.g_w = 946u;
  cfg.g_h = 192u;  // 3 sb rows, 4 tile_rows set below.
  cfg.g_threads = 1;
  cfg.g_profile = 0;
  cfg.g_bit_depth = VPX_BITS_8;
  // Rate control targeting deeper encoding paths
  cfg.rc_target_bitrate = 100;
  cfg.rc_min_quantizer = 0;
  cfg.rc_max_quantizer = 0;
  cfg.rc_end_usage = VPX_VBR;
  cfg.ss_number_layers = 1;
  cfg.g_lag_in_frames = 0;
  // Initialize encoder context
  vpx_codec_ctx_t ctx;
  ASSERT_EQ(vpx_codec_enc_init(&ctx, iface, &cfg, 0), VPX_CODEC_OK);
  // Set control parameters
  vpx_codec_control_(&ctx, VP8E_SET_CPUUSED, -5);
  vpx_codec_control_(&ctx, VP9E_SET_TILE_ROWS, 2);
  vpx_codec_control_(&ctx, VP9E_SET_TILE_COLUMNS, 1);
  vpx_codec_control_(&ctx, VP9E_SET_ROW_MT, 1);
  // Image format selection
  vpx_img_fmt_t img_fmt = VPX_IMG_FMT_I420;
  // Allocate image with varied alignment
  vpx_image_t *img = vpx_img_alloc(nullptr, img_fmt, cfg.g_w, cfg.g_h, 1);
  for (unsigned int y = 0; y < img->d_h; y++) {
    for (unsigned int x = 0; x < img->d_w; x++) {
      img->planes[0][y * img->stride[0] + x] = ((x ^ y) * 127) & 0xFF;
    }
  }
  const unsigned int uv_height = (img->d_h + 1) >> 1;
  for (int i : { VPX_PLANE_U, VPX_PLANE_V }) {
    memset(img->planes[i], 0, img->stride[i] * uv_height);
  }
  // Encode with dynamic configuration changes
  int num_frames = 2;
  // Per-frame constants captured from the original run (indices consumed per
  // frame)
  const vpx_codec_pts_t frame_pts_mul[] = { 33333UL, 33333UL };
  const unsigned long frame_durations[] = { 33333UL, 33333UL };
  const vpx_enc_deadline_t frame_deadlines[] = { VPX_DL_REALTIME,
                                                 VPX_DL_REALTIME };
  for (int frame = 0; frame < num_frames; frame++) {
    // Encode frame
    vpx_codec_pts_t pts = frame * frame_pts_mul[frame];
    unsigned long duration = frame_durations[frame];
    vpx_enc_deadline_t deadline = frame_deadlines[frame];
    ASSERT_EQ(vpx_codec_encode(&ctx, img, pts, duration, /*flags*/ 0, deadline),
              VPX_CODEC_OK);
  }
  // Flush encoder.
  ASSERT_EQ(vpx_codec_encode(&ctx, nullptr, 0, 0, 0, VPX_DL_REALTIME), 0);
  // Get remaining data
  vpx_codec_iter_t iter = nullptr;
  while (vpx_codec_get_cx_data(&ctx, &iter) != nullptr) {
    // Process remaining packets
  }
  vpx_img_free(img);
  vpx_codec_destroy(&ctx);
}

#if CONFIG_VP9_HIGHBITDEPTH
TEST(EncodeAPI, Buganizer329674887RowMT0BitDepth12) {
  VP9Encoder encoder(8, 0, VPX_BITS_12, VPX_IMG_FMT_I444);
  encoder.Configure(/*threads=*/2, /*width=*/1030, /*height=*/583, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/0, /*width=*/1030, /*height=*/1, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/0, /*width=*/548, /*height=*/322, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/16, /*width=*/24, /*height=*/583, VPX_CBR,
                    VPX_DL_GOOD_QUALITY);
}

TEST(EncodeAPI, Buganizer329179808RowMT0BitDepth10) {
  VP9Encoder encoder(4, 0, VPX_BITS_10, VPX_IMG_FMT_I444);
  encoder.Configure(/*threads=*/16, /*width=*/1488, /*height=*/5, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/16, /*width=*/839, /*height=*/1, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/11, /*width=*/657, /*height=*/5, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
}

TEST(EncodeAPI, Buganizer329179808RowMT1BitDepth10) {
  VP9Encoder encoder(4, 1, VPX_BITS_10, VPX_IMG_FMT_I444);
  encoder.Configure(/*threads=*/16, /*width=*/1488, /*height=*/5, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/16, /*width=*/839, /*height=*/1, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/11, /*width=*/657, /*height=*/5, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
}

TEST(EncodeAPI, Buganizer331108922BitDepth12) {
  VP9Encoder encoder(9, 1, VPX_BITS_12, VPX_IMG_FMT_I444);
  encoder.Configure(/*threads=*/1, /*width=*/1, /*height=*/1080, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/0, /*width=*/1, /*height=*/1080, VPX_CBR,
                    VPX_DL_GOOD_QUALITY);
  encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/394, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
  encoder.Encode(/*key_frame=*/true);
  encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/798, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false);
}

TEST(EncodeAPI, InvalidInputRange) {
  constexpr int kWidth = 64;
  constexpr int kHeight = 64;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg,
                                         /*usage=*/0),
            VPX_CODEC_OK);
  cfg.g_w = kWidth;
  cfg.g_h = kHeight;

  std::unique_ptr<vpx_image_t, decltype(&vpx_img_free)> image(
      vpx_img_alloc(nullptr, VPX_IMG_FMT_I42016, cfg.g_w, cfg.g_h, 0),
      &vpx_img_free);
  vpx_image_t *img = image.get();
  ASSERT_NE(img, nullptr);

  for (const auto bitdepth : { VPX_BITS_8, VPX_BITS_10, VPX_BITS_12 }) {
    cfg.g_profile = (bitdepth == VPX_BITS_8) ? 0 : 2;
    cfg.g_bit_depth = bitdepth;

    // A value that's slightly over the max is used to ensure when the range is
    // not checked an encode won't produce any integer overflows. Values
    // outside of the valid range are not supported, so overflows will be
    // ignored in that case.
    const int val = 1 << bitdepth;
    for (const int plane : { VPX_PLANE_Y, VPX_PLANE_U, VPX_PLANE_V }) {
      const int width = (plane == VPX_PLANE_Y) ? img->d_w : (img->d_w + 1) / 2;
      const int height = (plane == VPX_PLANE_Y) ? img->d_h : (img->d_h + 1) / 2;
      uint8_t *p = img->planes[plane];
      for (int y = 0; y < height; ++y) {
        vpx_memset16(p, val, width);
        p += img->stride[plane];
      }
    }

    vpx_codec_ctx_t enc;
    ASSERT_EQ(vpx_codec_enc_init(&enc, vpx_codec_vp9_cx(), &cfg,
                                 VPX_CODEC_USE_HIGHBITDEPTH),
              VPX_CODEC_OK);

    for (int check_input_range = 0; check_input_range <= 1;
         ++check_input_range) {
      EXPECT_EQ(vpx_codec_control(&enc, VP9E_SET_VALIDATE_HBD_INPUT,
                                  check_input_range),
                VPX_CODEC_OK);

      EXPECT_EQ(vpx_codec_encode(&enc, img, /*pts=*/0, /*duration=*/1,
                                 /*flags=*/0, VPX_DL_REALTIME),
                check_input_range ? VPX_CODEC_INVALID_PARAM : VPX_CODEC_OK)
          << "Error: " << vpx_codec_error_detail(&enc)
          << ", bitdepth: " << bitdepth
          << ", check_input_range: " << check_input_range;
    }

    EXPECT_EQ(vpx_codec_encode(&enc, /*img=*/nullptr, /*pts=*/0, /*duration=*/0,
                               /*flags=*/0, VPX_DL_REALTIME),
              VPX_CODEC_OK);
    EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
  }
}
#endif  // CONFIG_VP9_HIGHBITDEPTH

TEST(EncodeAPI, VP9GlobalHeaders) {
  constexpr int kWidth = 320;
  constexpr int kHeight = 240;

  libvpx_test::DummyVideoSource video;
  video.SetSize(kWidth, kHeight);

#if CONFIG_VP9_HIGHBITDEPTH
  const int profiles[] = { 0, 1, 2, 3 };
#else
  const int profiles[] = { 0, 1 };
#endif
  char str[80];
  for (const int profile : profiles) {
    std::vector<vpx_bit_depth_t> bitdepths;
    std::vector<vpx_img_fmt_t> formats;
    switch (profile) {
      case 0:
        bitdepths = { VPX_BITS_8 };
        formats = { VPX_IMG_FMT_I420 };
        break;
      case 1:
        bitdepths = { VPX_BITS_8 };
        formats = { VPX_IMG_FMT_I422, VPX_IMG_FMT_I444 };
        break;
#if CONFIG_VP9_HIGHBITDEPTH
      case 2:
        bitdepths = { VPX_BITS_10, VPX_BITS_12 };
        formats = { VPX_IMG_FMT_I42016 };
        break;
      case 3:
        bitdepths = { VPX_BITS_10, VPX_BITS_12 };
        formats = { VPX_IMG_FMT_I42216, VPX_IMG_FMT_I44416 };
        break;
#endif
    }

    for (const auto format : formats) {
      for (const auto bitdepth : bitdepths) {
        snprintf(str, sizeof(str), "profile: %d bitdepth: %d format: %d",
                 profile, bitdepth, format);
        SCOPED_TRACE(str);

        vpx_codec_enc_cfg_t cfg = {};
        struct Encoder {
          ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
          vpx_codec_ctx_t ctx = {};
        } enc;
        vpx_codec_ctx_t *const ctx = &enc.ctx;

        ASSERT_EQ(vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg, 0),
                  VPX_CODEC_OK);
        cfg.g_w = kWidth;
        cfg.g_h = kHeight;
        cfg.g_lag_in_frames = 0;
        cfg.g_pass = VPX_RC_ONE_PASS;
        cfg.g_profile = profile;
        cfg.g_bit_depth = bitdepth;
        ASSERT_EQ(
            vpx_codec_enc_init(ctx, vpx_codec_vp9_cx(), &cfg,
                               bitdepth == 8 ? 0 : VPX_CODEC_USE_HIGHBITDEPTH),
            VPX_CODEC_OK);
        ASSERT_EQ(vpx_codec_control_(ctx, VP8E_SET_CPUUSED, 2), VPX_CODEC_OK);
        ASSERT_EQ(vpx_codec_control_(ctx, VP9E_SET_TARGET_LEVEL, 62),
                  VPX_CODEC_OK);

        vpx_fixed_buf_t *global_headers = vpx_codec_get_global_headers(ctx);
        EXPECT_NE(global_headers, nullptr);
        EXPECT_EQ(global_headers->sz, size_t{ 9 });

        video.SetImageFormat(format);
        video.Begin();
        EXPECT_EQ(
            vpx_codec_encode(ctx, video.img(), video.pts(), video.duration(),
                             /*flags=*/0, VPX_DL_GOOD_QUALITY),
            VPX_CODEC_OK)
            << vpx_codec_error_detail(ctx);

        global_headers = vpx_codec_get_global_headers(ctx);
        EXPECT_NE(global_headers, nullptr);
        EXPECT_EQ(global_headers->sz, size_t{ 12 });
        uint8_t chroma_subsampling;
        if ((format & VPX_IMG_FMT_I420) == VPX_IMG_FMT_I420) {
          chroma_subsampling = 1;
        } else if ((format & VPX_IMG_FMT_I422) == VPX_IMG_FMT_I422) {
          chroma_subsampling = 2;
        } else {  // VPX_IMG_FMT_I444
          chroma_subsampling = 3;
        }
        const uint8_t expected_headers[] = { 1,
                                             1,
                                             static_cast<uint8_t>(profile),
                                             2,
                                             1,
                                             /*level,*/ 3,
                                             1,
                                             static_cast<uint8_t>(bitdepth),
                                             4,
                                             1,
                                             chroma_subsampling };
        const uint8_t *actual_headers =
            reinterpret_cast<const uint8_t *>(global_headers->buf);
        for (int i = 0; i < 5; ++i) {
          EXPECT_EQ(expected_headers[i], actual_headers[i]) << "index: " << i;
        }
        EXPECT_NE(actual_headers[6], 0);  // level
        for (int i = 5; i < 11; ++i) {
          EXPECT_EQ(expected_headers[i], actual_headers[i + 1])
              << "index: " << i + 1;
        }
      }
    }
  }
}

TEST(EncodeAPI, AomediaIssue3509VbrMinSection2PercentVP9) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 1920;
  cfg.g_h = 1080;
  cfg.g_lag_in_frames = 0;
  cfg.rc_target_bitrate = 1000000;
  // Set this to more than 1 percent to cause a signed integer overflow in the
  // multiplication rc->avg_frame_bandwidth * oxcf->rc_cfg.vbrmin_section in
  // vp9_rc_update_framerate() if the multiplication is done in the `int` type.
  cfg.rc_2pass_vbr_minsection_pct = 2;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  // Encode frame.
  // `duration` can go as high as 300, but the UBSan error is gone if
  // `duration` is 301 or higher.
  ASSERT_EQ(
      vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME),
      VPX_CODEC_OK);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, AomediaIssue3509VbrMinSection101PercentVP9) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 1920;
  cfg.g_h = 1080;
  cfg.g_lag_in_frames = 0;
  cfg.rc_target_bitrate = 1000000;
  // Set this to more than 100 percent to cause an error when vbr_min_bits is
  // cast to `int` in vp9_rc_update_framerate() if vbr_min_bits is not clamped
  // to INT_MAX.
  cfg.rc_2pass_vbr_minsection_pct = 101;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  // Encode frame.
  // `duration` can go as high as 300, but the UBSan error is gone if
  // `duration` is 301 or higher.
  ASSERT_EQ(
      vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME),
      VPX_CODEC_OK);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, Chromium352414650) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 1024;
  cfg.g_h = 1024;
  cfg.g_profile = 0;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  cfg.rc_max_quantizer = 58;
  cfg.rc_min_quantizer = 2;
  cfg.g_threads = 4;
  cfg.rc_resize_allowed = 0;
  cfg.rc_dropframe_thresh = 0;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 1000000;
  cfg.kf_min_dist = 0;
  cfg.kf_max_dist = 10000;
  cfg.rc_end_usage = VPX_CBR;
  cfg.rc_target_bitrate = 754974;
  cfg.ts_number_layers = 3;
  cfg.ts_periodicity = 4;
  cfg.ts_layer_id[0] = 0;
  cfg.ts_layer_id[1] = 2;
  cfg.ts_layer_id[2] = 1;
  cfg.ts_layer_id[3] = 2;
  cfg.ts_rate_decimator[0] = 4;
  cfg.ts_rate_decimator[1] = 2;
  cfg.ts_rate_decimator[2] = 1;
  cfg.layer_target_bitrate[0] = 2147483;
  cfg.layer_target_bitrate[1] = 3006476;
  cfg.layer_target_bitrate[2] = 4294967;
  cfg.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_0212;
  cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 7), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_TILE_COLUMNS, 2), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_ROW_MT, 1), VPX_CODEC_OK);

  vpx_svc_extra_cfg_t svc_cfg = {};
  svc_cfg.max_quantizers[0] = svc_cfg.max_quantizers[1] =
      svc_cfg.max_quantizers[2] = 58;
  svc_cfg.min_quantizers[0] = svc_cfg.min_quantizers[1] =
      svc_cfg.min_quantizers[2] = 2;
  svc_cfg.scaling_factor_num[0] = svc_cfg.scaling_factor_num[1] =
      svc_cfg.scaling_factor_num[2] = 1;
  svc_cfg.scaling_factor_den[0] = svc_cfg.scaling_factor_den[1] =
      svc_cfg.scaling_factor_den[2] = 1;
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC, 1), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_AQ_MODE, 3), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_STATIC_THRESHOLD, 1),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_COLOR_SPACE, VPX_CS_SMPTE_170),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_COLOR_RANGE, VPX_CR_STUDIO_RANGE),
            VPX_CODEC_OK);

  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  // Encode frame.
  ASSERT_EQ(vpx_codec_encode(&enc, image, 0, /*duration=*/500000,
                             VPX_EFLAG_FORCE_KF, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, PerFramePsnrNotSupportedWithLagInFrames) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_NE(cfg.g_lag_in_frames, 0u);

  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  vpx_enc_frame_flags_t psnr_flags = VPX_EFLAG_CALCULATE_PSNR;
  ASSERT_EQ(vpx_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1, psnr_flags,
                             VPX_DL_REALTIME),
            VPX_CODEC_INCAPABLE);

  // Free resources.
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, Buganizer487259772ScaledRefs) {
  libvpx_test::ACMRandom rng;
  VP9Encoder encoder(/*speed=*/7, /*row_mt=*/0, VPX_BITS_8, VPX_IMG_FMT_I420);
  encoder.Configure(/*threads=*/1, /*width=*/478, /*height=*/755, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Configure(/*threads=*/1, /*width=*/387, /*height=*/438, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false, &rng);
  encoder.Encode(/*key_frame=*/true, &rng);
  encoder.Encode(/*key_frame=*/false, &rng);
  encoder.Encode(/*key_frame=*/false, &rng);

  encoder.Configure(/*threads=*/1, /*width=*/341, /*height=*/655, VPX_VBR,
                    VPX_DL_REALTIME);
  encoder.Encode(/*key_frame=*/false, &rng);
  encoder.Encode(/*key_frame=*/false, &rng);
}

#if CONFIG_VP9_HIGHBITDEPTH
// This test uses source input based on the issue: 488585490,
// which is invalid for 10 bit. This test checks that the
// encoder correctly returns VPX_CODEC_INVALID_PARAM.
TEST(EncodeAPI, Buganizer488585490CostTableOverflow) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, /*usage=*/0),
            VPX_CODEC_OK);

  cfg.g_w = 149;
  cfg.g_h = 48;
  cfg.g_profile = 3;
  cfg.g_bit_depth = VPX_BITS_10;
  cfg.g_input_bit_depth = 10;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, VPX_CODEC_USE_HIGHBITDEPTH),
            VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 7), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_LOSSLESS, 1), VPX_CODEC_OK);

  libvpx_test::YUVVideoSource video(
      "repro-b488585490.yuv", VPX_IMG_FMT_I44416, cfg.g_w, cfg.g_h,
      /*rate_numerator=*/1, /*rate_denominator=*/30, /*start=*/0, /*limit=*/1);
  video.Begin();
  ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), /*duration=*/66666,
                             /*flags=*/0, VPX_DL_REALTIME),
            VPX_CODEC_INVALID_PARAM);

  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

void TestEncoderImageMismatch(bool use_highbitdepth) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 180;
  cfg.g_h = 68;
  cfg.g_lag_in_frames = 0;

  vpx_img_fmt_t img_fmt;
  vpx_codec_flags_t flags = 0;

  if (use_highbitdepth) {
    cfg.g_profile = 2;
    cfg.g_bit_depth = VPX_BITS_10;
    flags = VPX_CODEC_USE_HIGHBITDEPTH;
    img_fmt = VPX_IMG_FMT_I420;
  } else {
    cfg.g_profile = 0;
    flags = 0;
    img_fmt = VPX_IMG_FMT_I42016;
  }

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, flags), VPX_CODEC_OK);

  vpx_image_t *img = vpx_img_alloc(nullptr, img_fmt, cfg.g_w, cfg.g_h, 1);
  ASSERT_NE(img, nullptr);

  ASSERT_EQ(vpx_codec_encode(&enc, img, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_INVALID_PARAM);

  vpx_img_free(img);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, HighbdEncoderI420ImageMismatch) {
  TestEncoderImageMismatch(true);
}

TEST(EncodeAPI, NonHighbdEncoderI42016ImageMismatch) {
  TestEncoderImageMismatch(false);
}
#endif

TEST(EncodeAPI, SvcTestInvalidInputs) {
  // Initialize libvpx encoder.
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = 1280;
  cfg.g_h = 720;
  cfg.g_profile = 0;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 1000;
  cfg.g_lag_in_frames = 0;
  cfg.rc_max_quantizer = 58;
  cfg.rc_min_quantizer = 2;
  cfg.ss_number_layers = 2;
  cfg.ts_number_layers = 2;
  cfg.ts_periodicity = 2;
  cfg.ts_layer_id[0] = 0;
  cfg.ts_layer_id[1] = 1;
  cfg.ts_rate_decimator[0] = 2;
  cfg.ts_rate_decimator[1] = 1;
  cfg.layer_target_bitrate[0] = 100;
  cfg.layer_target_bitrate[1] = 200;
  cfg.layer_target_bitrate[2] = 300;
  cfg.layer_target_bitrate[3] = 400;
  cfg.rc_target_bitrate = 700;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);
  vpx_svc_extra_cfg_t svc_cfg = {};
  svc_cfg.scaling_factor_num[0] = 1;
  svc_cfg.scaling_factor_den[0] = 2;
  svc_cfg.scaling_factor_num[1] = 1;
  svc_cfg.scaling_factor_den[1] = 1;
  for (unsigned int i = 0; i < cfg.ss_number_layers * cfg.ss_number_layers;
       i++) {
    svc_cfg.max_quantizers[i] = 56;
    svc_cfg.min_quantizers[i] = 2;
  }
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC, 1), VPX_CODEC_OK);
  // Check for invalid quantizer inputs.
  svc_cfg.min_quantizers[1] = 2;
  svc_cfg.max_quantizers[1] = 65;
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_INVALID_PARAM);
  svc_cfg.min_quantizers[1] = 2;
  svc_cfg.max_quantizers[1] = -1;
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_INVALID_PARAM);
  svc_cfg.min_quantizers[1] = 64;
  svc_cfg.max_quantizers[1] = 56;
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_INVALID_PARAM);
  svc_cfg.min_quantizers[1] = -1;
  svc_cfg.max_quantizers[1] = 56;
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_INVALID_PARAM);

  // Check for invalid duration input.
  vpx_svc_ref_frame_config_t ref_frame_config = {};
  for (unsigned int i = 0; i < cfg.ss_number_layers; i++) {
    ref_frame_config.lst_fb_idx[i] = 0;
    ref_frame_config.reference_last[i] = 1;
    ref_frame_config.update_last[i] = 1;
    ref_frame_config.duration[i] = 1000;
  }
  ref_frame_config.duration[0] = INT64_MAX;
  ASSERT_EQ(
      vpx_codec_control(&enc, VP9E_SET_SVC_REF_FRAME_CONFIG, &ref_frame_config),
      VPX_CODEC_OK);
  // Create input image.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);
  ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 300, 0, VPX_DL_REALTIME),
            VPX_CODEC_INVALID_PARAM);
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

TEST(EncodeAPI, LargeDimensionsTokenAllocOverflow) {
  // To trigger the signed integer overflow, we need:
  // aligned_mb_rows * aligned_mb_cols * 772 > INT_MAX (2147483647)
  // aligned_mb_rows * aligned_mb_cols > 2147483647 / 772 = 2781714.5
  // For a square-ish frame, sqrt(2781714.5) is approx 1667.8.
  // Since aligned dimensions are multiples of 4, we round up to 1668.
  // 1668 * 1668 * 772 = 2147876928, which is > INT_MAX.
  // To get aligned MBs of 1668, we need input MBs in range (1664, 1668].
  // 1665 is chosen as the smallest integer in this range.
  int64_t tokens = get_token_alloc(1665, 1665);
  EXPECT_EQ(tokens, (int64_t)1668 * 1668 * (16 * 16 * 3 + 4));
}

// 4 spatial layers, 1 temporal, speed 1 and 0 for spatial_layer = 0, 1,
// with different scale factors. Reproduces bug: 505665613.
TEST(EncodeAPI, SvcIssue505665613) {
  vpx_codec_ctx_t codec;
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_iface_t *iface = vpx_codec_vp9_cx();
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = 935;
  cfg.g_h = 464;
  cfg.g_profile = 0;
  cfg.g_lag_in_frames = 0;
  cfg.ss_number_layers = 4;
  cfg.ts_number_layers = 1;
  for (int i = 0; i < 4; ++i) {
    cfg.layer_target_bitrate[i] = cfg.rc_target_bitrate / 4;
  }
  ASSERT_EQ(vpx_codec_enc_init(&codec, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC, 1), VPX_CODEC_OK);

  vpx_svc_extra_cfg_t svc = {};
  svc.max_quantizers[0] = 49;
  svc.min_quantizers[0] = 5;
  svc.scaling_factor_num[0] = 2;
  svc.scaling_factor_den[0] = 12;
  svc.speed_per_layer[0] = 1;
  svc.loopfilter_ctrl[0] = 1;

  svc.max_quantizers[1] = 46;
  svc.min_quantizers[1] = 28;
  svc.scaling_factor_num[1] = 9;
  svc.scaling_factor_den[1] = 9;
  svc.speed_per_layer[1] = 0;
  svc.loopfilter_ctrl[1] = 0;

  svc.max_quantizers[2] = 32;
  svc.min_quantizers[2] = 0;
  svc.scaling_factor_num[2] = 1;
  svc.scaling_factor_den[2] = 4;
  svc.speed_per_layer[2] = 4;
  svc.loopfilter_ctrl[2] = 0;

  svc.max_quantizers[3] = 38;
  svc.min_quantizers[3] = 25;
  svc.scaling_factor_num[3] = 7;
  svc.scaling_factor_den[3] = 16;
  svc.speed_per_layer[3] = 3;
  svc.loopfilter_ctrl[3] = 0;

  svc.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_0212;
  ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC_PARAMETERS, &svc),
            VPX_CODEC_OK);
  vpx_image_t *img = vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, 935, 464, 1);
  ASSERT_NE(img, nullptr);
  libvpx_test::ACMRandom rng(libvpx_test::ACMRandom::DeterministicSeed());
  for (unsigned int i = 0; i < img->d_h; ++i) {
    for (unsigned int j = 0; j < img->d_w; ++j) {
      img->planes[0][i * img->stride[0] + j] = rng.Rand8();
    }
  }
  const unsigned int uv_h =
      (img->d_h + img->y_chroma_shift) >> img->y_chroma_shift;
  const unsigned int uv_w =
      (img->d_w + img->x_chroma_shift) >> img->x_chroma_shift;
  for (unsigned int i = 0; i < uv_h; ++i) {
    for (unsigned int j = 0; j < uv_w; ++j) {
      img->planes[1][i * img->stride[1] + j] = rng.Rand8();
      img->planes[2][i * img->stride[2] + j] = rng.Rand8();
    }
  }
  // Encode.
  ASSERT_EQ(vpx_codec_encode(&codec, img, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);
  vpx_img_free(img);
  vpx_codec_destroy(&codec);
}

TEST(EncodeAPI, Vp9TargetLevelTinyResolution) {
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = 1;
  cfg.g_h = 1;

  vpx_codec_ctx_t codec;
  ASSERT_EQ(vpx_codec_enc_init(&codec, iface, &cfg, 0), VPX_CODEC_OK);

  EXPECT_EQ(vpx_codec_control_(&codec, VP9E_SET_TARGET_LEVEL, 40),
            VPX_CODEC_OK);

  EXPECT_EQ(vpx_codec_destroy(&codec), VPX_CODEC_OK);
}

// Bug: 501612368. Configuration changes with different resolution
// to trigger issues in the re-allocation logic.
void VerifyVp9EncoderMidstreamReconfig(int width1, int height1, bool encode1,
                                       int width2, int height2, bool encode2,
                                       int width3, int height3, bool encode3) {
  vpx_codec_iface_t *const iface = &vpx_codec_vp9_cx_algo;
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_ctx_t enc;

  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = width1;
  cfg.g_h = height1;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 30;
  cfg.rc_target_bitrate = 1000;
  cfg.g_lag_in_frames = 0;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control_(&enc, VP8E_SET_CPUUSED, 5), VPX_CODEC_OK);

  if (encode1) {
    vpx_image_t *image =
        CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);

    ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_REALTIME),
              VPX_CODEC_OK);
    vpx_img_free(image);
  }

  cfg.g_w = width2;
  cfg.g_h = height2;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  if (encode2) {
    vpx_image_t *image =
        CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);

    ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_REALTIME),
              VPX_CODEC_OK);
    vpx_img_free(image);
  }

  cfg.g_w = width3;
  cfg.g_h = height3;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  if (encode3) {
    vpx_image_t *image =
        CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);

    ASSERT_EQ(vpx_codec_encode(&enc, image, 1, 1, 0, VPX_DL_REALTIME),
              VPX_CODEC_OK);
    vpx_img_free(image);
  }
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// Resizing from 64x128 to 128x64: same allocation area.
// Bug: 501612368.
TEST(EncodeAPI, Vp9EncoderMidstreamReconfig1) {
  VerifyVp9EncoderMidstreamReconfig(64, 128, true, 128, 64, true, 128, 64,
                                    true);
}

// Resizing from 192x192 to 256x128: allocation area decreases
// but width is increased (so alloc for context_buffer is increased).
// Bug: 501612368.
TEST(EncodeAPI, Vp9EncoderMidstreamReconfig2) {
  VerifyVp9EncoderMidstreamReconfig(192, 192, true, 256, 128, true, 256, 128,
                                    true);
}

// Resizing from 128x128 to 96x160: allocation area decreases
// but token_alloc is increased. Bug: 501612368.
TEST(EncodeAPI, Vp9EncoderMidstreamReconfig3) {
  VerifyVp9EncoderMidstreamReconfig(128, 128, true, 96, 160, true, 96, 160,
                                    true);
}

// Start at 128x128, encode a frame. Then update to larger resolution
// twice before encoding a frame: Bug: 501612368.
TEST(EncodeAPI, Vp9EncoderMidstreamReconfig4) {
  VerifyVp9EncoderMidstreamReconfig(128, 128, true, 256, 256, false, 512, 512,
                                    true);
}
#endif  // CONFIG_VP9_ENCODER

#if CONFIG_VP9_ENCODER
void VerifyVp9UnsafeCastEosCount(double count_value) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_pass = VPX_RC_LAST_PASS;

  // Two packets required: one frame packet + one EOS packet.
  FIRSTPASS_STATS packets[2] = {};
  packets[1].count = count_value;

  cfg.rc_twopass_stats_in.buf = packets;
  cfg.rc_twopass_stats_in.sz = sizeof(packets);

  vpx_codec_ctx_t codec;
  // This should return VPX_CODEC_INVALID_PARAM instead of triggering UB.
  EXPECT_EQ(vpx_codec_enc_init(&codec, iface, &cfg, 0),
            VPX_CODEC_INVALID_PARAM);
}

TEST(EncodeAPI, Vp9UnsafeCastEosCount) { VerifyVp9UnsafeCastEosCount(1e300); }

TEST(EncodeAPI, Vp9UnsafeCastEosCountNaN) { VerifyVp9UnsafeCastEosCount(NAN); }

TEST(EncodeAPI, Vp9UnsafeCastEosCountZero) { VerifyVp9UnsafeCastEosCount(0.0); }

TEST(EncodeAPI, Vp9SvcLayeringModeNotSet) {
  const unsigned int kSpatialLayers = 4;
  const unsigned int kTemporalLayers = 3;
  const unsigned int kWidth = 640;
  const unsigned int kHeight = 320;
  const int kFrames = 8;
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = kWidth;
  cfg.g_h = kHeight;
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 30;
  cfg.g_error_resilient = 1;
  cfg.g_lag_in_frames = 0;
  cfg.rc_end_usage = VPX_CBR;
  cfg.ss_number_layers = kSpatialLayers;
  cfg.ts_number_layers = kTemporalLayers;
  cfg.rc_target_bitrate = 5000;
  cfg.ss_target_bitrate[0] = 500;
  cfg.ss_target_bitrate[1] = 1000;
  cfg.ss_target_bitrate[2] = 1500;
  cfg.ss_target_bitrate[3] = 2000;
  cfg.layer_target_bitrate[0] = 50 * cfg.ss_target_bitrate[0] / 100;
  cfg.layer_target_bitrate[1] = 70 * cfg.ss_target_bitrate[0] / 100;
  cfg.layer_target_bitrate[2] = cfg.ss_target_bitrate[0];
  cfg.layer_target_bitrate[3] = 50 * cfg.ss_target_bitrate[1] / 100;
  cfg.layer_target_bitrate[4] = 70 * cfg.ss_target_bitrate[1] / 100;
  cfg.layer_target_bitrate[5] = cfg.ss_target_bitrate[1];
  cfg.layer_target_bitrate[6] = 50 * cfg.ss_target_bitrate[2] / 100;
  cfg.layer_target_bitrate[7] = 70 * cfg.ss_target_bitrate[2] / 100;
  cfg.layer_target_bitrate[8] = cfg.ss_target_bitrate[2];
  cfg.layer_target_bitrate[9] = 50 * cfg.ss_target_bitrate[3] / 100;
  cfg.layer_target_bitrate[10] = 70 * cfg.ss_target_bitrate[3] / 100;
  cfg.layer_target_bitrate[11] = cfg.ss_target_bitrate[3];
  cfg.ts_rate_decimator[0] = 4;
  cfg.ts_rate_decimator[1] = 2;
  cfg.ts_rate_decimator[2] = 1;
  cfg.ts_periodicity = 12;
  for (unsigned int i = 0; i < cfg.ts_periodicity; ++i) {
    cfg.ts_layer_id[i] = i % kTemporalLayers;
  }
  vpx_codec_ctx_t codec;
  ASSERT_EQ(vpx_codec_enc_init(&codec, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC, 1), VPX_CODEC_OK);
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, kWidth, kHeight);
  ASSERT_NE(image, nullptr);

  for (int f = 0; f < kFrames; ++f) {
    vpx_svc_layer_id_t layer_id = {};
    layer_id.spatial_layer_id = 0;
    if (f % 4 == 0)
      layer_id.temporal_layer_id = 0;
    else if (f % 2 == 0)
      layer_id.temporal_layer_id = 1;
    else if ((f - 1) % 2 == 0)
      layer_id.temporal_layer_id = 2;
    ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC_LAYER_ID, &layer_id),
              VPX_CODEC_OK);
    const int flags = (f == 0) ? VPX_EFLAG_FORCE_KF : 0;
    ASSERT_EQ(vpx_codec_encode(&codec, image, f, 1, flags, VPX_DL_REALTIME),
              VPX_CODEC_OK);
    vpx_codec_iter_t iter = nullptr;
    while (vpx_codec_get_cx_data(&codec, &iter) != nullptr) {
    }
  }
  ASSERT_EQ(vpx_codec_encode(&codec, nullptr, kFrames, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);
  vpx_codec_iter_t iter = nullptr;
  while (vpx_codec_get_cx_data(&codec, &iter) != nullptr) {
  }
  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&codec), VPX_CODEC_OK);
}

// Test for SSIM tuning with resolution change: bug: 512508456.
TEST(EncodeAPI, BuganizerSSIMHeapOverflow) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);
  cfg.g_w = 16;
  cfg.g_h = 16;
  cfg.g_threads = 1;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  // Set VP8_TUNE_SSIM.
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_TUNING, VP8_TUNE_SSIM),
            VPX_CODEC_OK);

  vpx_image_t *img =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(img, nullptr);

  // Encode first frame
  ASSERT_EQ(vpx_codec_encode(&enc, img, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  // Change resolution to larger size.
  cfg.g_w = 4096;
  cfg.g_h = 4096;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  vpx_img_free(img);
  img = CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(img, nullptr);

  // Encode second frame.
  ASSERT_EQ(vpx_codec_encode(&enc, img, 1, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  vpx_img_free(img);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// Configure spatial layers and encode first superframe with 0 layer bitrates
// for enhancement layers, and start with SL1 (don't call encoder for SL0).
// This causes an empty superframe. Bug: 508317885.
TEST(EncodeAPI, Vp9SvcEmptySuperframe) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 640;
  cfg.g_h = 360;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  cfg.rc_end_usage = VPX_CBR;
  cfg.ss_number_layers = 3;
  cfg.ts_number_layers = 1;
  cfg.rc_dropframe_thresh = 30;
  cfg.rc_target_bitrate = 30;
  cfg.ss_target_bitrate[0] = 30;
  cfg.ss_target_bitrate[1] = 0;
  cfg.ss_target_bitrate[2] = 0;
  cfg.layer_target_bitrate[0] = 30;
  cfg.layer_target_bitrate[1] = 0;
  cfg.layer_target_bitrate[2] = 0;

  vpx_codec_ctx_t codec;
  ASSERT_EQ(vpx_codec_enc_init(&codec, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC, 1), VPX_CODEC_OK);

  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  vpx_svc_layer_id_t layer_id = {};
  layer_id.spatial_layer_id = 1;
  layer_id.temporal_layer_id = 0;
  ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC_LAYER_ID, &layer_id),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_encode(&codec, image, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  layer_id.spatial_layer_id = 2;
  layer_id.temporal_layer_id = 0;
  ASSERT_EQ(vpx_codec_control(&codec, VP9E_SET_SVC_LAYER_ID, &layer_id),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_encode(&codec, image, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  vpx_img_free(image);
  ASSERT_EQ(vpx_codec_destroy(&codec), VPX_CODEC_OK);
}

// Encode one pass VBR with nonzero-lookahead, with resize and realtime mode.
// Test for the re-allocation of count_arf_frame_usage and
// count_lastgolden_frame_usage. Bug: 515433297.
TEST(EncodeAPI, ResizeOnePassVbrAltrefUsageBug) {
  vpx_codec_ctx_t codec;
  vpx_codec_enc_cfg_t cfg;
  vpx_image_t *image;

  const int initial_width = 16;
  const int initial_height = 16;
  const int new_width = 4096;
  const int new_height = 4096;

  ASSERT_EQ(vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg, 0),
            VPX_CODEC_OK);

  cfg.g_w = initial_width;
  cfg.g_h = initial_height;
  cfg.g_threads = 1;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 1;
  cfg.rc_end_usage = VPX_VBR;
  ASSERT_EQ(vpx_codec_enc_init(&codec, vpx_codec_vp9_cx(), &cfg, 0),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&codec, VP8E_SET_CPUUSED, 5), VPX_CODEC_OK);

  image = CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);
  ASSERT_EQ(vpx_codec_encode(&codec, image, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  cfg.g_w = new_width;
  cfg.g_h = new_height;
  ASSERT_EQ(vpx_codec_enc_config_set(&codec, &cfg), VPX_CODEC_OK);

  vpx_img_free(image);
  image = CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);

  for (int i = 1; i < 30; i++) {
    ASSERT_EQ(vpx_codec_encode(&codec, image, i, 1, 0, VPX_DL_GOOD_QUALITY),
              VPX_CODEC_OK);
  }

  vpx_img_free(image);
  vpx_codec_destroy(&codec);
}

// Resize with PERCEPTUAL_AQ. Bug: 515431489.
TEST(EncodeAPI, PerceptualAQKMeansHeapOverflow) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_ctx_t enc;
  vpx_codec_enc_cfg_t cfg;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 16;
  cfg.g_h = 16;
  cfg.g_threads = 1;
  cfg.g_pass = VPX_RC_ONE_PASS;
  cfg.g_lag_in_frames = 0;
  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);

  vpx_image_t *img =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(img, nullptr);

  ASSERT_EQ(vpx_codec_encode(&enc, img, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_AQ_MODE, PERCEPTUAL_AQ),
            VPX_CODEC_OK);

  cfg.g_w = 4096;
  cfg.g_h = 4096;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  vpx_img_free(img);
  img = CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(img, nullptr);

  ASSERT_EQ(vpx_codec_encode(&enc, img, 1, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);

  vpx_img_free(img);
  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// Reproduces a VP9 encoder assertion failure / SEGV crash caused by
// superblock search failure under specific scaling and rate control scenarios.
// Bug: 479149056
TEST(EncodeAPI, VP9OssFuzz479149056) {
  VP9Encoder encoder(3, 1, VPX_BITS_8, VPX_IMG_FMT_I422);
  encoder.Configure(/*threads=*/2, /*width=*/33, /*height=*/341, VPX_CBR,
                    VPX_DL_GOOD_QUALITY);
  encoder.Encode(/*key_frame=*/true);
  encoder.Encode(/*key_frame=*/false);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/14, /*width=*/29, /*height=*/177, VPX_VBR,
                    VPX_DL_GOOD_QUALITY);
  encoder.Encode(/*key_frame=*/false);
  encoder.Configure(/*threads=*/0, /*width=*/33, /*height=*/337, VPX_CBR,
                    VPX_DL_REALTIME);
  encoder.Configure(/*threads=*/0, /*width=*/33, /*height=*/341, VPX_VBR,
                    VPX_DL_GOOD_QUALITY);
  encoder.Encode(/*key_frame=*/false);
  encoder.Encode(/*key_frame=*/false);
  encoder.Encode(/*key_frame=*/false);
  encoder.Encode(/*key_frame=*/false);
}

// Bug: 526717473. Resize up followed by activating AQ_MODE 3.
// This triggers an issue because the SVC layer buffers are not reallocated
// during the resize (since AQ_MODE is off), and they are also not reallocated
// when AQ_MODE is later turned on (since the resolution hasn't changed since
// the last config update).
TEST(EncodeAPI, SvcResolutionChangeAq3Vbr) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 320;
  cfg.g_h = 240;
  cfg.ss_number_layers = 2;
  cfg.ts_number_layers = 1;
  cfg.g_threads = 1;
  cfg.g_lag_in_frames = 0;
  cfg.rc_end_usage = VPX_VBR;
  cfg.rc_target_bitrate = 200;
  cfg.ss_target_bitrate[0] = 100;
  cfg.ss_target_bitrate[1] = 200;
  cfg.ts_target_bitrate[0] = 200;
  cfg.layer_target_bitrate[0] = 100;
  cfg.layer_target_bitrate[1] = 200;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 7), VPX_CODEC_OK);

  vpx_svc_extra_cfg_t svc_cfg = {};
  for (int i = 0; i < VPX_MAX_LAYERS; ++i) {
    svc_cfg.max_quantizers[i] = 58;
    svc_cfg.min_quantizers[i] = 2;
    svc_cfg.scaling_factor_num[i] = 1;
    svc_cfg.scaling_factor_den[i] = 1;
  }
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC, 1), VPX_CODEC_OK);

  // Resize up.
  cfg.g_w = 640;
  cfg.g_h = 480;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  // Set AQ mode 3.
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_AQ_MODE, 3), VPX_CODEC_OK);

  // Encode.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h);
  ASSERT_NE(image, nullptr);
  ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);
  vpx_img_free(image);

  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// Bug: 526717473.
TEST(EncodeAPI, SvcResolutionChangeAq3Cbr) {
  vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
  vpx_codec_enc_cfg_t cfg;
  vpx_codec_ctx_t enc;
  ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK);

  cfg.g_w = 320;
  cfg.g_h = 240;
  cfg.ss_number_layers = 2;
  cfg.ts_number_layers = 1;
  cfg.g_threads = 1;
  cfg.g_lag_in_frames = 0;
  cfg.rc_end_usage = VPX_CBR;
  cfg.rc_target_bitrate = 200;
  cfg.ss_target_bitrate[0] = 100;
  cfg.ss_target_bitrate[1] = 200;
  cfg.ts_target_bitrate[0] = 200;
  cfg.layer_target_bitrate[0] = 100;
  cfg.layer_target_bitrate[1] = 200;

  ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, 7), VPX_CODEC_OK);

  vpx_svc_extra_cfg_t svc_cfg = {};
  for (int i = 0; i < VPX_MAX_LAYERS; ++i) {
    svc_cfg.max_quantizers[i] = 58;
    svc_cfg.min_quantizers[i] = 2;
    svc_cfg.scaling_factor_num[i] = 1;
    svc_cfg.scaling_factor_den[i] = 1;
  }
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC_PARAMETERS, &svc_cfg),
            VPX_CODEC_OK);
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_SVC, 1), VPX_CODEC_OK);

  // Resize up to 640x480 while AQ_MODE is still 0.
  cfg.g_w = 640;
  cfg.g_h = 480;
  ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK);

  // Set AQ mode 3.
  ASSERT_EQ(vpx_codec_control(&enc, VP9E_SET_AQ_MODE, 3), VPX_CODEC_OK);

  // Encode at 640x480.
  vpx_image_t *const image =
      CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, 640, 480);
  ASSERT_NE(image, nullptr);
  ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_REALTIME),
            VPX_CODEC_OK);
  vpx_img_free(image);

  ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}
#endif  // CONFIG_VP9_ENCODER

}  // namespace
