blob: 6868190fbcd4c1f4148e46b2762ea60cd4e46db8 [file]
/*
* Copyright (c) 2026, 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.
*/
/*
* See build_av1_enc_fuzzer.sh for building instructions.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "aom/aom_encoder.h"
#include "aom/aom_image.h"
#include "aom/aomcx.h"
namespace {
constexpr unsigned int kMaxDimension = 1024;
constexpr size_t kMinHeaderSize = 16;
// Bit masks derived from the mode_flags byte. The low two bits are used to
// select the encoding usage (see PickUsage).
enum ModeFlag {
kLossless = 1u << 2,
kRowMt = 1u << 3,
kUseSecondResolution = 1u << 4,
kKeyFrameSecond = 1u << 5,
kErrorResilient = 1u << 6,
};
// Number of frames to encode for a given cpu_used value. Lower cpu_used
// values encode more slowly, so fewer frames are used to stay within the
// fuzzer's execution time budget (libFuzzer defaults to one second per
// input). This can be refined based on measured fuzzer performance.
constexpr unsigned int kNumFramesForCpuUsed[12] = {
50, // cpu_used 0
50, // cpu_used 1
50, // cpu_used 2
60, // cpu_used 3
60, // cpu_used 4
70, // cpu_used 5
70, // cpu_used 6
80, // cpu_used 7
80, // cpu_used 8
90, // cpu_used 9
90, // cpu_used 10
100 // cpu_used 11
};
struct FuzzReader {
const uint8_t *data;
size_t size;
};
uint8_t ReadU8(FuzzReader *reader) {
if (reader->size == 0) return 0;
const uint8_t value = *reader->data++;
--reader->size;
return value;
}
uint16_t ReadU16(FuzzReader *reader) {
if (reader->size < 2) return 0;
const uint16_t value = reader->data[0] | (reader->data[1] << 8);
reader->data += 2;
reader->size -= 2;
return value;
}
unsigned int UsageToIndex(unsigned int usage) {
switch (usage) {
case AOM_USAGE_GOOD_QUALITY: return 0;
case AOM_USAGE_REALTIME: return 1;
case AOM_USAGE_ALL_INTRA: return 2;
default: return 0;
}
}
unsigned int PickUsage(uint8_t raw) {
switch (raw % 3) {
case 0: return AOM_USAGE_GOOD_QUALITY;
case 1: return AOM_USAGE_REALTIME;
default: return AOM_USAGE_ALL_INTRA;
}
}
aom_rc_mode PickRcMode(uint8_t raw) {
switch (raw % 4) {
case 0: return AOM_VBR;
case 1: return AOM_CBR;
case 2: return AOM_CQ;
default: return AOM_Q;
}
}
unsigned int PickDimension(uint16_t raw) { return 1u + (raw % kMaxDimension); }
int PickCpuUsed(unsigned int usage, uint8_t raw) {
const int max_cpu_used = usage == AOM_USAGE_REALTIME ? 11 : 9;
return raw % (max_cpu_used + 1);
}
bool DrainPackets(aom_codec_ctx_t *codec) {
bool got_data = false;
aom_codec_iter_t iter = nullptr;
while (aom_codec_get_cx_data(codec, &iter) != nullptr) {
got_data = true;
}
return got_data;
}
bool InitDefaultConfig(aom_codec_iface_t *iface, unsigned int usage,
aom_codec_enc_cfg_t *cfg) {
const unsigned int usages[3] = { AOM_USAGE_GOOD_QUALITY, AOM_USAGE_REALTIME,
AOM_USAGE_ALL_INTRA };
const unsigned int requested_index = UsageToIndex(usage);
for (unsigned int i = 0; i < 3; ++i) {
const unsigned int index = (requested_index + i) % 3;
if (aom_codec_enc_config_default(iface, cfg, usages[index]) ==
AOM_CODEC_OK) {
return true;
}
}
return false;
}
// Deterministic PRNG so that the frame content is derived from the fuzzer
// input without requiring the reader to supply a full plane-sized chunk of
// data for every frame.
uint32_t Rand(uint32_t *state) {
*state = *state * 1664525u + 1013904223u;
return *state;
}
void FillPlane(uint8_t *plane, int stride, unsigned int width,
unsigned int height, uint32_t *state) {
for (unsigned int row = 0; row < height; ++row) {
for (unsigned int col = 0; col < width; ++col) {
plane[static_cast<size_t>(row) * stride + col] =
static_cast<uint8_t>(Rand(state) >> 24);
}
}
}
bool BuildImage(FuzzReader *reader, unsigned int width, unsigned int height,
aom_image_t *image, unsigned int frame_index) {
if (aom_img_alloc(image, AOM_IMG_FMT_I420, width, height, 1) == nullptr) {
return false;
}
// Seed a deterministic PRNG from the fuzzer input and the frame index so
// that each encoded frame differs without consuming a plane-sized chunk of
// input per frame.
uint32_t state = 0;
for (unsigned int i = 0; i < 4; ++i) {
state = (state << 8) | ReadU8(reader);
}
const uint32_t golden_ratio = 0x9e3779b9u;
state ^= frame_index * golden_ratio;
const unsigned int uv_width = (width + 1) / 2;
const unsigned int uv_height = (height + 1) / 2;
FillPlane(image->planes[AOM_PLANE_Y], image->stride[AOM_PLANE_Y], width,
height, &state);
FillPlane(image->planes[AOM_PLANE_U], image->stride[AOM_PLANE_U], uv_width,
uv_height, &state);
FillPlane(image->planes[AOM_PLANE_V], image->stride[AOM_PLANE_V], uv_width,
uv_height, &state);
return true;
}
void ApplyControls(aom_codec_ctx_t *codec, unsigned int usage,
uint8_t mode_flags, uint8_t cpu_used_raw, uint8_t ctl0,
uint8_t ctl1) {
const int cpu_used = PickCpuUsed(usage, cpu_used_raw);
const unsigned int lossless = (mode_flags & kLossless) != 0;
const unsigned int row_mt = (mode_flags & kRowMt) != 0;
const unsigned int aq_mode = ctl0 % 4;
const unsigned int deltaq_mode = ctl1 % 4;
const unsigned int tile_columns = (ctl0 >> 4) & 0x3;
const unsigned int tile_rows = (ctl1 >> 4) & 0x3;
const unsigned int enable_cdef = (ctl0 >> 2) & 1;
const unsigned int enable_restoration = (ctl1 >> 2) & 1;
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AOME_SET_CPUUSED, cpu_used);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_LOSSLESS, lossless);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ROW_MT, row_mt);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_AQ_MODE, aq_mode);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_DELTAQ_MODE, deltaq_mode);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_COLUMNS,
tile_columns);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_ROWS, tile_rows);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_CDEF, enable_cdef);
(void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_RESTORATION,
enable_restoration);
}
bool EncodeFrame(aom_codec_ctx_t *codec, FuzzReader *reader, unsigned int width,
unsigned int height, aom_codec_pts_t pts,
aom_enc_frame_flags_t flags) {
aom_image_t image;
memset(&image, 0, sizeof(image));
if (!BuildImage(reader, width, height, &image, pts)) {
return false;
}
const aom_codec_err_t enc_ret =
aom_codec_encode(codec, &image, pts, 1, flags);
if (enc_ret == AOM_CODEC_OK) {
(void)DrainPackets(codec);
}
aom_img_free(&image);
return true;
}
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
aom_codec_iface_t *iface = aom_codec_av1_cx();
if (iface == nullptr || size < kMinHeaderSize) return 0;
FuzzReader reader = { data, size };
const uint8_t mode_flags = ReadU8(&reader);
const uint8_t cpu_used_raw = ReadU8(&reader);
const uint8_t ctl0 = ReadU8(&reader);
const uint8_t ctl1 = ReadU8(&reader);
const unsigned int usage = PickUsage(mode_flags);
const unsigned int width0 = PickDimension(ReadU16(&reader));
const unsigned int height0 = PickDimension(ReadU16(&reader));
const unsigned int bitrate = 1u + (ReadU16(&reader) % 4000u);
const unsigned int width1 = PickDimension(ReadU16(&reader));
const unsigned int height1 = PickDimension(ReadU16(&reader));
const unsigned int threads0 = ReadU8(&reader) % 9;
const unsigned int threads1 = ReadU8(&reader) % 9;
aom_codec_enc_cfg_t cfg;
if (!InitDefaultConfig(iface, usage, &cfg)) return 0;
cfg.g_usage = usage;
cfg.g_w = width0;
cfg.g_h = height0;
cfg.g_threads = threads0;
cfg.g_forced_max_frame_width = kMaxDimension;
cfg.g_forced_max_frame_height = kMaxDimension;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 1000000;
cfg.g_pass = AOM_RC_ONE_PASS;
cfg.g_lag_in_frames = 0;
cfg.g_error_resilient =
(mode_flags & kErrorResilient) ? AOM_ERROR_RESILIENT_DEFAULT : 0;
cfg.rc_end_usage = PickRcMode(ctl0);
cfg.rc_target_bitrate = bitrate;
aom_codec_ctx_t codec;
memset(&codec, 0, sizeof(codec));
if (aom_codec_enc_init(&codec, iface, &cfg, 0) != AOM_CODEC_OK) return 0;
const int cpu_used = PickCpuUsed(usage, cpu_used_raw);
ApplyControls(&codec, usage, mode_flags, cpu_used_raw, ctl0, ctl1);
const unsigned int num_frames = kNumFramesForCpuUsed[cpu_used];
bool config_failed = false;
for (unsigned int frame = 0; frame < num_frames; ++frame) {
aom_enc_frame_flags_t flags = 0;
if (frame == 1 && (mode_flags & kKeyFrameSecond)) {
flags |= AOM_EFLAG_FORCE_KF;
}
unsigned int width = width0;
unsigned int height = height0;
if (mode_flags & kUseSecondResolution) {
// Vary the switch frame based on the input so different encodings
// exercise the resolution change at different points in the stream.
// num_frames >= 50 for every cpu_used, so switch_frame and
// restore_frame stay within [1, num_frames - 1].
const unsigned int switch_frame = 1u + (ctl0 % (num_frames / 4u));
const unsigned int restore_frame =
switch_frame + 1u + (ctl1 % (num_frames / 4u));
if (frame == switch_frame) {
cfg.g_w = width1;
cfg.g_h = height1;
cfg.g_threads = threads1;
cfg.rc_end_usage = PickRcMode(ctl1);
if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) {
config_failed = true;
break;
}
width = width1;
height = height1;
} else if (frame == restore_frame) {
cfg.g_w = width0;
cfg.g_h = height0;
cfg.g_threads = threads0;
cfg.rc_end_usage = PickRcMode(ctl0);
if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) {
config_failed = true;
break;
}
width = width0;
height = height0;
}
}
if (!EncodeFrame(&codec, &reader, width, height, frame, flags)) {
config_failed = true;
break;
}
}
if (!config_failed) {
while (aom_codec_encode(&codec, nullptr, 0, 0, 0) == AOM_CODEC_OK &&
DrainPackets(&codec)) {
}
}
(void)aom_codec_destroy(&codec);
return 0;
}