blob: eac1ec0261ee07e215afbd19890c51dd6cefcbd3 [file]
/*
* Copyright (c) 2016, Alliance for Open Media. All rights reserved.
*
* This source code is subject to the terms of the BSD 2 Clause License and
* the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
* was not distributed with this source code in the LICENSE file, you can
* obtain it at www.aomedia.org/license/software. If the Alliance for Open
* Media Patent License 1.0 was not distributed with this source code in the
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
#include <array>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <tuple>
#include <vector>
#include "gtest/gtest.h"
#include "config/aom_config.h"
#include "aom/aomcx.h"
#include "aom/aom_encoder.h"
#include "aom/aom_image.h"
#if CONFIG_AV1_DECODER
#include "aom/aom_decoder.h"
#include "aom/aomdx.h"
#endif
#include "aom_mem/aom_mem.h"
#include "test/acm_random.h"
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
#include "test/md5_helper.h"
#include "test/util.h"
#include "test/video_source.h"
namespace {
#if CONFIG_REALTIME_ONLY
const unsigned int kUsage = AOM_USAGE_REALTIME;
#else
const unsigned int kUsage = AOM_USAGE_GOOD_QUALITY;
#endif
static void *Memset16(void *dest, int val, size_t length) {
uint16_t *dest16 = (uint16_t *)dest;
for (size_t i = 0; i < length; ++i) *dest16++ = val;
return dest;
}
static void FillImage(aom_image_t *img, uint8_t val) {
for (int p = 0; p < 3; ++p) {
uint8_t *buf = img->planes[p];
const int h = p == 0 ? (int)img->h : (int)((img->h + 1) / 2);
const int w = p == 0 ? (int)img->w : (int)((img->w + 1) / 2);
for (int r = 0; r < h; ++r) {
memset(buf, val, w);
buf += img->stride[p];
}
}
}
static void FillImageRandom(aom_image_t *img) {
::libaom_test::ACMRandom rnd;
rnd.Reset(::libaom_test::ACMRandom::DeterministicSeed());
for (int p = 0; p < 3; ++p) {
uint8_t *buf = img->planes[p];
const int h = p == 0 ? (int)img->h : (int)((img->h + 1) / 2);
const int w = p == 0 ? (int)img->w : (int)((img->w + 1) / 2);
for (int r = 0; r < h; ++r) {
for (int c = 0; c < w; ++c) {
buf[c] = rnd.Rand8();
}
buf += img->stride[p];
}
}
}
static void SetSvc(aom_codec_ctx_t *codec, int num_sl, const int *num,
const int *den, const int *bitrates) {
aom_svc_params_t sp = {};
sp.number_spatial_layers = num_sl;
sp.number_temporal_layers = 1;
sp.framerate_factor[0] = 1;
for (int i = 0; i < num_sl; ++i) {
sp.scaling_factor_num[i] = num[i];
sp.scaling_factor_den[i] = den[i];
sp.max_quantizers[i] = 56;
sp.min_quantizers[i] = 2;
sp.layer_target_bitrate[i] = bitrates[i];
}
ASSERT_EQ(aom_codec_control(codec, AV1E_SET_SVC_PARAMS, &sp), AOM_CODEC_OK);
}
static void EncodeOne(aom_codec_ctx_t *codec, aom_image_t *img,
aom_codec_pts_t pts) {
ASSERT_EQ(aom_codec_encode(codec, img, pts, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = NULL;
while (aom_codec_get_cx_data(codec, &iter) != NULL) {
}
}
TEST(EncodeAPI, InvalidParams) {
uint8_t buf[1] = { 0 };
aom_image_t img;
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
EXPECT_EQ(&img, aom_img_wrap(&img, AOM_IMG_FMT_I420, 1, 1, 1, buf));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_init(nullptr, nullptr, nullptr, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_init(&enc, nullptr, nullptr, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_encode(nullptr, nullptr, 0, 0, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_encode(nullptr, &img, 0, 0, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_destroy(nullptr));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_config_default(nullptr, nullptr, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_config_default(nullptr, &cfg, 0));
EXPECT_NE(aom_codec_error(nullptr), nullptr);
aom_codec_iface_t *iface = aom_codec_av1_cx();
SCOPED_TRACE(aom_codec_iface_name(iface));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_init(nullptr, iface, nullptr, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_init(&enc, iface, nullptr, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM,
aom_codec_enc_config_default(iface, &cfg, 3));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
cfg.g_w = 1 << 16;
cfg.g_h = (1 << 14) + 1;
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
cfg.g_w = (1 << 14) + 1;
cfg.g_h = 1 << 16;
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
cfg.g_forced_max_frame_width = 1 << 16;
cfg.g_forced_max_frame_height = (1 << 14) + 1;
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
cfg.g_forced_max_frame_width = (1 << 14) + 1;
cfg.g_forced_max_frame_height = 1 << 16;
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(nullptr, aom_codec_get_global_headers(nullptr));
aom_fixed_buf_t *glob_headers = aom_codec_get_global_headers(&enc);
EXPECT_NE(glob_headers->buf, nullptr);
if (glob_headers) {
free(glob_headers->buf);
free(glob_headers);
}
EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 0, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
}
TEST(EncodeAPI, InvalidSvcParams) {
uint8_t buf[6] = { 0 };
aom_image_t img;
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
EXPECT_EQ(&img, aom_img_wrap(&img, AOM_IMG_FMT_I420, 1, 1, 1, buf));
aom_codec_iface_t *iface = aom_codec_av1_cx();
EXPECT_EQ(AOM_CODEC_OK,
aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME));
cfg.g_w = 1;
cfg.g_h = 1;
EXPECT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
EXPECT_EQ(aom_codec_encode(&enc, &img, 0, 1, 0), AOM_CODEC_OK);
EXPECT_EQ(aom_codec_control(&enc, AOME_SET_NUMBER_SPATIAL_LAYERS, -1),
AOM_CODEC_INVALID_PARAM);
EXPECT_EQ(aom_codec_control(&enc, AOME_SET_NUMBER_SPATIAL_LAYERS,
AOM_MAX_SS_LAYERS + 1),
AOM_CODEC_INVALID_PARAM);
aom_svc_params_t svc_params = {};
svc_params.framerate_factor[0] = 2;
svc_params.framerate_factor[1] = 1;
svc_params.layer_target_bitrate[0] = 60 * cfg.rc_target_bitrate / 100;
svc_params.layer_target_bitrate[1] = cfg.rc_target_bitrate;
// Check scale factors.
for (int i = 0; i < AOM_MAX_SS_LAYERS; i++) {
svc_params.scaling_factor_num[i] = 1;
svc_params.scaling_factor_den[i] = 1;
}
svc_params.number_spatial_layers = 2;
svc_params.number_temporal_layers = 1;
// Set invalid factors.
svc_params.scaling_factor_num[0] = 2;
svc_params.scaling_factor_den[0] = 1;
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_INVALID_PARAM);
// Set valid factors.
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 2;
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 1;
for (const bool use_flexible_mode : { false, true }) {
if (use_flexible_mode) {
aom_svc_ref_frame_config_t ref_frame_config = {};
ref_frame_config.refresh[0] = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG,
&ref_frame_config),
AOM_CODEC_OK);
}
const int max_valid_num_spatial = use_flexible_mode ? AOM_MAX_SS_LAYERS : 3;
const int max_valid_num_temporal =
use_flexible_mode ? AOM_MAX_TS_LAYERS : 3;
for (int num_spatial = AOM_MAX_SS_LAYERS;
num_spatial <= AOM_MAX_SS_LAYERS + 1; ++num_spatial) {
for (int num_temporal = AOM_MAX_TS_LAYERS;
num_temporal <= AOM_MAX_TS_LAYERS + 1; ++num_temporal) {
svc_params.number_spatial_layers = num_spatial;
svc_params.number_temporal_layers = num_temporal;
if (num_spatial > 0 && num_spatial <= AOM_MAX_SS_LAYERS &&
num_temporal > 0 && num_temporal <= AOM_MAX_TS_LAYERS) {
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK)
<< "num_spatial: " << num_spatial
<< " num_temporal: " << num_temporal
<< " use_flexible_mode: " << use_flexible_mode;
} else {
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_INVALID_PARAM)
<< "num_spatial: " << num_spatial
<< " num_temporal: " << num_temporal
<< " use_flexible_mode: " << use_flexible_mode;
}
if (use_flexible_mode || (num_spatial <= max_valid_num_spatial &&
num_temporal <= max_valid_num_temporal)) {
EXPECT_EQ(aom_codec_encode(&enc, &img, 0, 1, 0), AOM_CODEC_OK);
} else {
EXPECT_EQ(aom_codec_encode(&enc, &img, 0, 1, 0),
AOM_CODEC_INVALID_PARAM);
}
}
}
}
EXPECT_EQ(aom_codec_encode(&enc, &img, 0, 1, 0), AOM_CODEC_OK);
EXPECT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Bug: 538651949.
TEST(EncodeAPI, SvcFixBypass) {
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_svc_layer_id_t layer_id = { 3, 0 };
aom_svc_params_t svc_params = {};
EXPECT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_threads = 1;
cfg.g_lag_in_frames = 0;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Set number of spatial layers to 4 via control SET_NUMBER_SPATIAL_LAYERS.
EXPECT_EQ(aom_codec_control(&enc, AOME_SET_NUMBER_SPATIAL_LAYERS, 4),
AOM_CODEC_OK);
// Set the active spatial layer id to 3. This is expected to fail since
// cpi->svc.number_spatial_layers is still 1 (default).
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_INVALID_PARAM);
// Set SVC params to 2x2 layer config, via AV1E_SET_SVC_PARAMS. This sets
// all svc layer parameters, allocates for the layer context, and sets use_svc
// = 1.
svc_params.number_spatial_layers = 2;
svc_params.number_temporal_layers = 2;
for (int i = 0; i < AOM_MAX_LAYERS; i++) {
svc_params.max_quantizers[i] = 52;
svc_params.min_quantizers[i] = 10;
svc_params.layer_target_bitrate[i] = 100;
}
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 2;
svc_params.scaling_factor_num[1] = 1;
svc_params.scaling_factor_den[1] = 1;
svc_params.framerate_factor[0] = 2;
svc_params.framerate_factor[1] = 1;
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
// Now try setting number of spatial layers to 3 using the control
// SET_NUMBER_SPATIAL_LAYERS. This is expected to fail because SVC was
// configured via AV1E_SET_SVC_PARAMS, so use_svc = 1.
EXPECT_EQ(aom_codec_control(&enc, AOME_SET_NUMBER_SPATIAL_LAYERS, 3),
AOM_CODEC_INVALID_PARAM);
EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, InvalidControlId) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_ERROR, aom_codec_control(&enc, -1, 0));
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_control(&enc, 0, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
}
TEST(EncodeAPI, InvalidUVStrides) {
static constexpr std::array<aom_img_fmt_t, 9> kAv1ImageFormats = {
AOM_IMG_FMT_YV12, AOM_IMG_FMT_I420, AOM_IMG_FMT_I422,
AOM_IMG_FMT_I444, AOM_IMG_FMT_NV12, AOM_IMG_FMT_I42016,
AOM_IMG_FMT_YV1216, AOM_IMG_FMT_I42216, AOM_IMG_FMT_I44416
};
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 }
};
aom_image_t img;
aom_codec_ctx_t enc;
aom_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);
ASSERT_EQ(aom_codec_enc_config_default(aom_codec_av1_cx(), &cfg,
/*usage=*/AOM_USAGE_REALTIME),
AOM_CODEC_OK);
for (const auto img_fmt : kAv1ImageFormats) {
const bool high_bit_depth =
(img_fmt & AOM_IMG_FMT_HIGHBITDEPTH) == AOM_IMG_FMT_HIGHBITDEPTH;
if (high_bit_depth && !(aom_codec_get_caps(aom_codec_av1_cx()) &
AOM_CODEC_CAP_HIGHBITDEPTH)) {
break;
}
ASSERT_EQ(aom_img_wrap(&img, img_fmt, kWidth, kHeight, /*stride_align=*/1,
buf.get()),
&img)
<< "Unable to wrap aom_image for format: " << img_fmt;
const bool is_444 = (img.x_chroma_shift == 0 && img.y_chroma_shift == 0);
const bool is_422 = (img.x_chroma_shift == 1 && img.y_chroma_shift == 0);
// 4:4:4 is allowed in profile 1, 4:2:2 in profile 2.
cfg.g_profile = is_444 + 2 * is_422;
cfg.g_w = kWidth;
cfg.g_h = kHeight;
cfg.g_bit_depth = high_bit_depth ? AOM_BITS_10 : AOM_BITS_8;
// Monochrome is not allowed in profile 1.
const unsigned int max_monochrome = (cfg.g_profile == 1) ? 0u : 1u;
for (cfg.monochrome = 0; cfg.monochrome <= max_monochrome;
++cfg.monochrome) {
ASSERT_EQ(
aom_codec_enc_init(&enc, aom_codec_av1_cx(), &cfg,
high_bit_depth ? AOM_CODEC_USE_HIGHBITDEPTH : 0),
AOM_CODEC_OK)
<< " high_bit_depth: " << high_bit_depth;
for (const auto uv_stride : kUVStrides) {
const UVStride orig = { img.stride[AOM_PLANE_U],
img.stride[AOM_PLANE_V] };
img.stride[AOM_PLANE_U] = uv_stride.u_stride;
img.stride[AOM_PLANE_V] =
(img_fmt == AOM_IMG_FMT_NV12) ? 0 : uv_stride.v_stride;
img.monochrome = cfg.monochrome;
// Monochrome should ignore the U and V planes and NV12 only sets one
// stride value, they should always succeed.
const aom_codec_err_t expected_err =
(cfg.monochrome || img_fmt == AOM_IMG_FMT_NV12)
? AOM_CODEC_OK
: AOM_CODEC_INVALID_PARAM;
EXPECT_EQ(aom_codec_encode(&enc, &img, /*pts=*/0, /*duration=*/1,
/*flags=*/0),
expected_err)
<< "Error: " << aom_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[AOM_PLANE_U] = orig.u_stride;
img.stride[AOM_PLANE_V] = orig.v_stride;
EXPECT_EQ(aom_codec_encode(&enc, &img, /*pts=*/0, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK)
<< "Error: " << aom_codec_error_detail(&enc)
<< ", format: " << img_fmt << ", U stride: " << orig.u_stride
<< ", V stride: " << orig.v_stride;
}
EXPECT_EQ(
aom_codec_encode(&enc, /*img=*/nullptr, /*pts=*/0, /*duration=*/0,
/*flags=*/0),
AOM_CODEC_OK);
EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
}
}
#if CONFIG_AV1_HIGHBITDEPTH
TEST(EncodeAPI, InvalidInputRange) {
constexpr int kWidth = 64;
constexpr int kHeight = 64;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(aom_codec_av1_cx(), &cfg,
/*usage=*/AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = kWidth;
cfg.g_h = kHeight;
std::unique_ptr<aom_image_t, decltype(&aom_img_free)> image(
aom_img_alloc(nullptr, AOM_IMG_FMT_I42016, cfg.g_w, cfg.g_h, 0),
&aom_img_free);
aom_image_t *img = image.get();
ASSERT_NE(img, nullptr);
for (const auto bitdepth : { AOM_BITS_8, AOM_BITS_10, AOM_BITS_12 }) {
cfg.g_profile = (bitdepth == AOM_BITS_12) ? 2 : 0;
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 : { AOM_PLANE_Y, AOM_PLANE_U, AOM_PLANE_V }) {
const int width = aom_img_plane_width(img, plane);
const int height = aom_img_plane_height(img, plane);
uint8_t *p = img->planes[plane];
for (int y = 0; y < height; ++y) {
aom_memset16(p, val, width);
p += img->stride[plane];
}
}
for (cfg.monochrome = 0; cfg.monochrome <= 1; ++cfg.monochrome) {
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, aom_codec_av1_cx(), &cfg,
AOM_CODEC_USE_HIGHBITDEPTH),
AOM_CODEC_OK);
for (int check_input_range = 0; check_input_range <= 1;
++check_input_range) {
uint8_t *u = img->planes[AOM_PLANE_U];
uint8_t *v = img->planes[AOM_PLANE_V];
// Applications may not set img->monochrome. Leave it set to 0, but
// clear the planes to ensure the encoder properly ignores their
// values.
if (cfg.monochrome) {
img->planes[AOM_PLANE_U] = img->planes[AOM_PLANE_V] = nullptr;
}
EXPECT_EQ(aom_codec_control(&enc, AOME_SET_VALIDATE_HBD_INPUT,
check_input_range),
AOM_CODEC_OK);
EXPECT_EQ(aom_codec_encode(&enc, img, /*pts=*/0, /*duration=*/1,
/*flags=*/0),
check_input_range ? AOM_CODEC_INVALID_PARAM : AOM_CODEC_OK)
<< "Error: " << aom_codec_error_detail(&enc)
<< ", bitdepth: " << bitdepth << ", monochrome: " << img->monochrome
<< ", check_input_range: " << check_input_range;
img->planes[AOM_PLANE_U] = u;
img->planes[AOM_PLANE_V] = v;
}
EXPECT_EQ(
aom_codec_encode(&enc, /*img=*/nullptr, /*pts=*/0, /*duration=*/0,
/*flags=*/0),
AOM_CODEC_OK);
EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
}
}
#endif // CONFIG_AV1_HIGHBITDEPTH
void EncodeSetSFrameOnFirstFrame(aom_img_fmt fmt, aom_codec_flags_t flag) {
constexpr int kWidth = 2;
constexpr int kHeight = 128;
unsigned char kBuffer[kWidth * kHeight * 3] = { 0 };
aom_image_t img;
ASSERT_EQ(aom_img_wrap(&img, fmt, kWidth, kHeight, 1, kBuffer), &img);
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
cfg.g_w = kWidth;
cfg.g_h = kHeight;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, flag), AOM_CODEC_OK);
// One of these aom_codec_encode() calls should fail.
if (aom_codec_encode(&enc, &img, 0, 1, AOM_EFLAG_SET_S_FRAME) ==
AOM_CODEC_OK) {
EXPECT_NE(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
}
EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, SetSFrameOnFirstFrame) {
EncodeSetSFrameOnFirstFrame(AOM_IMG_FMT_I420, 0);
}
#if CONFIG_AV1_HIGHBITDEPTH
TEST(EncodeAPI, SetSFrameOnFirstFrameHighbd) {
EncodeSetSFrameOnFirstFrame(AOM_IMG_FMT_I42016, AOM_CODEC_USE_HIGHBITDEPTH);
}
#endif // CONFIG_AV1_HIGHBITDEPTH
TEST(EncodeAPI, MonochromeInProfiles) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(iface, &cfg, kUsage));
cfg.g_w = 128;
cfg.g_h = 128;
cfg.monochrome = 1;
aom_codec_ctx_t enc;
// Test Profile 0
cfg.g_profile = 0;
ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
// Test Profile 1
cfg.g_profile = 1;
ASSERT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
// Test Profile 3
cfg.g_profile = 2;
ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
}
TEST(EncodeAPI, LowBDEncoderLowBDImage) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 0);
ASSERT_NE(image, nullptr);
// Set the image to two colors so that av1_set_screen_content_options() will
// call av1_get_perpixel_variance().
int luma_value = 0;
for (unsigned int i = 0; i < image->d_h; ++i) {
memset(image->planes[0] + i * image->stride[0], luma_value, image->d_w);
luma_value = 255 - luma_value;
}
unsigned int uv_h = (image->d_h + 1) / 2;
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);
}
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, HighBDEncoderHighBDImage) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
aom_codec_ctx_t enc;
aom_codec_err_t init_status =
aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH);
#if !CONFIG_AV1_HIGHBITDEPTH
ASSERT_EQ(init_status, AOM_CODEC_INCAPABLE);
#else
ASSERT_EQ(init_status, AOM_CODEC_OK);
aom_image_t *image =
aom_img_alloc(nullptr, AOM_IMG_FMT_I42016, cfg.g_w, cfg.g_h, 0);
ASSERT_NE(image, nullptr);
// Set the image to two colors so that av1_set_screen_content_options() will
// call av1_get_perpixel_variance().
int luma_value = 0;
for (unsigned int i = 0; i < image->d_h; ++i) {
Memset16(image->planes[0] + i * image->stride[0], luma_value, image->d_w);
luma_value = 255 - luma_value;
}
unsigned int uv_h = (image->d_h + 1) / 2;
unsigned int uv_w = (image->d_w + 1) / 2;
for (unsigned int i = 0; i < uv_h; ++i) {
Memset16(image->planes[1] + i * image->stride[1], 128, uv_w);
Memset16(image->planes[2] + i * image->stride[2], 128, uv_w);
}
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
#endif
}
TEST(EncodeAPI, HighBDEncoderLowBDImage) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
aom_codec_ctx_t enc;
aom_codec_err_t init_status =
aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH);
#if !CONFIG_AV1_HIGHBITDEPTH
ASSERT_EQ(init_status, AOM_CODEC_INCAPABLE);
#else
ASSERT_EQ(init_status, AOM_CODEC_OK);
aom_image_t *image =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 0);
ASSERT_NE(image, nullptr);
// Set the image to two colors so that av1_set_screen_content_options() will
// call av1_get_perpixel_variance().
int luma_value = 0;
for (unsigned int i = 0; i < image->d_h; ++i) {
memset(image->planes[0] + i * image->stride[0], luma_value, image->d_w);
luma_value = 255 - luma_value;
}
unsigned int uv_h = (image->d_h + 1) / 2;
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);
}
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_INVALID_PARAM);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
#endif
}
TEST(EncodeAPI, LowBDEncoderHighBDImage) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image =
aom_img_alloc(nullptr, AOM_IMG_FMT_I42016, cfg.g_w, cfg.g_h, 0);
ASSERT_NE(image, nullptr);
// Set the image to two colors so that av1_set_screen_content_options() will
// call av1_get_perpixel_variance().
int luma_value = 0;
for (unsigned int i = 0; i < image->d_h; ++i) {
Memset16(image->planes[0] + i * image->stride[0], luma_value, image->d_w);
luma_value = 255 - luma_value;
}
unsigned int uv_h = (image->d_h + 1) / 2;
unsigned int uv_w = (image->d_w + 1) / 2;
for (unsigned int i = 0; i < uv_h; ++i) {
Memset16(image->planes[1] + i * image->stride[1], 128, uv_w);
Memset16(image->planes[2] + i * image->stride[2], 128, uv_w);
}
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_INVALID_PARAM);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
aom_image_t *CreateGrayImage(aom_img_fmt_t fmt, unsigned int w,
unsigned int h) {
aom_image_t *const image = aom_img_alloc(nullptr, fmt, w, h, 1);
if (!image) return image;
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);
}
return image;
}
TEST(EncodeAPI, Buganizer310548198) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
const unsigned int usage = AOM_USAGE_REALTIME;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
cfg.g_w = 1;
cfg.g_h = 444;
cfg.g_pass = AOM_RC_ONE_PASS;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
const int speed = 6;
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
const aom_enc_frame_flags_t flags = 0;
int frame_index = 0;
// Encode a frame.
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
ASSERT_EQ(aom_codec_encode(&enc, image, frame_index, 1, flags), AOM_CODEC_OK);
frame_index++;
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
}
aom_img_free(image);
cfg.g_w = 1;
cfg.g_h = 254;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK)
<< aom_codec_error_detail(&enc);
cfg.g_w = 1;
cfg.g_h = 154;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK)
<< aom_codec_error_detail(&enc);
// Encode a frame.
image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_EQ(aom_codec_encode(&enc, image, frame_index, 1, flags), AOM_CODEC_OK);
frame_index++;
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
}
aom_img_free(image);
// Flush the encoder.
bool got_data;
do {
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
got_data = false;
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
got_data = true;
}
} while (got_data);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
// Test for OSS-Fuzz Issue 559019046. The bug triggers when we increase the
// number of tiles after encoding a non-intra frame and then encode a key frame.
// Based on Yuan Tong's example in
// https://github.com/AOMediaCodec/libavif/pull/1069#discussion_r4090282167:
// magick -size 128x1024 xc:gray -alpha set -channel A -evaluate set 60%
// +channel flat.png
// avifenc -s 6 -j 8 -k 2 flat.png flat.png --tilerowslog2:u 2 flat.png
// -o flat.avif
TEST(EncodeAPI, Issue559019046) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
const unsigned int usage = AOM_USAGE_GOOD_QUALITY;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
cfg.g_threads = 8;
cfg.g_w = 128;
cfg.g_h = 1024;
cfg.g_lag_in_frames = 0;
cfg.kf_max_dist = 2;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
const int speed = 6;
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
const aom_enc_frame_flags_t flags = 0;
int frame_index = 0;
// Encode a key frame.
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
ASSERT_EQ(aom_codec_encode(&enc, image, frame_index, 1, flags), AOM_CODEC_OK);
frame_index++;
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
ASSERT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
}
// Encode a non-intra frame.
ASSERT_EQ(aom_codec_encode(&enc, image, frame_index, 1, flags), AOM_CODEC_OK);
frame_index++;
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
ASSERT_EQ(
pkt->data.frame.flags & (AOM_FRAME_IS_KEY | AOM_FRAME_IS_INTRAONLY), 0);
}
// Increase the number of tiles.
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_ROWS, 2), AOM_CODEC_OK);
// Encode a key frame.
ASSERT_EQ(aom_codec_encode(&enc, image, frame_index, 1, flags), AOM_CODEC_OK);
frame_index++;
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
ASSERT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
}
aom_img_free(image);
// Flush the encoder.
bool got_data;
do {
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
got_data = false;
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
got_data = true;
}
} while (got_data);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif // !CONFIG_REALTIME_ONLY
// Emulates the WebCodecs VideoEncoder interface.
class AV1Encoder {
public:
explicit AV1Encoder(int speed) : speed_(speed) {}
~AV1Encoder();
void Configure(unsigned int threads, unsigned int width, unsigned int height,
aom_rc_mode end_usage, unsigned int usage);
void Encode(bool key_frame);
aom_codec_ctx_t *GetCodecCtx() { return &enc_; }
private:
// Flushes the encoder. Should be called after all the Encode() calls.
void Flush();
const int speed_;
bool initialized_ = false;
aom_codec_enc_cfg_t cfg_;
aom_codec_ctx_t enc_;
int frame_index_ = 0;
};
AV1Encoder::~AV1Encoder() {
if (initialized_) {
Flush();
EXPECT_EQ(aom_codec_destroy(&enc_), AOM_CODEC_OK);
}
}
void AV1Encoder::Configure(unsigned int threads, unsigned int width,
unsigned int height, aom_rc_mode end_usage,
unsigned int usage) {
if (!initialized_) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg_, usage), AOM_CODEC_OK);
cfg_.g_threads = threads;
cfg_.g_w = width;
cfg_.g_h = height;
cfg_.g_forced_max_frame_width = cfg_.g_w;
cfg_.g_forced_max_frame_height = cfg_.g_h;
cfg_.g_timebase.num = 1;
cfg_.g_timebase.den = 1000 * 1000; // microseconds
cfg_.g_pass = AOM_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(aom_codec_enc_init(&enc_, iface, &cfg_, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc_, AOME_SET_CPUUSED, speed_), AOM_CODEC_OK);
const int log2_threads =
(cfg_.g_threads == 0) ? 0 : static_cast<int>(std::log2(cfg_.g_threads));
int tile_columns_log2 = 0;
int tile_rows_log2 = 0;
switch (log2_threads) {
case 4:
tile_columns_log2 = 2;
tile_rows_log2 = 2;
break;
case 3:
tile_columns_log2 = 2;
tile_rows_log2 = 1;
break;
case 2:
tile_columns_log2 = 1;
tile_rows_log2 = 1;
break;
default: tile_columns_log2 = log2_threads;
}
ASSERT_EQ(
aom_codec_control(&enc_, AV1E_SET_TILE_COLUMNS, tile_columns_log2),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc_, AV1E_SET_TILE_ROWS, tile_rows_log2),
AOM_CODEC_OK);
initialized_ = true;
return;
}
ASSERT_EQ(usage, cfg_.g_usage);
cfg_.g_threads = threads;
cfg_.g_w = width;
cfg_.g_h = height;
cfg_.rc_end_usage = end_usage;
ASSERT_EQ(aom_codec_enc_config_set(&enc_, &cfg_), AOM_CODEC_OK)
<< aom_codec_error_detail(&enc_);
}
void AV1Encoder::Encode(bool key_frame) {
assert(initialized_);
// TODO(wtc): Support high bit depths and other YUV formats.
aom_image_t *const image =
CreateGrayImage(AOM_IMG_FMT_I420, cfg_.g_w, cfg_.g_h);
ASSERT_NE(image, nullptr);
const aom_enc_frame_flags_t flags = key_frame ? AOM_EFLAG_FORCE_KF : 0;
ASSERT_EQ(aom_codec_encode(&enc_, image, frame_index_, 1, flags),
AOM_CODEC_OK);
frame_index_++;
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc_, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
if (key_frame) {
ASSERT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
}
}
aom_img_free(image);
}
void AV1Encoder::Flush() {
bool got_data;
do {
ASSERT_EQ(aom_codec_encode(&enc_, nullptr, 0, 0, 0), AOM_CODEC_OK);
got_data = false;
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc_, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
got_data = true;
}
} while (got_data);
}
TEST(EncodeAPI, Buganizer314858909) {
AV1Encoder encoder(7);
encoder.Configure(6, 1582, 750, AOM_CBR, AOM_USAGE_REALTIME);
// Encode a frame.
encoder.Encode(false);
encoder.Configure(0, 1582, 23, AOM_CBR, AOM_USAGE_REALTIME);
// Encode a frame..
encoder.Encode(false);
encoder.Configure(16, 1542, 363, AOM_CBR, AOM_USAGE_REALTIME);
// Encode a frame..
encoder.Encode(false);
}
// Run this test to reproduce the bug in fuzz test: ASSERT: cpi->rec_sse !=
// UINT64_MAX in av1_rc_bits_per_mb.
TEST(EncodeAPI, Buganizer310766628) {
AV1Encoder encoder(7);
encoder.Configure(16, 759, 383, AOM_CBR, AOM_USAGE_REALTIME);
// Encode a frame.
encoder.Encode(false);
encoder.Configure(2, 759, 383, AOM_VBR, AOM_USAGE_REALTIME);
// Encode a frame. This will trigger the assertion failure.
encoder.Encode(false);
}
// This test covers a possible use case where the change of frame sizes and
// thread numbers happens before and after the first frame coding.
TEST(EncodeAPI, Buganizer310455204) {
AV1Encoder encoder(7);
encoder.Configure(0, 1915, 503, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Configure(4, 1, 1, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Configure(6, 559, 503, AOM_CBR, AOM_USAGE_REALTIME);
// Encode a frame.
encoder.Encode(false);
// Increase the number of threads.
encoder.Configure(16, 1915, 503, AOM_CBR, AOM_USAGE_REALTIME);
// Encode a frame.
encoder.Encode(false);
}
// Run this test to reproduce the bug in fuzz test: Float-cast-overflow in
// av1_rc_bits_per_mb.
TEST(EncodeAPI, Buganizer310457427) {
AV1Encoder encoder(7);
encoder.Configure(12, 896, 1076, AOM_CBR, AOM_USAGE_REALTIME);
encoder.Configure(6, 609, 1076, AOM_VBR, AOM_USAGE_REALTIME);
// Encode a frame.
encoder.Encode(false);
// Encode a frame. This will trigger the float-cast-overflow bug which was
// caused by division by zero.
encoder.Encode(false);
}
TEST(EncodeAPI, PtsSmallerThanInitialPts) {
// Initialize libaom encoder.
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 1280;
cfg.g_h = 720;
cfg.rc_target_bitrate = 1000;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Create input image.
aom_image_t *const image =
CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
// Encode frame.
ASSERT_EQ(aom_codec_encode(&enc, image, 12, 1, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, image, 13, 1, 0), AOM_CODEC_OK);
// pts (10) is smaller than the initial pts (12).
ASSERT_EQ(aom_codec_encode(&enc, image, 10, 1, 0), AOM_CODEC_INVALID_PARAM);
// Free resources.
aom_img_free(image);
aom_codec_destroy(&enc);
}
TEST(EncodeAPI, PtsOrDurationTooBig) {
// Initialize libaom encoder.
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 1280;
cfg.g_h = 720;
cfg.rc_target_bitrate = 1000;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Create input image.
aom_image_t *const image =
CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
// Encode frame.
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
// pts, when converted to ticks, is too big.
ASSERT_EQ(aom_codec_encode(&enc, image, INT64_MAX / 1000000 + 1, 1, 0),
AOM_CODEC_INVALID_PARAM);
#if ULONG_MAX > INT64_MAX
// duration is too big.
ASSERT_EQ(aom_codec_encode(&enc, image, 0, (1ul << 63), 0),
AOM_CODEC_INVALID_PARAM);
// pts + duration is too big.
ASSERT_EQ(aom_codec_encode(&enc, image, 1, INT64_MAX, 0),
AOM_CODEC_INVALID_PARAM);
#endif
// pts + duration, when converted to ticks, is too big.
#if ULONG_MAX > INT64_MAX
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 0x1c0a0a1a3232, 0),
AOM_CODEC_INVALID_PARAM);
#endif
ASSERT_EQ(aom_codec_encode(&enc, image, INT64_MAX / 1000000, 1, 0),
AOM_CODEC_INVALID_PARAM);
// Free resources.
aom_img_free(image);
aom_codec_destroy(&enc);
}
// Reproduces https://crbug.com/339877165.
TEST(EncodeAPI, Buganizer339877165) {
// Initialize libaom encoder.
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 2560;
cfg.g_h = 1600;
cfg.rc_target_bitrate = 231;
cfg.rc_end_usage = AOM_CBR;
cfg.g_threads = 8;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// From libaom_av1_encoder.cc in WebRTC.
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 11), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_CDEF, 1), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_TPL_MODEL, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_DELTAQ_MODE, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_ORDER_HINT, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, 3), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_MAX_INTRA_BITRATE_PCT, 300),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_COEFF_COST_UPD_FREQ, 3),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_MODE_COST_UPD_FREQ, 3),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_MV_COST_UPD_FREQ, 3),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_PALETTE, 1), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_ROWS, 1), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_COLUMNS, 2), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_OBMC, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_NOISE_SENSITIVITY, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_WARPED_MOTION, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_GLOBAL_MOTION, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_REF_FRAME_MVS, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SUPERBLOCK_SIZE,
AOM_SUPERBLOCK_SIZE_DYNAMIC),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_CFL_INTRA, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_SMOOTH_INTRA, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_ANGLE_DELTA, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_FILTER_INTRA, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_INTRA_DEFAULT_TX_ONLY, 1),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_DISABLE_TRELLIS_QUANT, 1),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_DIST_WTD_COMP, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_DIFF_WTD_COMP, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_DUAL_FILTER, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_INTERINTRA_COMP, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_INTERINTRA_WEDGE, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_INTRA_EDGE_FILTER, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_INTRABC, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_MASKED_COMP, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_PAETH_INTRA, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_QM, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_RECT_PARTITIONS, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_RESTORATION, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_SMOOTH_INTERINTRA, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_TX64, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_MAX_REFERENCE_FRAMES, 3),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 2;
svc_params.number_temporal_layers = 1;
svc_params.max_quantizers[0] = svc_params.max_quantizers[1] = 56;
svc_params.min_quantizers[0] = svc_params.min_quantizers[1] = 10;
svc_params.scaling_factor_num[0] = svc_params.scaling_factor_num[1] = 1;
svc_params.scaling_factor_den[0] = 2;
svc_params.scaling_factor_den[1] = 1;
svc_params.layer_target_bitrate[0] = cfg.rc_target_bitrate;
svc_params.framerate_factor[0] = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
aom_svc_layer_id_t layer_id = {};
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
aom_svc_ref_frame_config_t ref_frame_config = {};
ref_frame_config.refresh[0] = 1;
ASSERT_EQ(
aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_frame_config),
AOM_CODEC_OK);
// Create input image.
aom_image_t *const image =
CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
// Encode layer 0.
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
layer_id.spatial_layer_id = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ref_frame_config.refresh[0] = 0;
ASSERT_EQ(
aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_frame_config),
AOM_CODEC_OK);
// Encode layer 1.
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
// Free resources.
aom_img_free(image);
aom_codec_destroy(&enc);
}
TEST(EncodeAPI, AomediaIssue3509VbrMinSection2Percent) {
// Initialize libaom encoder.
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 1920;
cfg.g_h = 1080;
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
// av1_rc_update_framerate() if the multiplication is done in the `int` type.
cfg.rc_2pass_vbr_minsection_pct = 2;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Create input image.
aom_image_t *const image =
CreateGrayImage(AOM_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(aom_codec_encode(&enc, image, 0, /*duration=*/300, 0),
AOM_CODEC_OK);
// Free resources.
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, AomediaIssue3509VbrMinSection101Percent) {
// Initialize libaom encoder.
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 1920;
cfg.g_h = 1080;
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 av1_rc_update_framerate() if vbr_min_bits is not clamped
// to INT_MAX.
cfg.rc_2pass_vbr_minsection_pct = 101;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Create input image.
aom_image_t *const image =
CreateGrayImage(AOM_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(aom_codec_encode(&enc, image, 0, /*duration=*/300, 0),
AOM_CODEC_OK);
// Free resources.
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, Buganizer392929025) {
// Initialize libaom encoder.
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 16;
cfg.g_h = 16;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_MATRIX_COEFFICIENTS,
AOM_CICP_MC_IDENTITY),
AOM_CODEC_OK);
// Create input image.
aom_image_t *const image =
CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
// Encode frame.
// AOM_CICP_MC_IDENTITY requires subsampling to be 0.
EXPECT_EQ(
aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1, /*flags=*/0),
AOM_CODEC_INVALID_PARAM);
// Attempt to reconfigure with non-zero subsampling.
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_CHROMA_SUBSAMPLING_X, 1),
AOM_CODEC_INVALID_PARAM);
EXPECT_EQ(aom_codec_control(&enc, AV1E_SET_CHROMA_SUBSAMPLING_Y, 1),
AOM_CODEC_INVALID_PARAM);
// Free resources.
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
void ReproBuganizer487259772(const bool row_mt, int initial_threads = 2) {
AV1Encoder encoder(7);
encoder.Configure(initial_threads, 800, 600, AOM_VBR, AOM_USAGE_REALTIME);
// This is not exposed by the WebCodecs interface. It's set to 1 in Chrome's
// implementation.
ASSERT_EQ(aom_codec_control(encoder.GetCodecCtx(), AV1E_SET_ROW_MT, row_mt),
AOM_CODEC_OK);
encoder.Encode(false);
encoder.Encode(false);
encoder.Encode(false);
encoder.Configure(1, 352, 288, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Encode(false);
encoder.Encode(false);
encoder.Encode(false);
encoder.Encode(false);
encoder.Configure(1, 48, 480, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Encode(false);
encoder.Encode(true);
encoder.Encode(false);
encoder.Configure(1, 8, 8, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Encode(false);
encoder.Encode(false);
encoder.Configure(1, 24, 24, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Encode(false);
encoder.Encode(false);
encoder.Configure(1, 97, 53, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Encode(false);
encoder.Encode(false);
encoder.Configure(1, 32, 320, AOM_VBR, AOM_USAGE_REALTIME);
encoder.Encode(false);
encoder.Encode(false);
}
TEST(EncodeAPI, Buganizer487259772NoThreads) {
ReproBuganizer487259772(/*row_mt=*/false, /*initial_threads=*/1);
}
// TODO: bug 487259772 - Enable this test after assertion/crash (NULL mbmi) in
// av1_loopfilter is fixed.
TEST(EncodeAPI, DISABLED_Buganizer487259772NoRowMT) {
ReproBuganizer487259772(/*row_mt=*/false);
}
TEST(EncodeAPI, Buganizer487259772RowMT) {
ReproBuganizer487259772(/*row_mt=*/true);
}
class EncodeAPIParameterized
: public testing::TestWithParam<std::tuple<
/*usage=*/unsigned int, /*speed=*/int, /*aq_mode=*/unsigned int>> {};
// Encodes two frames at a given usage, speed, and aq_mode setting.
// Reproduces b/303023614
TEST_P(EncodeAPIParameterized, HighBDEncoderHighBDFrames) {
const unsigned int usage = std::get<0>(GetParam());
int speed = std::get<1>(GetParam());
if (speed == 10 && usage != AOM_USAGE_REALTIME) {
speed = 9; // 10 is only allowed in AOM_USAGE_REALTIME
}
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
cfg.g_w = 500;
cfg.g_h = 400;
aom_codec_ctx_t enc;
aom_codec_err_t init_status =
aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH);
#if !CONFIG_AV1_HIGHBITDEPTH
ASSERT_EQ(init_status, AOM_CODEC_INCAPABLE);
#else
ASSERT_EQ(init_status, AOM_CODEC_OK);
const unsigned int aq_mode = std::get<2>(GetParam());
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, aq_mode), AOM_CODEC_OK);
aom_image_t *image =
aom_img_alloc(nullptr, AOM_IMG_FMT_I42016, cfg.g_w, cfg.g_h, 0);
ASSERT_NE(image, nullptr);
for (unsigned int i = 0; i < image->d_h; ++i) {
Memset16(image->planes[0] + i * image->stride[0], 128, image->d_w);
}
unsigned int uv_h = (image->d_h + 1) / 2;
unsigned int uv_w = (image->d_w + 1) / 2;
for (unsigned int i = 0; i < uv_h; ++i) {
Memset16(image->planes[1] + i * image->stride[1], 128, uv_w);
Memset16(image->planes[2] + i * image->stride[2], 128, uv_w);
}
// Encode two frames.
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1, /*flags=*/0),
AOM_CODEC_OK);
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1, /*flags=*/0),
AOM_CODEC_OK);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
#endif
}
const unsigned int kUsages[] = {
AOM_USAGE_REALTIME,
#if !CONFIG_REALTIME_ONLY
AOM_USAGE_GOOD_QUALITY,
AOM_USAGE_ALL_INTRA,
#endif
};
INSTANTIATE_TEST_SUITE_P(All, EncodeAPIParameterized,
testing::Combine(
/*usage=*/testing::ValuesIn(kUsages),
/*speed=*/testing::Values(6, 7, 10),
/*aq_mode=*/testing::Values(0, 1, 2, 3)));
// Triggers the IntraBC motion search by using screen content tools to encode a
// key frame.
// Note: The speed must be low enough to run the IntraBC pixel search.
void IntraBCFrameSizeIncreaseTest(unsigned int usage, int speed) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
cfg.g_w = 64;
cfg.g_h = 16;
cfg.g_forced_max_frame_width = 64;
cfg.g_forced_max_frame_height = 64;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 1;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
AOM_CODEC_OK);
aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 16, 1);
ASSERT_NE(img_small, nullptr);
FillImageRandom(img_small);
aom_image_t *img_large = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1);
ASSERT_NE(img_large, nullptr);
FillImageRandom(img_large);
EncodeOne(&enc, img_small, 0);
// Only the height grows, so the reconstructed frame keeps its stride
// (SS_CFG_SRC) while the current lookahead source buffer is reallocated in
// av1_lookahead_push() with AOM_BORDER_IN_PIXELS (160) instead of
// oxcf->border_in_pixels (96), changing its stride (SS_CFG_LOOKAHEAD).
// Furthermore, for GOOD_QUALITY and REALTIME modes (even with
// lag_in_frames = 0), the lookahead ring buffer has 2 slots (max_pre_frames =
// 1): frame 0 was placed in buf[0], so frame 1 is placed in buf[1] while
// buf[0] retains the old stride.
cfg.g_h = 64;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
bool got_key_frame = false;
aom_codec_iter_t iter = nullptr;
while (const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter)) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT &&
(pkt->data.frame.flags & AOM_FRAME_IS_KEY)) {
got_key_frame = true;
}
}
EXPECT_TRUE(got_key_frame);
aom_img_free(img_small);
aom_img_free(img_large);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, IntraBCFrameSizeIncreaseGoodQuality) {
IntraBCFrameSizeIncreaseTest(AOM_USAGE_GOOD_QUALITY, 2);
}
TEST(EncodeAPI, IntraBCFrameSizeIncreaseAllIntra) {
IntraBCFrameSizeIncreaseTest(AOM_USAGE_ALL_INTRA, 5);
}
#endif // !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, IntraBCFrameSizeIncreaseRealtime) {
IntraBCFrameSizeIncreaseTest(AOM_USAGE_REALTIME, 7);
}
// Encodes a narrow key frame after a wide one with screen content tools in
// realtime mode. The source buffer of the narrow frame has a smaller stride
// than the buffer of the wide frame, so the IntraBC motion search must not use
// search sites built for the wider stride. The assertion in
// av1_full_pixel_search() checks that the strides match.
TEST(EncodeAPI, IntraBCFrameSizeDecreaseRealtime) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 1024;
cfg.g_h = 512;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 1;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
AOM_CODEC_OK);
aom_image_t *img_wide =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 1024, 512, 1);
ASSERT_NE(img_wide, nullptr);
FillImageRandom(img_wide);
aom_image_t *img_narrow =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 32, 512, 1);
ASSERT_NE(img_narrow, nullptr);
FillImageRandom(img_narrow);
EncodeOne(&enc, img_wide, 0);
cfg.g_w = 32;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img_narrow, 1, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
}
aom_img_free(img_wide);
aom_img_free(img_narrow);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#if CONFIG_AV1_DECODER
// Draws a text like screen texture moved left by offset_x pixels.
void FillPannedScreen(aom_image_t *img, int offset_x) {
for (int plane = 0; plane < 3; ++plane) {
const int ss = plane ? 1 : 0;
const int w = static_cast<int>(img->d_w + ss) >> ss;
const int h = static_cast<int>(img->d_h + ss) >> ss;
for (int y = 0; y < h; ++y) {
uint8_t *row = img->planes[plane] + y * img->stride[plane];
for (int x = 0; x < w; ++x) {
const int px = (x << ss) + offset_x;
const int py = y << ss;
const uint32_t cell = static_cast<uint32_t>(px >> 4) * 73856093u ^
static_cast<uint32_t>(py >> 4) * 19349663u;
const int bg = 40 + static_cast<int>(cell % 160);
const bool glyph = (px * 7 + py * 3) % 11 < 3 && ((px ^ py) & 4);
const int v = glyph ? 255 - bg : bg;
row[x] = plane ? static_cast<uint8_t>(128 + ((v - 128) >> 2))
: static_cast<uint8_t>(v);
}
}
}
}
// Encodes an IntraBC key frame with 2 threads, then pans the content so the
// inter frames predict from past the frame edge. The loop filters are off on
// IntraBC frames, so the encoder must still extend the key frame borders for
// its reconstruction to match the decoder.
TEST(EncodeAPI, IntraBCMultiThreadBorderExtension) {
constexpr int kWidth = 320;
constexpr int kHeight = 192;
constexpr int kFrames = 5;
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = kWidth;
cfg.g_h = kHeight;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 2;
cfg.rc_min_quantizer = 8;
cfg.rc_max_quantizer = 40;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 11), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_INTRABC, 1), AOM_CODEC_OK);
// Decode to 8-bit buffers like the encoder reconstruction, even when
// FORCE_HIGHBITDEPTH_DECODING is on.
aom_codec_dec_cfg_t dec_cfg = {};
dec_cfg.allow_lowbitdepth = 1;
aom_codec_ctx_t dec;
ASSERT_EQ(aom_codec_dec_init(&dec, aom_codec_av1_dx(), &dec_cfg, 0),
AOM_CODEC_OK);
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, kWidth, kHeight, 1);
ASSERT_NE(img, nullptr);
for (int i = 0; i < kFrames; ++i) {
FillPannedScreen(img, i * 3);
ASSERT_EQ(aom_codec_encode(&enc, img, i, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter)) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) continue;
ASSERT_EQ(aom_codec_decode(
&dec, static_cast<const uint8_t *>(pkt->data.frame.buf),
pkt->data.frame.sz, nullptr),
AOM_CODEC_OK);
aom_codec_iter_t dec_iter = nullptr;
const aom_image_t *decoded = aom_codec_get_frame(&dec, &dec_iter);
ASSERT_NE(decoded, nullptr);
aom_image_t recon;
ASSERT_EQ(aom_codec_control(&enc, AV1_GET_NEW_FRAME_IMAGE, &recon),
AOM_CODEC_OK);
::libaom_test::MD5 recon_md5;
recon_md5.Add(&recon);
::libaom_test::MD5 decoded_md5;
decoded_md5.Add(decoded);
EXPECT_STREQ(recon_md5.Get(), decoded_md5.Get()) << "frame " << i;
}
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&dec), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Encodes a key frame with screen content tools in realtime mode. A 64x64 patch
// of two color noise at (src_x, src_y) is copied to (dst_x, dst_y), so the
// IntraBC hash search finds a match that far away. A DV of 2048 pixels or more
// can't be coded, so the decoder must still accept the frame.
void IntraBCFarCopyTest(int width, int height, int src_x, int src_y, int dst_x,
int dst_y) {
constexpr int kPatchSize = 64;
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = width;
cfg.g_h = height;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 1;
cfg.rc_min_quantizer = 10;
cfg.rc_max_quantizer = 10;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 11), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_INTRABC, 1), AOM_CODEC_OK);
aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, width, height, 1);
ASSERT_NE(img, nullptr);
memset(img->planes[0], 128, img->stride[0] * height);
memset(img->planes[1], 128, img->stride[1] * ((height + 1) / 2));
memset(img->planes[2], 128, img->stride[2] * ((height + 1) / 2));
::libaom_test::ACMRandom rnd;
rnd.Reset(::libaom_test::ACMRandom::DeterministicSeed());
for (int r = 0; r < kPatchSize; ++r) {
uint8_t *src = img->planes[0] + (src_y + r) * img->stride[0] + src_x;
uint8_t *dst = img->planes[0] + (dst_y + r) * img->stride[0] + dst_x;
for (int c = 0; c < kPatchSize; ++c) {
src[c] = (rnd.Rand8() & 1) ? 16 : 235;
}
memcpy(dst, src, kPatchSize);
}
aom_codec_ctx_t dec;
ASSERT_EQ(aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img, 0, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter)) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) continue;
EXPECT_EQ(aom_codec_decode(
&dec, static_cast<const uint8_t *>(pkt->data.frame.buf),
pkt->data.frame.sz, nullptr),
AOM_CODEC_OK)
<< aom_codec_error_detail(&dec);
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&dec), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// The match is 2048 pixels to the left.
TEST(EncodeAPI, IntraBCFarCopyLeftRealtime) {
IntraBCFarCopyTest(2112, 64, 0, 0, 2048, 0);
}
// The match is 2048 pixels above.
TEST(EncodeAPI, IntraBCFarCopyAboveRealtime) {
IntraBCFarCopyTest(64, 2112, 0, 0, 0, 2048);
}
// The match is 2056 pixels to the right and 512 pixels above. The difference
// from the reference DV is past the end of the DV cost table, which ASan
// reports as a heap buffer overflow.
TEST(EncodeAPI, IntraBCFarCopyAboveRightRealtime) {
IntraBCFarCopyTest(2120, 576, 2056, 0, 0, 512);
}
#endif // CONFIG_AV1_DECODER
#if !CONFIG_REALTIME_ONLY
// Tests that search_site_cfg[SS_CFG_LOOKAHEAD] in init_motion_estimation() is
// updated using the current frame's unscaled_source->y_stride rather than
// lookahead->buf[0].img.y_stride. In TPL's motion_estimation() (tpl_model.c),
// when the reference buffer is an unscaled lookahead source buffer whose stride
// differs from the reconstructed frame stride (SS_CFG_SRC), it directly selects
// search_site_cfg[SS_CFG_LOOKAHEAD] and asserts that search_site_cfg->stride ==
// ref_stride (without calling av1_get_search_site_config()).
TEST(EncodeAPI, TplSubsamplingChangeGoodQuality) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 64;
cfg.g_h = 64;
cfg.g_lag_in_frames = 4;
cfg.g_threads = 1;
cfg.monochrome = 1;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 5), AOM_CODEC_OK);
// Disable temporal filtering so init_gop_frames_for_tpl() uses the lookahead
// buffers (&buf->img, y_stride = 384) directly as tpl_frame->gf_picture
// rather than replacing them with tf_buf (y_stride = 256 == SS_CFG_SRC).
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_ARNR_MAXFRAMES, 0), AOM_CODEC_OK);
aom_image_t *img_420 = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1);
ASSERT_NE(img_420, nullptr);
FillImageRandom(img_420);
aom_image_t *img_444 = aom_img_alloc(nullptr, AOM_IMG_FMT_I444, 64, 64, 1);
ASSERT_NE(img_444, nullptr);
FillImageRandom(img_444);
// Frame 0 (I420) goes into lookahead->buf[0] with border_in_pixels = 96
// (y_stride = 256), and is then flushed.
ASSERT_EQ(aom_codec_encode(&enc, img_420, 0, 1, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
{
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
}
}
// Frames 1..4 (I444) go into lookahead->buf[1..4]. Because monochrome = 1,
// changing subsampling from I420 to I444 is valid across frames and triggers
// new_dimensions in av1_lookahead_push(), reallocating buf[1..4] with
// AOM_BORDER_IN_PIXELS = 160 (y_stride = 384) while buf[0] keeps
// y_stride = 256.
// Because av1_tpl_setup_stats() runs before av1_encode() updates
// cpi->unscaled_source, frame 1 is encoded first (as a 1-frame GOP) to update
// cpi->unscaled_source to buf[1] (y_stride = 384). Forcing a key frame on
// frame 2 starts a new GOP at frame 2 so init_motion_estimation() and TPL run
// on frames 2..4 (in slots buf[2..4]).
ASSERT_EQ(aom_codec_encode(&enc, img_444, 1, 1, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img_444, 2, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img_444, 3, 1, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img_444, 4, 1, 0), AOM_CODEC_OK);
// Each flush (nullptr) call drains 1 frame from the lookahead buffer, so 4
// calls are needed for frames 1..4.
for (int i = 0; i < 4; ++i) {
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
}
}
aom_img_free(img_420);
aom_img_free(img_444);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, AllIntraMode) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
EXPECT_EQ(AOM_CODEC_OK,
aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0));
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
// Set g_lag_in_frames to a nonzero value. This should cause
// aom_codec_enc_init() to fail.
EXPECT_EQ(AOM_CODEC_OK,
aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA));
cfg.g_lag_in_frames = 1;
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
// Set kf_max_dist to a nonzero value. This should cause aom_codec_enc_init()
// to fail.
EXPECT_EQ(AOM_CODEC_OK,
aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA));
cfg.kf_max_dist = 1;
EXPECT_EQ(AOM_CODEC_INVALID_PARAM, aom_codec_enc_init(&enc, iface, &cfg, 0));
}
TEST(EncodeAPI, AllIntraAndUsePsnr) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
AOM_CODEC_OK);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_PSNR),
AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) {
ASSERT_EQ(pkt->kind, AOM_CODEC_PSNR_PKT);
}
}
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, AllIntraAndTuneIq) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
AOM_CODEC_OK);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_TUNING, AOM_TUNE_SSIMULACRA2),
AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
pkt = aom_codec_get_cx_data(&enc, &iter);
ASSERT_NE(pkt, nullptr);
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
pkt = aom_codec_get_cx_data(&enc, &iter);
ASSERT_EQ(pkt, nullptr);
// Flush the encoder.
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
iter = nullptr;
pkt = aom_codec_get_cx_data(&enc, &iter);
ASSERT_EQ(pkt, nullptr);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// A test that reproduces bug aomedia:3534.
TEST(EncodeAPI, AllIntraAndNoRefLast) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
AOM_CODEC_OK);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, AOM_EFLAG_NO_REF_LAST),
AOM_CODEC_OK);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Fills |img| (8-bit 4:2:0) with frame |frame_index| of a video that zooms in
// on a fixed picture of random 3x3 blocks: frame i shows the picture magnified
// by a factor of (32 + i) / 32, with the top-left corner fixed. Global motion
// can model the zoom between frames.
void FillZoomingFrame(aom_image_t *img, int frame_index) {
// Small blocks give global motion estimation plenty of corners to track.
constexpr int kBlockSize = 3;
const int w = static_cast<int>(img->d_w);
const int h = static_cast<int>(img->d_h);
const int blocks_w = (w + kBlockSize - 1) / kBlockSize;
const int blocks_h = (h + kBlockSize - 1) / kBlockSize;
// One random value per block. ACMRandom uses a fixed seed, so every frame
// gets the same picture.
::libaom_test::ACMRandom rnd;
std::vector<uint8_t> blocks(blocks_w * blocks_h);
for (uint8_t &block : blocks) block = rnd.Rand8();
const int magnification = 32 + frame_index; // In units of 1/32.
for (int r = 0; r < h; ++r) {
uint8_t *const row =
img->planes[AOM_PLANE_Y] + r * img->stride[AOM_PLANE_Y];
for (int c = 0; c < w; ++c) {
// Pixel (c, r) of the frame shows pixel (x, y) of the picture.
const int x = c * 32 / magnification;
const int y = r * 32 / magnification;
row[c] = blocks[(y / kBlockSize) * blocks_w + x / kBlockSize];
}
}
const int uv_w = (w + 1) / 2;
const int uv_h = (h + 1) / 2;
for (int r = 0; r < uv_h; ++r) {
memset(img->planes[AOM_PLANE_U] + r * img->stride[AOM_PLANE_U], 128, uv_w);
memset(img->planes[AOM_PLANE_V] + r * img->stride[AOM_PLANE_V], 128, uv_w);
}
}
// Encodes 5 frames of zooming content in good-quality mode with no lag and
// stores the compressed data in |data|. Before frame i is encoded,
// AV1E_SET_ENABLE_GLOBAL_MOTION is set to each value in
// enable_global_motion[i], in order. If |large_scale_tile_on_first_frame| is
// true, large_scale_tile is set to 1 for the first frame and to 0 after it.
void EncodeZoomingFrames(
const std::vector<std::vector<int>> &enable_global_motion,
std::vector<uint8_t> *data, bool large_scale_tile_on_first_frame = false) {
constexpr int kNumFrames = 5;
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 128;
cfg.g_h = 128;
cfg.g_lag_in_frames = 0;
cfg.large_scale_tile = large_scale_tile_on_first_frame;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 5), AOM_CODEC_OK);
aom_image_t *const image =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(image, nullptr);
data->clear();
for (int i = 0; i < kNumFrames; ++i) {
if (i == 1 && large_scale_tile_on_first_frame) {
cfg.large_scale_tile = 0;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
}
if (i < static_cast<int>(enable_global_motion.size())) {
for (const int enable : enable_global_motion[i]) {
ASSERT_EQ(
aom_codec_control(&enc, AV1E_SET_ENABLE_GLOBAL_MOTION, enable),
AOM_CODEC_OK);
}
}
FillZoomingFrame(image, i);
ASSERT_EQ(aom_codec_encode(&enc, image, i, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
const aom_codec_cx_pkt_t *pkt;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
const uint8_t *const buf =
static_cast<const uint8_t *>(pkt->data.frame.buf);
data->insert(data->end(), buf, buf + pkt->data.frame.sz);
}
}
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Global motion needs an image pyramid in every source and reference frame
// buffer, but pyramids are only allocated while global motion is enabled. So
// global motion can only be enabled before the first frame is passed to
// aom_codec_encode(); later requests to enable it are ignored. Such requests
// used to cause an assertion failure in av1_encode_frame() in debug builds and
// a crash in global motion search in release builds.
//
// The tests below compare the output of encoders that make the same sequence
// of control calls, so that the only difference is the global motion setting.
TEST(EncodeAPI, EnableGlobalMotionBeforeFirstFrame) {
std::vector<uint8_t> on, off_on, off;
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 1, 1 } }, &on));
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 0, 1 } }, &off_on));
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 0, 0 } }, &off));
// Global motion makes a difference for this content.
EXPECT_NE(on, off);
// Global motion can be enabled (again) before the first frame.
EXPECT_EQ(off_on, on);
}
TEST(EncodeAPI, EnableGlobalMotionAfterFirstFrame) {
std::vector<uint8_t> off, off_on;
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 0 }, { 0 } }, &off));
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 0 }, { 1 } }, &off_on));
// The request to enable global motion after the first frame is ignored.
EXPECT_EQ(off_on, off);
}
TEST(EncodeAPI, DisableGlobalMotionAfterFirstFrame) {
std::vector<uint8_t> off, on_off;
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 0 }, { 0 } }, &off));
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames({ { 1 }, { 0 } }, &on_off));
// Global motion can be disabled after the first frame. The first frame is a
// key frame, which does not use global motion, so this is equivalent to
// disabling global motion before the first frame.
EXPECT_EQ(on_off, off);
std::vector<uint8_t> on_off_off, on_off_on;
ASSERT_NO_FATAL_FAILURE(
EncodeZoomingFrames({ { 1 }, { 0 }, { 0 } }, &on_off_off));
ASSERT_NO_FATAL_FAILURE(
EncodeZoomingFrames({ { 1 }, { 0 }, { 1 } }, &on_off_on));
// Once disabled, global motion cannot be enabled again.
EXPECT_EQ(on_off_on, on_off_off);
}
// large_scale_tile = 1 forces global motion off, so setting large_scale_tile to
// 0 after the first frame implicitly requests global motion to be enabled. That
// request is ignored, too.
TEST(EncodeAPI, DisableLargeScaleTileAfterFirstFrame) {
std::vector<uint8_t> on, off;
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames(
{ { 1 } }, &on, /*large_scale_tile_on_first_frame=*/true));
ASSERT_NO_FATAL_FAILURE(EncodeZoomingFrames(
{ { 0 } }, &off, /*large_scale_tile_on_first_frame=*/true));
EXPECT_EQ(on, off);
}
#endif // !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, PerFramePsnr) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
ASSERT_EQ(cfg.g_lag_in_frames, 0);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
aom_enc_frame_flags_t psnr_flags = AOM_EFLAG_CALCULATE_PSNR;
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1, psnr_flags),
AOM_CODEC_OK);
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
bool had_psnr = false;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) {
ASSERT_EQ(pkt->kind, AOM_CODEC_PSNR_PKT);
had_psnr = true;
}
}
EXPECT_TRUE(had_psnr);
aom_enc_frame_flags_t no_psnr_flags = 0;
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1, no_psnr_flags),
AOM_CODEC_OK);
iter = nullptr;
had_psnr = false;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) {
ASSERT_EQ(pkt->kind, AOM_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
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, PerFramePsnrGoodQualityZeroLagInFrames) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
ASSERT_NE(cfg.g_lag_in_frames, 0);
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
aom_enc_frame_flags_t psnr_flags = AOM_EFLAG_CALCULATE_PSNR;
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1, psnr_flags),
AOM_CODEC_OK);
const aom_codec_cx_pkt_t *pkt;
aom_codec_iter_t iter = nullptr;
bool had_psnr = false;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) {
ASSERT_EQ(pkt->kind, AOM_CODEC_PSNR_PKT);
had_psnr = true;
}
}
EXPECT_TRUE(had_psnr);
aom_enc_frame_flags_t no_psnr_flags = 0;
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1, no_psnr_flags),
AOM_CODEC_OK);
iter = nullptr;
had_psnr = false;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) {
ASSERT_EQ(pkt->kind, AOM_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
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, PerFramePsnrNotSupportedWithLagInFrames) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
ASSERT_NE(cfg.g_lag_in_frames, 0);
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
aom_enc_frame_flags_t psnr_flags = AOM_EFLAG_CALCULATE_PSNR;
ASSERT_EQ(
aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1, psnr_flags),
AOM_CODEC_INCAPABLE);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif // !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, FreezeInternalState) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
cfg.g_w = 176;
cfg.g_h = 144;
cfg.rc_target_bitrate = 200;
cfg.g_lag_in_frames = 0; // Needed for single frame updates
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_PSNR),
AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
// Encode Frame A (Keyframe)
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1,
/*flags=*/AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
// Drain packets
}
std::vector<uint8_t> bitstream1;
bool psnr1 = false;
// Encode Frame B with freeze flag
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1,
/*flags=*/AOM_EFLAG_FREEZE_INTERNAL_STATE |
AOM_EFLAG_CALCULATE_PSNR),
AOM_CODEC_OK);
iter = nullptr;
const aom_codec_cx_pkt_t *pkt;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
bitstream1.assign((uint8_t *)pkt->data.frame.buf,
(uint8_t *)pkt->data.frame.buf + pkt->data.frame.sz);
} else if (pkt->kind == AOM_CODEC_PSNR_PKT) {
psnr1 = true;
}
}
EXPECT_TRUE(psnr1);
EXPECT_FALSE(bitstream1.empty());
std::vector<uint8_t> bitstream2;
bool psnr2 = false;
// Encode Frame B again without freeze flag
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1,
/*flags=*/AOM_EFLAG_CALCULATE_PSNR),
AOM_CODEC_OK);
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
bitstream2.assign((uint8_t *)pkt->data.frame.buf,
(uint8_t *)pkt->data.frame.buf + pkt->data.frame.sz);
} else if (pkt->kind == AOM_CODEC_PSNR_PKT) {
psnr2 = true;
}
}
EXPECT_TRUE(psnr2);
EXPECT_FALSE(bitstream2.empty());
// Bitstreams should be identical
EXPECT_EQ(bitstream1, bitstream2);
// Encode one more frame for good measure.
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/2, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
// Drain packets
}
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, FreezeInternalStateSVC) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 176;
cfg.g_h = 144;
cfg.rc_target_bitrate = 300;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_CBR;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_PSNR),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK);
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 2;
svc_params.number_temporal_layers = 1;
svc_params.max_quantizers[0] = 56;
svc_params.min_quantizers[0] = 10;
svc_params.max_quantizers[1] = 56;
svc_params.min_quantizers[1] = 10;
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 2;
svc_params.scaling_factor_num[1] = 1;
svc_params.scaling_factor_den[1] = 1;
svc_params.layer_target_bitrate[0] = cfg.rc_target_bitrate * 2 / 3;
svc_params.layer_target_bitrate[1] = cfg.rc_target_bitrate;
svc_params.framerate_factor[0] = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
aom_svc_layer_id_t layer_id = {};
aom_svc_ref_frame_config_t ref_frame_config = {};
// Encode SL0 - Keyframe
layer_id.spatial_layer_id = 0;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1,
/*flags=*/AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
} // Drain
// Encode SL1 - Delta Frame with Freeze
layer_id.spatial_layer_id = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ref_frame_config.refresh[0] = 1;
ref_frame_config.reference[0] = 1;
ref_frame_config.ref_idx[0] = 0;
ASSERT_EQ(
aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_frame_config),
AOM_CODEC_OK);
std::vector<uint8_t> bitstream1;
bool psnr1 = false;
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1,
/*flags=*/AOM_EFLAG_FREEZE_INTERNAL_STATE |
AOM_EFLAG_CALCULATE_PSNR),
AOM_CODEC_OK);
iter = nullptr;
const aom_codec_cx_pkt_t *pkt;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
bitstream1.assign((uint8_t *)pkt->data.frame.buf,
(uint8_t *)pkt->data.frame.buf + pkt->data.frame.sz);
} else if (pkt->kind == AOM_CODEC_PSNR_PKT) {
psnr1 = true;
}
}
EXPECT_TRUE(psnr1);
EXPECT_FALSE(bitstream1.empty());
// Encode SL1 - Delta Frame again without Freeze
std::vector<uint8_t> bitstream2;
bool psnr2 = false;
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1,
/*flags=*/AOM_EFLAG_CALCULATE_PSNR),
AOM_CODEC_OK);
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
bitstream2.assign((uint8_t *)pkt->data.frame.buf,
(uint8_t *)pkt->data.frame.buf + pkt->data.frame.sz);
} else if (pkt->kind == AOM_CODEC_PSNR_PKT) {
psnr2 = true;
}
}
EXPECT_TRUE(psnr2);
EXPECT_FALSE(bitstream2.empty());
EXPECT_EQ(bitstream1, bitstream2);
// Encode one more frame for good measure.
layer_id.spatial_layer_id = 0;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ref_frame_config.refresh[0] = 1;
ref_frame_config.reference[0] = 1;
ref_frame_config.ref_idx[0] = 0;
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/2, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
// Drain packets
}
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, FreezeInternalStateTemporalLayers) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 176;
cfg.g_h = 144;
cfg.rc_target_bitrate = 300;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_CBR;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_PSNR),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK);
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 1;
svc_params.number_temporal_layers = 2;
svc_params.max_quantizers[0] = 56;
svc_params.min_quantizers[0] = 10;
svc_params.max_quantizers[1] = 56;
svc_params.min_quantizers[1] = 10;
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 1;
svc_params.layer_target_bitrate[0] = cfg.rc_target_bitrate * 2 / 3;
svc_params.layer_target_bitrate[1] = cfg.rc_target_bitrate;
svc_params.framerate_factor[0] = 1;
svc_params.framerate_factor[1] = 2;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
aom_svc_layer_id_t layer_id = {};
aom_svc_ref_frame_config_t ref_frame_config = {};
// Encode a Keyframe.
layer_id.spatial_layer_id = 0;
layer_id.temporal_layer_id = 0;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/0, /*duration=*/1,
/*flags=*/AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
} // Drain
// Encode a TL1 - Delta Frame
layer_id.temporal_layer_id = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ref_frame_config.refresh[0] = 0;
ref_frame_config.reference[0] = 1;
ref_frame_config.ref_idx[0] = 0;
ASSERT_EQ(
aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_frame_config),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/1, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
} // Drain
// Encode a TL0 - Delta Frame, with frozen state.
layer_id.temporal_layer_id = 0;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ref_frame_config.refresh[0] = 1;
ref_frame_config.reference[0] = 1;
ref_frame_config.ref_idx[0] = 0;
std::vector<uint8_t> bitstream1;
bool psnr1 = false;
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/2, /*duration=*/1,
/*flags=*/AOM_EFLAG_FREEZE_INTERNAL_STATE |
AOM_EFLAG_CALCULATE_PSNR),
AOM_CODEC_OK);
iter = nullptr;
const aom_codec_cx_pkt_t *pkt;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
bitstream1.assign((uint8_t *)pkt->data.frame.buf,
(uint8_t *)pkt->data.frame.buf + pkt->data.frame.sz);
} else if (pkt->kind == AOM_CODEC_PSNR_PKT) {
psnr1 = true;
}
}
EXPECT_TRUE(psnr1);
EXPECT_FALSE(bitstream1.empty());
// Encode TL1 - Delta Frame again without frozen state.
std::vector<uint8_t> bitstream2;
bool psnr2 = false;
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/2, /*duration=*/1,
/*flags=*/AOM_EFLAG_CALCULATE_PSNR),
AOM_CODEC_OK);
iter = nullptr;
while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
bitstream2.assign((uint8_t *)pkt->data.frame.buf,
(uint8_t *)pkt->data.frame.buf + pkt->data.frame.sz);
} else if (pkt->kind == AOM_CODEC_PSNR_PKT) {
psnr2 = true;
}
}
EXPECT_TRUE(psnr2);
EXPECT_FALSE(bitstream2.empty());
EXPECT_EQ(bitstream1, bitstream2);
// Encode one more frame for good measure.
layer_id.temporal_layer_id = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ref_frame_config.refresh[0] = 0;
ref_frame_config.reference[0] = 1;
ref_frame_config.ref_idx[0] = 0;
ASSERT_EQ(aom_codec_encode(&enc, image, /*pts=*/3, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
// Drain packets
}
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, FreezeInternalStateNotAllowedWithNonZeroLag) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, kUsage), AOM_CODEC_OK);
cfg.g_w = 176;
cfg.g_h = 144;
cfg.rc_target_bitrate = 200;
cfg.g_lag_in_frames =
1; // Not supported with AOM_EFLAG_FREEZE_INTERNAL_STATE.
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *image = CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
ASSERT_NE(image, nullptr);
ASSERT_EQ(aom_codec_encode(
&enc, image, /*pts=*/0, /*duration=*/1,
/*flags=*/AOM_EFLAG_FREEZE_INTERNAL_STATE | AOM_EFLAG_FORCE_KF),
AOM_CODEC_INCAPABLE);
aom_img_free(image);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
// This test is based on the issue:449376308. The segfault in
// av1_update_layer_context_change_config() is triggered
// by doing svc with nonzero lag_in_frames and good_quality usage.
// Note good_quality mode is needed because for realtime mode lag_in_frames
// is forced to 0 in set_encoder_config() .
TEST(EncodeAPI, Issue449376308) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30;
cfg.rc_target_bitrate = 1000;
cfg.g_lag_in_frames = 25;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// AV1E_SET_SVC_PARAMS
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 3;
svc_params.number_temporal_layers = 2;
for (int i = 0; i < AOM_MAX_LAYERS; i++) {
svc_params.max_quantizers[i] = 30;
svc_params.min_quantizers[i] = 0;
svc_params.layer_target_bitrate[i] = 1000;
}
for (int i = 0; i < AOM_MAX_SS_LAYERS; i++) {
svc_params.scaling_factor_num[i] = 1;
svc_params.scaling_factor_den[i] = 1;
}
for (int i = 0; i < AOM_MAX_TS_LAYERS; i++) {
svc_params.framerate_factor[i] = 1;
}
// set_svc_params should fail since lag_in_frames > 0.
EXPECT_NE(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif
// This test is based on the issue:449341177. The issue occurs in the
// codec_destroy after invalid params (quantizer out of range) are passed
// to the set_svc_params control. The issue can occur for realtime mode
// with lag_in_frames = 0.
TEST(EncodeAPI, Issue449341177) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.rc_target_bitrate = 500;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30;
cfg.rc_end_usage = AOM_CBR;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// AV1E_SET_SVC_PARAMS
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 3;
svc_params.number_temporal_layers = 2;
for (int i = 0; i < AOM_MAX_LAYERS; i++) {
// Set quantizer out of range.
svc_params.max_quantizers[i] = 80;
svc_params.min_quantizers[i] = 0;
svc_params.layer_target_bitrate[i] = 1000;
}
for (int i = 0; i < AOM_MAX_SS_LAYERS; i++) {
svc_params.scaling_factor_num[i] = 1;
svc_params.scaling_factor_den[i] = 1;
}
for (int i = 0; i < AOM_MAX_TS_LAYERS; i++) {
svc_params.framerate_factor[i] = 1;
}
// set_svc_params should fail because svc_params.max_quantizer[i] is set out
// of range.
EXPECT_NE(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// This test is based on the issue:471095598. For profile 2 444 12 bit,
// assert is triggered on first frame in intra_mode_search.c, the function
// av1_count_colors_highbd, for good quality mode.
// Disabled until the assert issue is fixed.
TEST(EncodeAPI, DISABLED_Issue471095598) {
aom_codec_iface_t *encoder_iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(
aom_codec_enc_config_default(encoder_iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_profile = 2;
cfg.g_bit_depth = AOM_BITS_12;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t encoder;
ASSERT_EQ(aom_codec_enc_init(&encoder, encoder_iface, &cfg,
AOM_CODEC_USE_HIGHBITDEPTH),
AOM_CODEC_OK);
aom_image_t *img = CreateGrayImage(AOM_IMG_FMT_I44416, cfg.g_w, cfg.g_h);
ASSERT_EQ(aom_codec_encode(&encoder, img, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&encoder), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
class GetGopInfoTest : public ::libaom_test::EncoderTest,
public ::testing::Test {
protected:
GetGopInfoTest() : EncoderTest(&::libaom_test::kAV1) {}
~GetGopInfoTest() override = default;
void SetUp() override {
InitializeConfig(::libaom_test::kTwoPassGood);
cfg_.g_w = 176;
cfg_.g_h = 144;
cfg_.rc_target_bitrate = 200;
cfg_.g_lag_in_frames = 25;
cfg_.g_limit = kFrameLimit;
}
void PreEncodeFrameHook(::libaom_test::VideoSource *video,
::libaom_test::Encoder *encoder) override {
if (video->frame() == 0) {
encoder->Control(AOME_SET_CPUUSED, 3);
}
}
void PostEncodeFrameHook(::libaom_test::Encoder *encoder) override {
if (cfg_.g_pass == AOM_RC_FIRST_PASS) return;
encoder->Control(AV1E_GET_GOP_INFO, &gop_info_);
}
void FramePktHook(const aom_codec_cx_pkt_t * /*pkt*/) override {
// This is verified here (not in PostEncodeFrameHook) because
// PostEncodeFrameHook is also called when encoder is reading frames into
// lookahead buffer, when GOP structure hasn't been determined.
ASSERT_GT(gop_info_.gop_size, 0);
}
aom_gop_info_t gop_info_;
static constexpr int kFrameLimit = 10;
};
TEST_F(GetGopInfoTest, GetGopInfo) {
::libaom_test::RandomVideoSource video;
video.SetSize(cfg_.g_w, cfg_.g_h);
video.set_limit(kFrameLimit);
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
}
#endif // !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, SizeAlignOverflow) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 16;
cfg.g_h = 16;
cfg.g_threads = 1;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Bug aomdiea:480978101: size_align=32 causes w,h=32 while d_w,d_h=16
// This mismatch causes buffer overflow in av1_copy_and_extend_frame()
// if the fix is not present.
aom_image_t *img =
aom_img_alloc_with_border(NULL, AOM_IMG_FMT_NV12, /*d_w=*/16, /*d_h=*/16,
/*align=*/32,
/*size_align=*/32,
/*border=*/15);
ASSERT_NE(img, nullptr);
memset(img->img_data, 128, img->sz);
// Should not crash with heap-buffer-overflow
EXPECT_EQ(aom_codec_encode(&enc, img, /*pts=*/0, /*duration=*/1, /*flags=*/0),
AOM_CODEC_OK);
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, DynamicSvcAq0Issue499606109) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
// Init at 1920x1080.
cfg.g_w = 1920;
cfg.g_h = 1080;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 2000;
cfg.g_lag_in_frames = 0;
cfg.g_error_resilient = 1;
cfg.g_threads = 1;
cfg.kf_max_dist = 3000;
cfg.kf_min_dist = 3000;
cfg.rc_dropframe_thresh = 30;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 10), AOM_CODEC_OK);
ASSERT_EQ(
aom_codec_control(&codec, AV1E_SET_MAX_CONSEC_FRAME_DROP_MS_CBR, 34),
AOM_CODEC_OK);
// Phase 0: shrink to 640x360, 2 SLs. Frame 0 allocates
// source_last_TL0 at 640x360 (small buffer).
cfg.g_w = 640;
cfg.g_h = 360;
cfg.rc_target_bitrate = 500;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
{
const int num[] = { 1, 1 }, den[] = { 1, 1 }, br[] = { 200, 300 };
SetSvc(&codec, 2, num, den, br);
}
aom_codec_pts_t pts = 0;
aom_image_t *raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 640, 360, 1);
ASSERT_NE(raw, nullptr);
for (int f = 0; f < 3; ++f) {
for (int sl = 0; sl < 2; ++sl) {
aom_svc_layer_id_t lid = { sl, 0 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &lid),
AOM_CODEC_OK);
FillImage(raw, static_cast<uint8_t>(128 + f));
EncodeOne(&codec, raw, pts++);
}
}
aom_img_free(raw);
// Phase 1: grow to 1920x1080, 3 SLs. SL2 encoding sets
// mi_cols_full_resoln = 480, mi_rows_full_resoln = 270.
cfg.g_w = 1920;
cfg.g_h = 1080;
cfg.rc_target_bitrate = 2000;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
{
const int num[] = { 1, 2, 1 }, den[] = { 3, 3, 1 },
br[] = { 200, 500, 1300 };
SetSvc(&codec, 3, num, den, br);
}
raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 1920, 1080, 1);
ASSERT_NE(raw, nullptr);
for (int f = 0; f < 3; ++f) {
for (int sl = 0; sl < 3; ++sl) {
aom_svc_layer_id_t lid = { sl, 0 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &lid),
AOM_CODEC_OK);
FillImage(raw, static_cast<uint8_t>(64 + f));
EncodeOne(&codec, raw, pts++);
}
}
aom_img_free(raw);
// Phase 2: back to 640x360, 2 SLs. Encode SL0 only with mi_cols
// stays stale. Very low SL0 bitrate forces a frame drop.
// After the drop, next SL0 gets last_source = source_last_TL0
// (640x360 small buffer). SAD loop overruns it with OOB. */
cfg.g_w = 640;
cfg.g_h = 360;
cfg.rc_target_bitrate = 500;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
{
const int num[] = { 1, 1 }, den[] = { 1, 1 }, br[] = { 1, 499 };
SetSvc(&codec, 2, num, den, br);
}
raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 640, 360, 1);
ASSERT_NE(raw, nullptr);
for (int f = 0; f < 20; ++f) {
aom_svc_layer_id_t lid = { 0, 0 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &lid),
AOM_CODEC_OK);
FillImage(raw, static_cast<uint8_t>((f % 2 == 0) ? 16 : 235));
EncodeOne(&codec, raw, pts++);
}
aom_img_free(raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
TEST(EncodeAPI, DynamicSvcAq3Issue499606109) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
// Init at 1920x1080.
cfg.g_w = 1920;
cfg.g_h = 1080;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 2000;
cfg.g_lag_in_frames = 0;
cfg.g_error_resilient = 1;
cfg.g_threads = 1;
cfg.kf_max_dist = 3000;
cfg.kf_min_dist = 3000;
cfg.rc_dropframe_thresh = 30;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 10), AOM_CODEC_OK);
ASSERT_EQ(
aom_codec_control(&codec, AV1E_SET_MAX_CONSEC_FRAME_DROP_MS_CBR, 34),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_AQ_MODE, 3), AOM_CODEC_OK);
// Phase 0: shrink to 640x360, 2 SLs. Frame 0 allocates
// source_last_TL0 at 640x360 (small buffer).
cfg.g_w = 640;
cfg.g_h = 360;
cfg.rc_target_bitrate = 500;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
{
const int num[] = { 1, 1 }, den[] = { 1, 1 }, br[] = { 200, 300 };
SetSvc(&codec, 2, num, den, br);
}
aom_codec_pts_t pts = 0;
aom_image_t *raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 640, 360, 1);
ASSERT_NE(raw, nullptr);
for (int f = 0; f < 3; ++f) {
for (int sl = 0; sl < 2; ++sl) {
aom_svc_layer_id_t lid = { sl, 0 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &lid),
AOM_CODEC_OK);
FillImage(raw, static_cast<uint8_t>(128 + f));
EncodeOne(&codec, raw, pts++);
}
}
aom_img_free(raw);
// Phase 1: grow to 1920x1080, 3 SLs. SL2 encoding sets
// mi_cols_full_resoln = 480, mi_rows_full_resoln = 270.
cfg.g_w = 1920;
cfg.g_h = 1080;
cfg.rc_target_bitrate = 2000;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
{
const int num[] = { 1, 2, 1 }, den[] = { 3, 3, 1 },
br[] = { 200, 500, 1300 };
SetSvc(&codec, 3, num, den, br);
}
raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 1920, 1080, 1);
ASSERT_NE(raw, nullptr);
for (int f = 0; f < 3; ++f) {
for (int sl = 0; sl < 3; ++sl) {
aom_svc_layer_id_t lid = { sl, 0 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &lid),
AOM_CODEC_OK);
FillImage(raw, static_cast<uint8_t>(64 + f));
EncodeOne(&codec, raw, pts++);
}
}
aom_img_free(raw);
// Phase 2: back to 640x360, 2 SLs. Encode SL0 only with mi_cols
// stays stale. Very low SL0 bitrate forces a frame drop.
// After the drop, next SL0 gets last_source = source_last_TL0
// (640x360 small buffer). SAD loop overruns it with OOB. */
cfg.g_w = 640;
cfg.g_h = 360;
cfg.rc_target_bitrate = 500;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
{
const int num[] = { 1, 1 }, den[] = { 1, 1 }, br[] = { 1, 499 };
SetSvc(&codec, 2, num, den, br);
}
raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 640, 360, 1);
ASSERT_NE(raw, nullptr);
for (int f = 0; f < 20; ++f) {
aom_svc_layer_id_t lid = { 0, 0 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &lid),
AOM_CODEC_OK);
FillImage(raw, static_cast<uint8_t>((f % 2 == 0) ? 16 : 235));
EncodeOne(&codec, raw, pts++);
}
aom_img_free(raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
TEST(EncodeAPI, DynamicSvcTemporalIssue502735235) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
// Init at 256x512.
cfg.g_w = 256;
cfg.g_h = 512;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 1000;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 10), AOM_CODEC_OK);
// AV1E_SET_SVC_PARAMS
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 1;
svc_params.number_temporal_layers = 2;
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 1;
svc_params.framerate_factor[0] = 2;
svc_params.framerate_factor[1] = 1;
svc_params.max_quantizers[0] = 56;
svc_params.min_quantizers[0] = 10;
svc_params.max_quantizers[1] = 56;
svc_params.min_quantizers[1] = 10;
svc_params.layer_target_bitrate[0] = cfg.rc_target_bitrate * 60 / 100;
svc_params.layer_target_bitrate[1] = cfg.rc_target_bitrate;
EXPECT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
// Encode at 256x512. TL0.
aom_svc_layer_id_t layer_id = {};
layer_id.spatial_layer_id = 0;
layer_id.temporal_layer_id = 0;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
aom_image_t *raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 256, 512, 1);
ASSERT_NE(raw, nullptr);
FillImage(raw, 128);
ASSERT_EQ(aom_codec_encode(&codec, raw, /*pts=*/0, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
aom_img_free(raw);
// Encode at 256x64 TL1, twice, set keyframe on first one, delta
// on second.
cfg.g_w = 256;
cfg.g_h = 64;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
layer_id.spatial_layer_id = 0;
layer_id.temporal_layer_id = 1;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 256, 64, 1);
ASSERT_NE(raw, nullptr);
FillImage(raw, 128);
ASSERT_EQ(aom_codec_encode(&codec, raw, /*pts=*/1, /*duration=*/1,
/*flags=*/AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&codec, raw, /*pts=*/2, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
aom_img_free(raw);
// Encode TL0 back at original resolution 256x512.
cfg.g_w = 256;
cfg.g_h = 512;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
layer_id.spatial_layer_id = 0;
layer_id.temporal_layer_id = 0;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 256, 512, 1);
ASSERT_NE(raw, nullptr);
FillImage(raw, 128);
ASSERT_EQ(aom_codec_encode(&codec, raw, /*pts=*/3, /*duration=*/1,
/*flags=*/0),
AOM_CODEC_OK);
aom_img_free(raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
TEST(EncodeAPI, Buganizer503197490) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 640;
cfg.g_h = 360;
cfg.g_timebase.num = 570;
cfg.g_timebase.den = 30;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 1728882; // 1728882000 bits/s
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, Buganizer504317456) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 640;
cfg.g_h = 360;
cfg.g_lag_in_frames = 1;
cfg.g_error_resilient = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 640, 360, 1);
FillImage(img, 128);
ASSERT_NE(img, nullptr);
struct Frame {
int64_t pts;
unsigned long duration;
aom_enc_frame_flags_t flags;
} frames[] = { { 0, 2, 0 },
{ 1075, 9, AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_NO_UPD_GF },
{ 2099, 6, AOM_EFLAG_FORCE_KF },
{ 3035, 5, AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_FORCE_KF } };
for (const auto &f : frames) {
ASSERT_EQ(aom_codec_encode(&enc, img, f.pts, f.duration, f.flags),
AOM_CODEC_OK);
}
// Add 10+ additional frames to extend overflow and cause crash on destruction
for (int i = 0; i < 15; ++i) {
int64_t pts = 4000 + i * 100;
aom_enc_frame_flags_t flags = (i % 3 == 0) ? AOM_EFLAG_FORCE_KF : 0;
ASSERT_EQ(aom_codec_encode(&enc, img, pts, 5, flags), AOM_CODEC_OK);
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif // !CONFIG_REALTIME_ONLY
// Test for issue: 505976409.
TEST(EncodeAPI, SvcSourceLastTL0Uninitialized) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 64;
cfg.g_h = 64;
cfg.rc_end_usage = AOM_CBR;
cfg.g_lag_in_frames = 0;
cfg.rc_target_bitrate = 400;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
aom_svc_params_t svc = {};
svc.number_spatial_layers = 1;
svc.number_temporal_layers = 2;
svc.scaling_factor_num[0] = 1;
svc.scaling_factor_den[0] = 4;
for (int i = 0; i < 32; ++i) svc.layer_target_bitrate[i] = 200;
for (int i = 0; i < 8; ++i) svc.framerate_factor[i] = 1;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_PARAMS, &svc), AOM_CODEC_OK);
aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1);
ASSERT_NE(img, nullptr);
FillImage(img, 128);
// Frame 0: TL1 (Starts with non-zero temporal layer).
aom_svc_layer_id_t layer_id = { 0, 1 };
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
aom_svc_ref_frame_config_t ref_cfg = {};
for (int r = 0; r < 7; ++r) {
ref_cfg.reference[r] = 1;
ref_cfg.ref_idx[r] = 7;
}
ref_cfg.refresh[2] = 1;
ref_cfg.refresh[6] = 1;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_cfg),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&codec, img, 0, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
// Frame 1: TL0.
layer_id.temporal_layer_id = 0;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&codec, img, 1, 1, 0), AOM_CODEC_OK);
// Frame 2: TL1 (Triggers use of source_last_TL0).
layer_id.temporal_layer_id = 1;
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
// This frame used to crash or hang before the fix.
ASSERT_EQ(aom_codec_encode(&codec, img, 2, 1, 0), AOM_CODEC_OK);
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
TEST(EncodeAPI, Buganizer503810640) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 176;
cfg.g_h = 144;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_DENOISE_NOISE_LEVEL, 29),
AOM_CODEC_OK);
aom_image_t *raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 176, 144, 1);
ASSERT_NE(raw, nullptr);
FillImage(raw, 128);
::libaom_test::ACMRandom rnd;
rnd.Reset(::libaom_test::ACMRandom::DeterministicSeed());
for (int y = 0; y < 144; ++y) {
for (int x = 0; x < 176; ++x) {
raw->planes[AOM_PLANE_Y][y * raw->stride[AOM_PLANE_Y] + x] = rnd.Rand8();
}
}
ASSERT_EQ(aom_codec_encode(&codec, raw, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
TEST(EncodeAPI, Buganizer503810640V2) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 437;
cfg.g_h = 1121;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_DENOISE_NOISE_LEVEL, 29),
AOM_CODEC_OK);
aom_image_t *raw = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 437, 1121, 1);
ASSERT_NE(raw, nullptr);
FillImageRandom(raw);
ASSERT_EQ(aom_codec_encode(&codec, raw, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, Buganizer558434716) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 128;
cfg.g_h = 96;
cfg.g_forced_max_frame_width = 1920;
cfg.g_forced_max_frame_height = 1080;
cfg.g_pass = AOM_RC_ONE_PASS;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 1;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 128, 96, 1);
ASSERT_NE(img_small, nullptr);
FillImage(img_small, 128);
aom_image_t *img_large =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 320, 240, 1);
ASSERT_NE(img_large, nullptr);
FillImage(img_large, 128);
// Frame 0 at 128x96
ASSERT_EQ(aom_codec_encode(&enc, img_small, 0, 1, 0), AOM_CODEC_OK);
// Switch to larger resolution
cfg.g_w = 320;
cfg.g_h = 240;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
// Frame 1 at 320x240
ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
// Switch back to smaller resolution
cfg.g_w = 128;
cfg.g_h = 96;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
// Frame 2 at 128x96 (keyframe)
ASSERT_EQ(aom_codec_encode(&enc, img_small, 2, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
// Frames 3+ at 128x96 (interframes referencing frame 2)
for (int i = 3; i < 8; ++i) {
ASSERT_EQ(aom_codec_encode(&enc, img_small, i, 1, 0), AOM_CODEC_OK);
}
aom_img_free(img_small);
aom_img_free(img_large);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, PerceptualAIDynamicResolutionChange) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
AOM_CODEC_OK);
cfg.g_w = 17;
cfg.g_h = 1;
cfg.g_forced_max_frame_width = 320;
cfg.g_forced_max_frame_height = 240;
cfg.g_pass = AOM_RC_ONE_PASS;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 1;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_DELTAQ_MODE, 3), AOM_CODEC_OK);
aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 17, 1, 1);
ASSERT_NE(img_small, nullptr);
FillImage(img_small, 128);
aom_image_t *img_large =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 320, 240, 1);
ASSERT_NE(img_large, nullptr);
FillImage(img_large, 128);
// Frame 0 at 17x1
ASSERT_EQ(aom_codec_encode(&enc, img_small, 0, 1, 0), AOM_CODEC_OK);
// Switch to larger resolution
cfg.g_w = 320;
cfg.g_h = 240;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
// Frame 1 at 320x240
ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, 0), AOM_CODEC_OK);
// Switch back to 17x1
cfg.g_w = 17;
cfg.g_h = 1;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
// Frame 2 at 17x1
ASSERT_EQ(aom_codec_encode(&enc, img_small, 2, 1, 0), AOM_CODEC_OK);
aom_img_free(img_small);
aom_img_free(img_large);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Test for OSS-Fuzz Issue 559079132: Integer-overflow in
// av1_caq_select_segment. When COMPLEXITY_AQ (aq_mode 2) is enabled with a high
// target bitrate, sb64_target_rate is large, causing signed integer overflow
// when multiplying sb64_target_rate * xmis * ymis in 32-bit arithmetic and when
// casting target_rate to int.
TEST(EncodeAPI, Issue559079132) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 64;
cfg.g_h = 64;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 1;
cfg.rc_target_bitrate = 2000000;
cfg.rc_end_usage = AOM_CBR;
cfg.g_lag_in_frames = 0;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, 2), AOM_CODEC_OK);
aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1);
ASSERT_NE(img, nullptr);
FillImageRandom(img);
EncodeOne(&enc, img, 0);
// Flush encoder.
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif // !CONFIG_REALTIME_ONLY
// Tests for OSS-Fuzz Issues 558463888, 559075253, 559225640:
// When dynamically reducing threads with multiple tiles and row_mt=0, the
// primary worker pool size (mt_info->num_workers) remains at the higher
// capacity allocated previously while the module workers for the encode stage
// (mt_info->num_mod_workers[MOD_ENC]) is reduced. If encode_frame_internal()
// uses mt_info->num_workers instead of mt_info->num_mod_workers[MOD_ENC], it
// can erroneously call av1_encode_tiles_mt() or stride with the wrong worker
// count, causing tiles to be skipped and leading to a crash in bitstream
// packing.
void TestDynamicThreadReductionWithTiles(int tile_rows, int tile_columns,
int init_threads, int max_threads) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_ctx_t enc;
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 256;
cfg.g_h = 256;
// Allow dynamic resolution changes up to 512x512.
cfg.g_forced_max_frame_width = 512;
cfg.g_forced_max_frame_height = 512;
cfg.g_threads = init_threads;
cfg.g_lag_in_frames = 0;
cfg.g_error_resilient = 1;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
// Disable row-mt and configure tiling.
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ROW_MT, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_ROWS, tile_rows),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_COLUMNS, tile_columns),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_ENABLEAUTOALTREF, 0),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, 3), AOM_CODEC_OK);
aom_image_t *img_256 = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 256, 256, 1);
ASSERT_NE(img_256, nullptr);
FillImageRandom(img_256);
aom_image_t *img_512 = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 512, 512, 1);
ASSERT_NE(img_512, nullptr);
FillImageRandom(img_512);
// Frame 0: Encode at 256x256 with init_threads.
EncodeOne(&enc, img_256, 0);
// Frame 1: Switch to 512x512 with max_threads. This allocates max_threads
// workers in the primary worker pool (mt_info->num_workers = max_threads).
cfg.g_w = 512;
cfg.g_h = 512;
cfg.g_threads = max_threads;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
EncodeOne(&enc, img_512, 1);
// Frame 2: Switch back to 256x256 with init_threads. The worker pool retains
// mt_info->num_workers = max_threads, but mt_info->num_mod_workers[MOD_ENC]
// is init_threads. This verifies that tile encoding properly handles the
// reduced worker count without skipping tiles.
cfg.g_w = 256;
cfg.g_h = 256;
cfg.g_threads = init_threads;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
EncodeOne(&enc, img_256, 2);
// Flush encoder.
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
}
aom_img_free(img_256);
aom_img_free(img_512);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
TEST(EncodeAPI, Issue558463888) {
TestDynamicThreadReductionWithTiles(/*tile_rows=*/1, /*tile_columns=*/0,
/*init_threads=*/1, /*max_threads=*/2);
}
TEST(EncodeAPI, Issue559075253) {
TestDynamicThreadReductionWithTiles(/*tile_rows=*/1, /*tile_columns=*/1,
/*init_threads=*/2, /*max_threads=*/4);
}
TEST(EncodeAPI, Issue559225640) {
TestDynamicThreadReductionWithTiles(/*tile_rows=*/0, /*tile_columns=*/1,
/*init_threads=*/1, /*max_threads=*/4);
}
#if !CONFIG_REALTIME_ONLY
TEST(EncodeAPI, Buganizer558446054) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
AOM_CODEC_OK);
cfg.g_w = 65;
cfg.g_h = 33;
cfg.g_threads = 0;
cfg.g_lag_in_frames = 0;
cfg.g_pass = AOM_RC_ONE_PASS;
cfg.rc_end_usage = AOM_Q;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 1), AOM_CODEC_OK);
aom_image_t raw;
ASSERT_NE(aom_img_alloc(&raw, AOM_IMG_FMT_I420, 65, 33, 1), nullptr);
FillImageRandom(&raw);
ASSERT_EQ(aom_codec_encode(&codec, &raw, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(&raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
TEST(EncodeAPI, Buganizer558463892_558589747) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA),
AOM_CODEC_OK);
cfg.g_w = 64;
cfg.g_h = 64;
cfg.g_forced_max_frame_width = 1024;
cfg.g_forced_max_frame_height = 1024;
cfg.g_threads = 0;
cfg.g_lag_in_frames = 0;
cfg.g_pass = AOM_RC_ONE_PASS;
cfg.rc_end_usage = AOM_Q;
aom_codec_ctx_t codec;
ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_DELTAQ_MODE, 3), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 0), AOM_CODEC_OK);
// Encode frame 0 at 64x64
aom_image_t raw;
ASSERT_NE(aom_img_alloc(&raw, AOM_IMG_FMT_I420, 64, 64, 1), nullptr);
FillImageRandom(&raw);
ASSERT_EQ(aom_codec_encode(&codec, &raw, 0, 1, 0), AOM_CODEC_OK);
aom_img_free(&raw);
// Change resolution to 256x256 mid-stream
cfg.g_w = 256;
cfg.g_h = 256;
ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK);
// Encode frame 1 at 256x256
ASSERT_NE(aom_img_alloc(&raw, AOM_IMG_FMT_I420, 256, 256, 1), nullptr);
FillImageRandom(&raw);
ASSERT_EQ(aom_codec_encode(&codec, &raw, 1, 1, 0), AOM_CODEC_OK);
aom_img_free(&raw);
ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
}
// Regression test for b/565488030: Heap-buffer-overflow in
// save_deblock_boundary_lines when Frame 0 is coded losslessly (q = 0, setting
// cm->features.all_lossless = 1) or with large_scale_tile = 1 at a small
// resolution and Frame 1 increases the resolution via
// aom_codec_enc_config_set() with large_scale_tile = 0 and lossy coding (q > 0,
// cm->features.all_lossless = 0) with loop restoration enabled.
TEST(EncodeAPI, Buganizer565488030) {
for (unsigned int threads : { 1u, 4u }) {
for (unsigned int large_scale_tile : { 0u, 1u }) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(
aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
AOM_CODEC_OK);
cfg.g_w = 4;
cfg.g_h = 4;
cfg.g_forced_max_frame_width = 256;
cfg.g_forced_max_frame_height = 256;
cfg.g_threads = threads;
cfg.g_lag_in_frames = 0;
cfg.large_scale_tile = large_scale_tile;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 3999;
cfg.rc_min_quantizer = 0;
cfg.rc_max_quantizer = 63;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 4), AOM_CODEC_OK);
// large_scale_tile = 1 implicitly disables global motion, so switching
// it to 0 below would turn global motion on mid-stream. The reference
// frames coded before that have no image pyramid, which trips
// assert(buf->buf.y_pyramid) in av1_encode_frame(). That is a separate
// issue, so keep global motion off to focus on loop restoration.
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_GLOBAL_MOTION, 0),
AOM_CODEC_OK);
aom_image_t *img_small =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 4, 4, 16);
ASSERT_NE(img_small, nullptr);
FillImage(img_small, 128);
// Frame 0: 4x4 at high CBR bitrate picks q = 0 (all_lossless = 1).
EncodeOne(&enc, img_small, 0);
aom_img_free(img_small);
// Frame 1: Reconfigure to 256x256 with large_scale_tile = 0 (picks q > 0,
// all_lossless = 0).
cfg.g_w = 256;
cfg.g_h = 256;
cfg.large_scale_tile = 0;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
aom_image_t *img_large =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 256, 256, 16);
ASSERT_NE(img_large, nullptr);
FillImage(img_large, 128);
EncodeOne(&enc, img_large, 1);
aom_img_free(img_large);
// Flush encoder.
ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(&enc, &iter) != nullptr) {
}
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
}
}
#endif // !CONFIG_REALTIME_ONLY
// Regression test for b/568565869: Undefined left-shift overflow of
// this_rdc->rate in av1_block_yrd() when encoding a large partition in
// realtime lossless mode.
TEST(EncodeAPI, NonrdBlockYrdRateOverflow) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 128;
cfg.g_h = 128;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 10), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_LOSSLESS, 1), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SUPERBLOCK_SIZE,
AOM_SUPERBLOCK_SIZE_128X128),
AOM_CODEC_OK);
aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 128, 128, 16);
ASSERT_NE(img, nullptr);
FillImage(img, 0);
EncodeOne(&enc, img, 0);
// Use a 253/255 checkerboard in Frame 1 against all-zero Frame 0:
// - The residual variance around the mean (254) is tiny (1), so
// variance-based partitioning does not split and evaluates a large
// partition.
// - The large DC residual (~254) combined with non-zero AC (ncoeffs > 1)
// causes aom_satd_lp(low_qcoeff) in av1_block_yrd() to accumulate a rate
// above INT_MAX >> 11, which would overflow 32-bit int when shifted left
// by 2 + AV1_PROB_COST_SHIFT (11).
for (int r = 0; r < 128; ++r) {
for (int c = 0; c < 128; ++c) {
img->planes[AOM_PLANE_Y][r * img->stride[AOM_PLANE_Y] + c] =
((r ^ c) & 1) ? 255 : 253;
}
}
EncodeOne(&enc, img, 1);
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Regression test for webrtc:496266459: Enabling compound prediction via
// AV1E_SET_SVC_REF_FRAME_COMP_PRED must not affect encoding of frames that do
// not have any active compound reference pair (e.g., single-reference delta
// frames referencing only LAST_FRAME).
TEST(EncodeAPI, SvcCompPredDoesNotAffectSingleRefFrames) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_Q;
aom_codec_ctx_t enc_no_comp;
aom_codec_ctx_t enc_with_comp;
ASSERT_EQ(aom_codec_enc_init(&enc_no_comp, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_enc_init(&enc_with_comp, iface, &cfg, 0), AOM_CODEC_OK);
for (aom_codec_ctx_t *enc : { &enc_no_comp, &enc_with_comp }) {
ASSERT_EQ(aom_codec_control(enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(enc, AOME_SET_CQ_LEVEL, 40), AOM_CODEC_OK);
}
// Enable compound prediction for all three compound reference pairs
// (GOLDEN_LAST, LAST2_LAST, ALTREF_LAST) on the second encoder.
aom_svc_ref_frame_comp_pred_t comp_pred = {};
comp_pred.use_comp_pred[0] = 1;
comp_pred.use_comp_pred[1] = 1;
comp_pred.use_comp_pred[2] = 1;
ASSERT_EQ(aom_codec_control(&enc_with_comp, AV1E_SET_SVC_REF_FRAME_COMP_PRED,
&comp_pred),
AOM_CODEC_OK);
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(img, nullptr);
constexpr int kNumFrames = 10;
for (int frame = 0; frame < kNumFrames; ++frame) {
SCOPED_TRACE(frame);
// Fill moving textured pattern.
for (unsigned int r = 0; r < img->d_h; ++r) {
for (unsigned int c = 0; c < img->d_w; ++c) {
const int texture = (((r + frame) / 8 + (c + 2 * frame) / 8) & 1) * 32;
img->planes[AOM_PLANE_Y][r * img->stride[AOM_PLANE_Y] + c] =
static_cast<uint8_t>((r + 2 * c + 3 * frame + texture) & 0xff);
}
}
for (int plane = AOM_PLANE_U; plane <= AOM_PLANE_V; ++plane) {
const unsigned int h =
(img->d_h + img->y_chroma_shift) >> img->y_chroma_shift;
for (unsigned int r = 0; r < h; ++r) {
memset(img->planes[plane] + r * img->stride[plane], 128,
(img->d_w + img->x_chroma_shift) >> img->x_chroma_shift);
}
}
// Frame 0 refreshes slot 0; delta frames reference only LAST_FRAME (slot 0)
// with all other reference[i] = 0, and refresh slot 0.
aom_svc_ref_frame_config_t ref_cfg = {};
ref_cfg.ref_idx[0] = 0; // LAST_FRAME -> slot 0.
ref_cfg.reference[0] = frame > 0;
ref_cfg.refresh[0] = 1;
for (aom_codec_ctx_t *enc : { &enc_no_comp, &enc_with_comp }) {
ASSERT_EQ(aom_codec_control(enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_cfg),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(enc, img, frame, 1, 0), AOM_CODEC_OK);
}
aom_codec_iter_t iter_no_comp = nullptr;
aom_codec_iter_t iter_with_comp = nullptr;
const aom_codec_cx_pkt_t *pkt_no_comp =
aom_codec_get_cx_data(&enc_no_comp, &iter_no_comp);
const aom_codec_cx_pkt_t *pkt_with_comp =
aom_codec_get_cx_data(&enc_with_comp, &iter_with_comp);
ASSERT_NE(pkt_no_comp, nullptr);
ASSERT_NE(pkt_with_comp, nullptr);
ASSERT_EQ(pkt_no_comp->kind, AOM_CODEC_CX_FRAME_PKT);
ASSERT_EQ(pkt_with_comp->kind, AOM_CODEC_CX_FRAME_PKT);
// If use_comp_ref_nonrd is left enabled when no compound reference pair is
// active, single-reference frames switch reference mode
// (REFERENCE_MODE_SELECT) and interpolation filter (SWITCHABLE) signaling
// and mode search, altering both packet sizes and bitstream bytes. Verify
// that the output frames remain bit-exact (same size and identical bytes)
// as when compound prediction is disabled.
EXPECT_EQ(pkt_with_comp->data.frame.sz, pkt_no_comp->data.frame.sz);
if (pkt_with_comp->data.frame.sz == pkt_no_comp->data.frame.sz) {
EXPECT_EQ(memcmp(pkt_with_comp->data.frame.buf,
pkt_no_comp->data.frame.buf, pkt_no_comp->data.frame.sz),
0);
}
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc_with_comp), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc_no_comp), AOM_CODEC_OK);
}
// Regression test for b/567944558: Out-of-bounds read in setup_planes when
// encoding with manual reference buffer control (AV1E_SET_SVC_REF_FRAME_CONFIG)
// after a resolution change, when LAST_FRAME is not referenced and GOLDEN_FRAME
// and ALTREF_FRAME have equal SADs.
TEST(EncodeAPI, Buganizer567944558) {
for (int cpu_used : { 7, 9 }) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_VBR;
cfg.rc_target_bitrate = 500;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, cpu_used),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ROW_MT, 1), AOM_CODEC_OK);
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 1;
svc_params.number_temporal_layers = 1;
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 1;
svc_params.layer_target_bitrate[0] = cfg.rc_target_bitrate;
svc_params.max_quantizers[0] = 56;
svc_params.min_quantizers[0] = 10;
svc_params.framerate_factor[0] = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
auto set_ref_config = [&](bool ref_last, bool ref_golden, bool ref_altref,
int update_slot) {
aom_svc_ref_frame_config_t ref_frame_config = {};
ref_frame_config.ref_idx[0] = 0; // LAST_FRAME -> slot 0
ref_frame_config.ref_idx[3] = 1; // GOLDEN_FRAME -> slot 1
ref_frame_config.ref_idx[6] = 2; // ALTREF_FRAME -> slot 2
if (update_slot >= 0) {
ref_frame_config.refresh[update_slot] = 1;
}
ref_frame_config.reference[0] = ref_last ? 1 : 0;
ref_frame_config.reference[3] = ref_golden ? 1 : 0;
ref_frame_config.reference[6] = ref_altref ? 1 : 0;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG,
&ref_frame_config),
AOM_CODEC_OK);
};
aom_image_t *img_large =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 320, 240, 1);
ASSERT_NE(img_large, nullptr);
FillImageRandom(img_large);
// Frame 0: Keyframe at 320x240.
set_ref_config(/*ref_last=*/false, /*ref_golden=*/false,
/*ref_altref=*/false, /*update_slot=*/0);
ASSERT_EQ(aom_codec_encode(&enc, img_large, 0, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
// Frame 1: Delta frame at 320x240 referencing LAST and updating slot 0.
FillImageRandom(img_large);
set_ref_config(/*ref_last=*/true, /*ref_golden=*/false,
/*ref_altref=*/false, /*update_slot=*/0);
ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, 0), AOM_CODEC_OK);
// Reconfigure down to 42x19.
cfg.g_w = 42;
cfg.g_h = 19;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
aom_image_t *img_small =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 42, 19, 1);
ASSERT_NE(img_small, nullptr);
FillImageRandom(img_small);
// Frame 2: Keyframe at 42x19 (refreshes all slots with the same frame,
// leaving INVALID_MV in mi_alloc).
set_ref_config(/*ref_last=*/false, /*ref_golden=*/false,
/*ref_altref=*/false, /*update_slot=*/0);
ASSERT_EQ(aom_codec_encode(&enc, img_small, 2, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
// Frame 3: Delta frame at 42x19 referencing GOLDEN and ALTREF (without
// LAST). GOLDEN and ALTREF hold the same frame so their SADs tie.
FillImageRandom(img_small);
set_ref_config(/*ref_last=*/false, /*ref_golden=*/true, /*ref_altref=*/true,
/*update_slot=*/0);
ASSERT_EQ(aom_codec_encode(&enc, img_small, 3, 1, 0), AOM_CODEC_OK);
aom_img_free(img_small);
aom_img_free(img_large);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
}
// Fills `img` with deterministic content that changes from frame to frame, so
// that inter prediction and rate control have something to work with.
static void FillMovingPattern(aom_image_t *img, int frame) {
for (unsigned int r = 0; r < img->d_h; ++r) {
for (unsigned int c = 0; c < img->d_w; ++c) {
const int texture = (((r + frame) / 8 + (c + 2 * frame) / 8) & 1) * 32;
img->planes[AOM_PLANE_Y][r * img->stride[AOM_PLANE_Y] + c] =
static_cast<uint8_t>((r + 2 * c + 3 * frame + texture) & 0xff);
}
}
for (int plane = AOM_PLANE_U; plane <= AOM_PLANE_V; ++plane) {
const unsigned int h =
(img->d_h + img->y_chroma_shift) >> img->y_chroma_shift;
for (unsigned int r = 0; r < h; ++r) {
memset(img->planes[plane] + r * img->stride[plane], 128,
(img->d_w + img->x_chroma_shift) >> img->x_chroma_shift);
}
}
}
#if CONFIG_AV1_DECODER
// AV1E_SET_SVC_REF_FRAME_CONFIG: the refresh[] flags are indexed by buffer
// slot, and a frame may refresh a slot that none of the seven ref_idx[] entries
// point to. A typical example is a "ping-pong" buffer structure, where each
// frame predicts from one slot (the only reference in use) and stores its
// reconstruction in another slot, which it does not reference.
//
// Such refreshes used to be dropped: the slot was left out of
// refresh_frame_flags, so it silently kept its old content. This test checks,
// on the decoder side, which slots each frame refreshes.
TEST(EncodeAPI, SvcRefFrameConfigRefreshesSlotNotInRefIdx) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 64;
cfg.g_h = 64;
cfg.g_lag_in_frames = 0;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
aom_codec_ctx_t dec;
ASSERT_EQ(aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0),
AOM_CODEC_OK);
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(img, nullptr);
// Frame 0 is a key frame, which refreshes all slots. Frame n > 0 predicts
// from slot n - 1 and refreshes slot n. All seven ref_idx[] entries point
// to the predicted slot, so the refreshed slot is not among them.
for (int frame = 0; frame < 4; ++frame) {
SCOPED_TRACE(frame);
aom_svc_ref_frame_config_t ref_cfg = {};
if (frame > 0) {
for (int i = 0; i < 7; ++i) ref_cfg.ref_idx[i] = frame - 1;
ref_cfg.reference[0] = 1; // LAST.
}
ref_cfg.refresh[frame] = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_cfg),
AOM_CODEC_OK);
FillMovingPattern(img, frame);
ASSERT_EQ(aom_codec_encode(&enc, img, frame, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter);
ASSERT_NE(pkt, nullptr);
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
ASSERT_EQ(aom_codec_decode(
&dec, static_cast<const uint8_t *>(pkt->data.frame.buf),
pkt->data.frame.sz, nullptr),
AOM_CODEC_OK);
int ref_updates = 0;
ASSERT_EQ(aom_codec_control(&dec, AOMD_GET_LAST_REF_UPDATES, &ref_updates),
AOM_CODEC_OK);
EXPECT_EQ(ref_updates, frame == 0 ? 0xff : 1 << frame);
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&dec), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif // CONFIG_AV1_DECODER
// AV1E_SET_SVC_REF_FRAME_CONFIG: the encoder keeps track of when each buffer
// slot was last refreshed. Among other things, this is used to detect recovery
// frames with RPS (reference picture selection): if a frame's closest
// reference is several frames old, but the previous frame did predict from its
// predecessor, the frame is encoded at a lower QP (see
// set_flag_rps_bias_recovery_frame()).
//
// This bookkeeping used to only be done for refreshed slots that one of the
// ref_idx[] entries points to. This test runs two encoders with the same RPS
// structure: frame n is stored in slot n % 6 and predicts from frame n - 1,
// except for every 8th frame, which predicts from frame n - 5. LAST is the
// only reference in use. The encoders only differ in where the unused
// references point:
// - "Aliased": the refreshed slot is aliased as LAST2, and the other unused
// references point to the predicted slot.
// - "Not aliased": all seven ref_idx[] entries point to the predicted slot,
// so the refreshed slot is not among them.
// Both encoders must produce frames with identical QPs and sizes.
TEST(EncodeAPI, SvcRefFrameConfigTracksRefreshOfSlotNotInRefIdx) {
constexpr int kNumFrames = 20;
constexpr int kNumSlots = 6;
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 300;
aom_codec_ctx_t enc_aliased;
aom_codec_ctx_t enc_not_aliased;
ASSERT_EQ(aom_codec_enc_init(&enc_aliased, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_enc_init(&enc_not_aliased, iface, &cfg, 0), AOM_CODEC_OK);
for (aom_codec_ctx_t *enc : { &enc_aliased, &enc_not_aliased }) {
ASSERT_EQ(aom_codec_control(enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
// Order hints are off, as is common for RTC. This also ensures that the
// two encoders' frame headers have the same size.
ASSERT_EQ(aom_codec_control(enc, AV1E_SET_ENABLE_ORDER_HINT, 0),
AOM_CODEC_OK);
}
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(img, nullptr);
for (int frame = 0; frame < kNumFrames; ++frame) {
SCOPED_TRACE(frame);
const int refreshed_slot = frame % kNumSlots;
int predicted_slot = 0;
if (frame > 0) {
const int predicted_frame = frame % 8 == 0 ? frame - 5 : frame - 1;
predicted_slot = predicted_frame % kNumSlots;
}
aom_svc_ref_frame_config_t not_aliased_cfg = {};
for (int i = 0; i < 7; ++i) not_aliased_cfg.ref_idx[i] = predicted_slot;
not_aliased_cfg.reference[0] = frame > 0; // LAST.
not_aliased_cfg.refresh[refreshed_slot] = 1;
aom_svc_ref_frame_config_t aliased_cfg = not_aliased_cfg;
aliased_cfg.ref_idx[1] = refreshed_slot; // LAST2.
FillMovingPattern(img, frame);
int qp[2];
size_t size[2];
for (int i = 0; i < 2; ++i) {
aom_codec_ctx_t *enc = i == 0 ? &enc_aliased : &enc_not_aliased;
ASSERT_EQ(aom_codec_control(enc, AV1E_SET_SVC_REF_FRAME_CONFIG,
i == 0 ? &aliased_cfg : &not_aliased_cfg),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(enc, img, frame, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(enc, &iter);
ASSERT_NE(pkt, nullptr);
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
size[i] = pkt->data.frame.sz;
ASSERT_EQ(aom_codec_control(enc, AOME_GET_LAST_QUANTIZER, &qp[i]),
AOM_CODEC_OK);
}
EXPECT_EQ(qp[0], qp[1]);
EXPECT_EQ(size[0], size[1]);
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc_aliased), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc_not_aliased), AOM_CODEC_OK);
}
// AV1E_SET_SVC_REF_FRAME_CONFIG: the ref_idx[] entries of references whose
// reference[] flag is 0 are don't-cares, and must not affect the encoding.
//
// This test runs two encoders with the same prediction structure (every frame
// predicts from the previous frame's reconstruction, using LAST only) that
// only differ in which buffer slots they use:
// - "In place": every frame predicts from slot 0 and refreshes slot 0. The six
// unused references also point to slot 0, which is what a zero-initialized
// config gives (libaom's fixed SVC mode does the same for base layer frames
// on the lowest spatial layer).
// - "Ping-pong": frames alternate between predicting from slot 0 and
// refreshing slot 1, and vice versa. The refreshed slot is aliased as LAST2
// (like libaom's internal single layer RTC structure does) and the other
// unused references point to the predicted slot.
// Both encoders must produce frames with identical QPs and sizes.
//
// Previously, the in-place encoder treated every frame as a golden and altref
// update, because the unused GOLDEN/BWDREF/ALTREF aliases pointed to the
// refreshed slot. How that affected the encoding depends on the rate control
// mode, see the tests below.
static void SvcRefFrameConfigUnusedAliasesTest(aom_rc_mode rc_mode) {
constexpr int kNumFrames = 20;
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = rc_mode;
cfg.rc_target_bitrate = 300;
aom_codec_ctx_t enc_in_place;
aom_codec_ctx_t enc_ping_pong;
ASSERT_EQ(aom_codec_enc_init(&enc_in_place, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_enc_init(&enc_ping_pong, iface, &cfg, 0), AOM_CODEC_OK);
for (aom_codec_ctx_t *enc : { &enc_in_place, &enc_ping_pong }) {
ASSERT_EQ(aom_codec_control(enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(enc, AOME_SET_CQ_LEVEL, 40), AOM_CODEC_OK);
// Order hints are off, as is common for RTC. This also ensures that the
// two encoders' frame headers have the same size.
ASSERT_EQ(aom_codec_control(enc, AV1E_SET_ENABLE_ORDER_HINT, 0),
AOM_CODEC_OK);
}
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(img, nullptr);
for (int frame = 0; frame < kNumFrames; ++frame) {
SCOPED_TRACE(frame);
aom_svc_ref_frame_config_t in_place_cfg = {};
in_place_cfg.reference[0] = frame > 0; // LAST.
in_place_cfg.refresh[0] = 1;
aom_svc_ref_frame_config_t ping_pong_cfg = {};
const int predicted_slot = (frame + 1) % 2;
const int refreshed_slot = frame % 2;
for (int i = 0; i < 7; ++i) ping_pong_cfg.ref_idx[i] = predicted_slot;
ping_pong_cfg.ref_idx[1] = refreshed_slot; // LAST2.
ping_pong_cfg.reference[0] = frame > 0; // LAST.
ping_pong_cfg.refresh[refreshed_slot] = 1;
FillMovingPattern(img, frame);
int qp[2];
size_t size[2];
for (int i = 0; i < 2; ++i) {
aom_codec_ctx_t *enc = i == 0 ? &enc_in_place : &enc_ping_pong;
ASSERT_EQ(aom_codec_control(enc, AV1E_SET_SVC_REF_FRAME_CONFIG,
i == 0 ? &in_place_cfg : &ping_pong_cfg),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(enc, img, frame, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(enc, &iter);
ASSERT_NE(pkt, nullptr);
ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
size[i] = pkt->data.frame.sz;
ASSERT_EQ(aom_codec_control(enc, AOME_GET_LAST_QUANTIZER, &qp[i]),
AOM_CODEC_OK);
}
EXPECT_EQ(qp[0], qp[1]);
EXPECT_EQ(size[0], size[1]);
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&enc_in_place), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc_ping_pong), AOM_CODEC_OK);
}
// In single layer CBR mode, among other things, the average inter frame QP is
// not updated on golden and altref updates.
TEST(EncodeAPI, SvcRefFrameConfigUnusedAliasesDoNotAffectEncodingInCbrMode) {
SvcRefFrameConfigUnusedAliasesTest(AOM_CBR);
}
// In AOM_Q mode, golden and altref updates are encoded at a lower QP than the
// configured cq-level.
TEST(EncodeAPI, SvcRefFrameConfigUnusedAliasesDoNotAffectEncodingInQMode) {
SvcRefFrameConfigUnusedAliasesTest(AOM_Q);
}
#if CONFIG_AV1_DECODER
// Encodes the next frame from `enc`, and decodes it with `dec`. Returns the
// decoder's status, and the size of the decoded frame in `decoded_w` and
// `decoded_h`.
static aom_codec_err_t EncodeAndDecodeFrame(
aom_codec_ctx_t *enc, aom_codec_ctx_t *dec, const aom_image_t *img,
aom_codec_pts_t pts, unsigned int *decoded_w, unsigned int *decoded_h) {
*decoded_w = 0;
*decoded_h = 0;
EXPECT_EQ(aom_codec_encode(enc, img, pts, 1, 0), AOM_CODEC_OK);
aom_codec_iter_t iter = nullptr;
const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(enc, &iter);
EXPECT_NE(pkt, nullptr);
if (pkt == nullptr) return AOM_CODEC_ERROR;
EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
const aom_codec_err_t status =
aom_codec_decode(dec, static_cast<const uint8_t *>(pkt->data.frame.buf),
pkt->data.frame.sz, nullptr);
if (status != AOM_CODEC_OK) return status;
aom_codec_iter_t dec_iter = nullptr;
const aom_image_t *decoded = aom_codec_get_frame(dec, &dec_iter);
if (decoded != nullptr) {
*decoded_w = decoded->d_w;
*decoded_h = decoded->d_h;
}
return status;
}
// AV1E_SET_SVC_REF_FRAME_CONFIG: the ref_idx[] entries of references whose
// reference[] flag is 0 are don't-cares, even if they point to a slot that is
// not a valid reference for the current frame. The frame header signals all
// seven references though, and decoders (libaom, dav1d) reject a frame if any
// of them is more than 2x larger, or more than 16x smaller, than the frame.
//
// This test encodes two spatial layers, with the lower layer at 1/4 of the
// resolution of the upper layer. Each layer predicts from its own previous
// frame (LAST): slot 0 for the lower layer and slot 1 for the upper layer. The
// upper layer also predicts from the lower layer's frame (GOLDEN, slot 0). All
// unused references point to slot 1, which for the lower layer is a 4x larger
// frame from the upper layer, and so an invalid reference.
//
// Order hints are off, as is common for RTC. With order hints, unused
// references also affect the decoding process, so the encoder leaves them as
// configured, and the application must make sure that they are valid.
TEST(EncodeAPI, SvcRefFrameConfigUnusedRefIdxMayPointToInvalidReference) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 400;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_ORDER_HINT, 0),
AOM_CODEC_OK);
aom_svc_params_t svc_params = {};
svc_params.number_spatial_layers = 2;
svc_params.number_temporal_layers = 1;
svc_params.scaling_factor_num[0] = 1;
svc_params.scaling_factor_den[0] = 4;
svc_params.scaling_factor_num[1] = 1;
svc_params.scaling_factor_den[1] = 1;
svc_params.layer_target_bitrate[0] = 100;
svc_params.layer_target_bitrate[1] = 300;
svc_params.framerate_factor[0] = 1;
for (int sl = 0; sl < 2; ++sl) {
svc_params.min_quantizers[sl] = 0;
svc_params.max_quantizers[sl] = 56;
}
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_PARAMS, &svc_params),
AOM_CODEC_OK);
aom_codec_ctx_t dec;
ASSERT_EQ(aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0),
AOM_CODEC_OK);
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(img, nullptr);
for (int frame = 0; frame < 3; ++frame) {
FillMovingPattern(img, frame);
for (int sl = 0; sl < 2; ++sl) {
SCOPED_TRACE(testing::Message() << "frame " << frame << ", layer " << sl);
aom_svc_layer_id_t layer_id = {};
layer_id.spatial_layer_id = sl;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_LAYER_ID, &layer_id),
AOM_CODEC_OK);
aom_svc_ref_frame_config_t ref_cfg = {};
for (int i = 0; i < 7; ++i) ref_cfg.ref_idx[i] = 1;
ref_cfg.ref_idx[0] = sl; // LAST.
ref_cfg.reference[0] = 1;
if (sl == 1) {
ref_cfg.ref_idx[3] = 0; // GOLDEN.
ref_cfg.reference[3] = 1;
}
ref_cfg.refresh[sl] = 1;
ASSERT_EQ(
aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_cfg),
AOM_CODEC_OK);
unsigned int decoded_w;
unsigned int decoded_h;
EXPECT_EQ(
EncodeAndDecodeFrame(&enc, &dec, img, frame, &decoded_w, &decoded_h),
AOM_CODEC_OK)
<< aom_codec_error_detail(&dec);
}
}
aom_img_free(img);
ASSERT_EQ(aom_codec_destroy(&dec), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
// Like the test above, but with a single layer that changes resolution. When
// a frame's size differs from the maximum size, the frame header may signal
// that it has the same size as one of its seven references. This must take
// the references that are actually signaled into account.
//
// The frames are 320x240, 160x120, 320x240 and 160x120. The last frame
// predicts from the 320x240 frame (LAST), and its unused references point to
// the slot holding the first 160x120 frame.
TEST(EncodeAPI, SvcRefFrameConfigUnusedRefIdxWithResolutionChange) {
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = 320;
cfg.g_h = 240;
cfg.g_lag_in_frames = 0;
cfg.rc_end_usage = AOM_CBR;
cfg.rc_target_bitrate = 300;
// Allows changing the resolution without key frames.
cfg.g_forced_max_frame_width = 320;
cfg.g_forced_max_frame_height = 240;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 9), AOM_CODEC_OK);
// Order hints are off, as is common for RTC.
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ENABLE_ORDER_HINT, 0),
AOM_CODEC_OK);
aom_codec_ctx_t dec;
ASSERT_EQ(aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0),
AOM_CODEC_OK);
struct FrameConfig {
unsigned int width;
unsigned int height;
int predicted_slot;
int refreshed_slot;
int unused_slot;
};
// Frame 0 is a key frame, which refreshes all slots.
constexpr FrameConfig kFrames[] = { { 320, 240, 0, 0, 0 },
{ 160, 120, 0, 1, 0 },
{ 320, 240, 1, 0, 1 },
{ 160, 120, 0, 2, 1 } };
for (int frame = 0; frame < 4; ++frame) {
SCOPED_TRACE(frame);
const FrameConfig &config = kFrames[frame];
cfg.g_w = config.width;
cfg.g_h = config.height;
ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK);
aom_svc_ref_frame_config_t ref_cfg = {};
for (int i = 0; i < 7; ++i) ref_cfg.ref_idx[i] = config.unused_slot;
ref_cfg.ref_idx[0] = config.predicted_slot; // LAST.
ref_cfg.reference[0] = frame > 0;
ref_cfg.refresh[config.refreshed_slot] = 1;
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_cfg),
AOM_CODEC_OK);
aom_image_t *img =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
ASSERT_NE(img, nullptr);
FillMovingPattern(img, frame);
unsigned int decoded_w;
unsigned int decoded_h;
EXPECT_EQ(
EncodeAndDecodeFrame(&enc, &dec, img, frame, &decoded_w, &decoded_h),
AOM_CODEC_OK)
<< aom_codec_error_detail(&dec);
EXPECT_EQ(decoded_w, config.width);
EXPECT_EQ(decoded_h, config.height);
// Only frame 0 is a key frame.
int ref_updates = 0;
ASSERT_EQ(aom_codec_control(&dec, AOMD_GET_LAST_REF_UPDATES, &ref_updates),
AOM_CODEC_OK);
EXPECT_EQ(ref_updates, frame == 0 ? 0xff : 1 << config.refreshed_slot);
aom_img_free(img);
}
ASSERT_EQ(aom_codec_destroy(&dec), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
#endif // CONFIG_AV1_DECODER
// Verify that when handle_inter_mode_nonrd() reuses the superblock motion
// vector (x->sb_me_mv, searched relative to zero) for NEWMV on screen content,
// the vector is clamped to the valid subpel range relative to the block's
// reference MV predictor (ref_mv) so that |mv - ref_mv| does not exceed MV_MAX.
TEST(EncodeAPI, NonrdSbMeForcedNewmvRange) {
// At >= 4K resolution (width * height >= 3840 * 2160),
// do_int_pro_motion_estimation() doubles the screen-content search window
// from 512 to 1024 full-pel pixels ([-1024, +1023] relative to (0, 0)).
// Use a portrait 2160x3840 frame so +/-1023 pixel vertical displacements
// stay well within the frame height.
constexpr int kWidth = 2160;
constexpr int kHeight = 3840;
constexpr int kActiveW = 128;
constexpr int kActiveH = 2312;
aom_codec_iface_t *const iface = aom_codec_av1_cx();
aom_codec_enc_cfg_t cfg;
ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
AOM_CODEC_OK);
cfg.g_w = kWidth;
cfg.g_h = kHeight;
cfg.g_lag_in_frames = 0;
cfg.g_threads = 1;
cfg.rc_min_quantizer = 1;
cfg.rc_max_quantizer = 1;
aom_codec_ctx_t enc;
ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_QUANTIZER_ONE_PASS, 1),
AOM_CODEC_OK);
aom_image_t *img0 =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, kWidth, kHeight, 1);
aom_image_t *img1 =
aom_img_alloc(nullptr, AOM_IMG_FMT_I420, kWidth, kHeight, 1);
ASSERT_NE(img0, nullptr);
ASSERT_NE(img1, nullptr);
FillImage(img0, 128);
FillImage(img1, 128);
// Fill a 128-px wide strip (one superblock column) of the keyframe (img0)
// with random luma values so that displaced copies in img1 have a unique
// zero-SAD match and a large zero-MV SAD.
::libaom_test::ACMRandom rnd(::libaom_test::ACMRandom::DeterministicSeed());
for (int y = 0; y < kActiveH; ++y) {
uint8_t *const row0 = img0->planes[AOM_PLANE_Y] + y * img0->stride[0];
for (int x = 0; x < kActiveW; ++x) {
row0[x] = rnd.Rand8();
}
}
// Construct three vertically adjacent superblock regions in img1 (delta
// frame) to produce an unrepresentable MV difference if x->sb_me_mv is not
// clamped relative to ref_mv:
//
// 1. y in [0, 1152): copied from img0 at y + 1023 (dy = +1023 px).
// do_int_pro_motion_estimation() searches [-1024, +1023] relative to
// (0, 0), finds the exact match, and sets x->sb_me_mv = (+8184, 0)
// (+1023 px in 1/8-pel units).
//
// 2. y in [1152, 1280): copied from img0 at y + 1031 (dy = +1031 px).
// Because +1031 px is outside int_pro's [-1024, +1023] window around
// (0, 0), x->sb_me_block is not set. Instead, normal NEWMV motion search
// (search_new_mv()) starts from the top neighbor's predictor
// ref_mv = (+8184, 0) (+1023 px) and steps +8 px to find the exact match
// at mv = (+8248, 0) (+1031 px).
//
// 3. y in [1280, 1408): copied from img0 at y - 1023 (dy = -1023 px).
// At y >= 1280, -1023 px upward is inside img0, so int_pro ME finds the
// exact match relative to (0, 0) and sets x->sb_me_block = 1 with
// x->sb_me_mv = (-8184, 0) (-1023 px). However, this superblock's coding
// predictor from its top neighbor (region 2) is ref_mv = (+8248, 0)
// (+1031 px). Without clamping x->sb_me_mv relative to ref_mv in
// handle_inter_mode_nonrd(), NEWMV encodes the difference:
// mv_diff.row = -8184 - 8248 = -16432 (-2054 px)
// which exceeds MV_MAX (16383, +/-2047.875 px) and triggers
// Assertion `c <= MV_CLASS_10' failed in av1_get_mv_class().
for (int y = 0; y < 1408; ++y) {
const int dy = (y < 1152) ? 1023 : (y < 1280) ? 1031 : -1023;
memcpy(img1->planes[AOM_PLANE_Y] + y * img1->stride[0],
img0->planes[AOM_PLANE_Y] + (y + dy) * img0->stride[0], kActiveW);
}
ASSERT_EQ(aom_codec_encode(&enc, img0, 0, 1, AOM_EFLAG_FORCE_KF),
AOM_CODEC_OK);
ASSERT_EQ(aom_codec_encode(&enc, img1, 1, 1, 0), AOM_CODEC_OK);
aom_img_free(img1);
aom_img_free(img0);
ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
} // namespace