Coverage Report

Created: 2026-11-12 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/aom/examples/av1_enc_fuzzer.cc
Line
Count
Source
1
/*
2
 * Copyright (c) 2026, Alliance for Open Media. All rights reserved.
3
 *
4
 * This source code is subject to the terms of the BSD 2 Clause License and
5
 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6
 * was not distributed with this source code in the LICENSE file, you can
7
 * obtain it at www.aomedia.org/license/software. If the Alliance for Open
8
 * Media Patent License 1.0 was not distributed with this source code in the
9
 * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
10
 */
11
12
/*
13
 * See build_av1_enc_fuzzer.sh for building instructions.
14
 */
15
16
#include <stddef.h>
17
#include <stdint.h>
18
#include <stdlib.h>
19
#include <string.h>
20
21
#include "aom/aom_encoder.h"
22
#include "aom/aom_image.h"
23
#include "aom/aomcx.h"
24
25
namespace {
26
27
constexpr unsigned int kMaxDimension = 1024;
28
constexpr size_t kMinHeaderSize = 16;
29
30
// Bit masks derived from the mode_flags byte. The low two bits are used to
31
// select the encoding usage (see PickUsage).
32
enum ModeFlag {
33
  kLossless = 1u << 2,
34
  kRowMt = 1u << 3,
35
  kUseSecondResolution = 1u << 4,
36
  kKeyFrameSecond = 1u << 5,
37
  kErrorResilient = 1u << 6,
38
};
39
40
// Number of frames to encode for a given cpu_used value. Lower cpu_used
41
// values encode more slowly, so fewer frames are used to stay within the
42
// fuzzer's execution time budget (libFuzzer defaults to one second per
43
// input). This can be refined based on measured fuzzer performance.
44
constexpr unsigned int kNumFramesForCpuUsed[12] = {
45
  12,  // cpu_used 0
46
  12,  // cpu_used 1
47
  12,  // cpu_used 2
48
  15,  // cpu_used 3
49
  15,  // cpu_used 4
50
  17,  // cpu_used 5
51
  17,  // cpu_used 6
52
  20,  // cpu_used 7
53
  20,  // cpu_used 8
54
  22,  // cpu_used 9
55
  22,  // cpu_used 10
56
  25   // cpu_used 11
57
};
58
59
// Maximum number of 64x64 superblocks to encode across all frames for a
60
// given cpu_used value (one 1024x1024 frame is 256 superblocks). This caps
61
// worst-case execution time on medium and large frames at slow speed presets
62
// while letting small frames run the full kNumFramesForCpuUsed count and still
63
// allowing full 1024x1024 resolution with at least 3 frames (initial,
64
// switch_frame, and restore_frame) at every cpu_used.
65
constexpr unsigned int kMaxSuperblocksForCpuUsed[12] = {
66
  96,    // cpu_used 0
67
  256,   // cpu_used 1
68
  384,   // cpu_used 2
69
  512,   // cpu_used 3
70
  640,   // cpu_used 4
71
  1024,  // cpu_used 5
72
  1280,  // cpu_used 6
73
  1536,  // cpu_used 7
74
  1792,  // cpu_used 8
75
  2048,  // cpu_used 9
76
  2048,  // cpu_used 10
77
  2304   // cpu_used 11
78
};
79
80
struct FuzzReader {
81
  const uint8_t *data;
82
  size_t size;
83
};
84
85
0
uint8_t ReadU8(FuzzReader *reader) {
86
0
  if (reader->size == 0) return 0;
87
0
  const uint8_t value = *reader->data++;
88
0
  --reader->size;
89
0
  return value;
90
0
}
91
92
0
uint16_t ReadU16(FuzzReader *reader) {
93
0
  if (reader->size < 2) return 0;
94
0
  const uint16_t value = reader->data[0] | (reader->data[1] << 8);
95
0
  reader->data += 2;
96
0
  reader->size -= 2;
97
0
  return value;
98
0
}
99
100
0
unsigned int UsageToIndex(unsigned int usage) {
101
0
  switch (usage) {
102
0
    case AOM_USAGE_GOOD_QUALITY: return 0;
103
0
    case AOM_USAGE_REALTIME: return 1;
104
0
    case AOM_USAGE_ALL_INTRA: return 2;
105
0
    default: return 0;
106
0
  }
107
0
}
108
109
0
unsigned int PickUsage(uint8_t raw) {
110
0
  switch (raw % 3) {
111
0
    case 0: return AOM_USAGE_GOOD_QUALITY;
112
0
    case 1: return AOM_USAGE_REALTIME;
113
0
    default: return AOM_USAGE_ALL_INTRA;
114
0
  }
115
0
}
116
117
0
aom_rc_mode PickRcMode(uint8_t raw) {
118
0
  switch (raw % 4) {
119
0
    case 0: return AOM_VBR;
120
0
    case 1: return AOM_CBR;
121
0
    case 2: return AOM_CQ;
122
0
    default: return AOM_Q;
123
0
  }
124
0
}
125
126
0
unsigned int PickDimension(uint16_t raw) { return 1u + (raw % kMaxDimension); }
127
128
0
int PickCpuUsed(unsigned int usage, uint8_t raw) {
129
0
  const int max_cpu_used = usage == AOM_USAGE_REALTIME ? 11 : 9;
130
0
  return raw % (max_cpu_used + 1);
131
0
}
132
133
0
bool DrainPackets(aom_codec_ctx_t *codec) {
134
0
  bool got_data = false;
135
0
  aom_codec_iter_t iter = nullptr;
136
0
  while (aom_codec_get_cx_data(codec, &iter) != nullptr) {
137
0
    got_data = true;
138
0
  }
139
0
  return got_data;
140
0
}
141
142
bool InitDefaultConfig(aom_codec_iface_t *iface, unsigned int usage,
143
0
                       aom_codec_enc_cfg_t *cfg) {
144
0
  const unsigned int usages[3] = { AOM_USAGE_GOOD_QUALITY, AOM_USAGE_REALTIME,
145
0
                                   AOM_USAGE_ALL_INTRA };
146
0
  const unsigned int requested_index = UsageToIndex(usage);
147
0
  for (unsigned int i = 0; i < 3; ++i) {
148
0
    const unsigned int index = (requested_index + i) % 3;
149
0
    if (aom_codec_enc_config_default(iface, cfg, usages[index]) ==
150
0
        AOM_CODEC_OK) {
151
0
      return true;
152
0
    }
153
0
  }
154
0
  return false;
155
0
}
156
157
// Deterministic PRNG so that the frame content is derived from the fuzzer
158
// input without requiring the reader to supply a full plane-sized chunk of
159
// data for every frame.
160
0
uint32_t Rand(uint32_t *state) {
161
0
  *state = *state * 1664525u + 1013904223u;
162
0
  return *state;
163
0
}
164
165
void FillPlane(uint8_t *plane, int stride, unsigned int width,
166
0
               unsigned int height, uint32_t *state) {
167
0
  uint8_t background = 0;
168
0
  for (unsigned int row = 0; row < height; ++row) {
169
0
    if ((row & 15u) == 0) {
170
0
      background = static_cast<uint8_t>(Rand(state) >> 24);
171
0
    }
172
0
    memset(plane + static_cast<size_t>(row) * stride, background, width);
173
0
  }
174
  // Stamp two small 16x16 textured patches (one random noise, one gradient)
175
  // at pseudo-random locations so that both flat/skip and high-variance RD
176
  // paths are exercised without filling large frames entirely with white
177
  // noise.
178
0
  constexpr unsigned int kPatchSize = 16;
179
0
  for (unsigned int pass = 0; pass < 2; ++pass) {
180
0
    const unsigned int patch_x = (Rand(state) >> 16) % width;
181
0
    const unsigned int patch_y = (Rand(state) >> 16) % height;
182
0
    const unsigned int patch_width =
183
0
        (width - patch_x < kPatchSize) ? (width - patch_x) : kPatchSize;
184
0
    const unsigned int patch_height =
185
0
        (height - patch_y < kPatchSize) ? (height - patch_y) : kPatchSize;
186
0
    for (unsigned int row = 0; row < patch_height; ++row) {
187
0
      uint8_t *dst_row =
188
0
          plane + static_cast<size_t>(patch_y + row) * stride + patch_x;
189
0
      for (unsigned int col = 0; col < patch_width; ++col) {
190
0
        dst_row[col] =
191
0
            (pass == 0)
192
0
                ? static_cast<uint8_t>(Rand(state) >> 24)
193
0
                : static_cast<uint8_t>(background + row * 8u + col * 8u);
194
0
      }
195
0
    }
196
0
  }
197
0
}
198
199
bool BuildImage(FuzzReader *reader, unsigned int width, unsigned int height,
200
0
                aom_image_t *image, unsigned int frame_index) {
201
0
  if (aom_img_alloc(image, AOM_IMG_FMT_I420, width, height, 1) == nullptr) {
202
0
    return false;
203
0
  }
204
205
  // Seed a deterministic PRNG from the fuzzer input and the frame index so
206
  // that each encoded frame differs without consuming a plane-sized chunk of
207
  // input per frame.
208
0
  uint32_t state = 0;
209
0
  for (unsigned int i = 0; i < 4; ++i) {
210
0
    state = (state << 8) | ReadU8(reader);
211
0
  }
212
0
  const uint32_t golden_ratio = 0x9e3779b9u;
213
0
  state ^= frame_index * golden_ratio;
214
215
0
  const unsigned int uv_width = (width + 1) / 2;
216
0
  const unsigned int uv_height = (height + 1) / 2;
217
0
  FillPlane(image->planes[AOM_PLANE_Y], image->stride[AOM_PLANE_Y], width,
218
0
            height, &state);
219
0
  FillPlane(image->planes[AOM_PLANE_U], image->stride[AOM_PLANE_U], uv_width,
220
0
            uv_height, &state);
221
0
  FillPlane(image->planes[AOM_PLANE_V], image->stride[AOM_PLANE_V], uv_width,
222
0
            uv_height, &state);
223
0
  return true;
224
0
}
225
226
void ApplyControls(aom_codec_ctx_t *codec, unsigned int usage,
227
                   uint8_t mode_flags, uint8_t cpu_used_raw, uint8_t ctl0,
228
0
                   uint8_t ctl1) {
229
0
  const int cpu_used = PickCpuUsed(usage, cpu_used_raw);
230
0
  const unsigned int lossless = (mode_flags & kLossless) != 0;
231
0
  const unsigned int row_mt = (mode_flags & kRowMt) != 0;
232
0
  const unsigned int aq_mode = ctl0 % 4;
233
0
  const unsigned int deltaq_mode = ctl1 % 4;
234
0
  const unsigned int tile_columns = (ctl0 >> 4) & 0x3;
235
0
  const unsigned int tile_rows = (ctl1 >> 4) & 0x3;
236
0
  const unsigned int enable_cdef = (ctl0 >> 2) & 1;
237
0
  const unsigned int enable_restoration = (ctl1 >> 2) & 1;
238
239
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AOME_SET_CPUUSED, cpu_used);
240
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_LOSSLESS, lossless);
241
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ROW_MT, row_mt);
242
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_AQ_MODE, aq_mode);
243
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_DELTAQ_MODE, deltaq_mode);
244
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_COLUMNS,
245
0
                                      tile_columns);
246
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_ROWS, tile_rows);
247
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_CDEF, enable_cdef);
248
0
  (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_RESTORATION,
249
0
                                      enable_restoration);
250
0
}
251
252
bool EncodeFrame(aom_codec_ctx_t *codec, FuzzReader *reader, unsigned int width,
253
                 unsigned int height, aom_codec_pts_t pts,
254
0
                 aom_enc_frame_flags_t flags) {
255
0
  aom_image_t image;
256
0
  memset(&image, 0, sizeof(image));
257
258
0
  if (!BuildImage(reader, width, height, &image, pts)) {
259
0
    return false;
260
0
  }
261
262
0
  const aom_codec_err_t enc_ret =
263
0
      aom_codec_encode(codec, &image, pts, 1, flags);
264
0
  if (enc_ret == AOM_CODEC_OK) {
265
0
    (void)DrainPackets(codec);
266
0
  }
267
268
0
  aom_img_free(&image);
269
0
  return true;
270
0
}
271
272
}  // namespace
273
274
0
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
275
0
  aom_codec_iface_t *iface = aom_codec_av1_cx();
276
0
  if (iface == nullptr || size < kMinHeaderSize) return 0;
277
278
0
  FuzzReader reader = { data, size };
279
0
  const uint8_t mode_flags = ReadU8(&reader);
280
0
  const uint8_t cpu_used_raw = ReadU8(&reader);
281
0
  const uint8_t ctl0 = ReadU8(&reader);
282
0
  const uint8_t ctl1 = ReadU8(&reader);
283
0
  const unsigned int usage = PickUsage(mode_flags);
284
0
  const unsigned int width0 = PickDimension(ReadU16(&reader));
285
0
  const unsigned int height0 = PickDimension(ReadU16(&reader));
286
0
  const unsigned int bitrate = 1u + (ReadU16(&reader) % 4000u);
287
0
  const unsigned int width1 = PickDimension(ReadU16(&reader));
288
0
  const unsigned int height1 = PickDimension(ReadU16(&reader));
289
0
  const unsigned int threads0 = ReadU8(&reader) % 9;
290
0
  const unsigned int threads1 = ReadU8(&reader) % 9;
291
292
0
  aom_codec_enc_cfg_t cfg;
293
0
  if (!InitDefaultConfig(iface, usage, &cfg)) return 0;
294
295
0
  cfg.g_usage = usage;
296
0
  cfg.g_w = width0;
297
0
  cfg.g_h = height0;
298
0
  cfg.g_threads = threads0;
299
0
  cfg.g_forced_max_frame_width = kMaxDimension;
300
0
  cfg.g_forced_max_frame_height = kMaxDimension;
301
0
  cfg.g_timebase.num = 1;
302
0
  cfg.g_timebase.den = 1000000;
303
0
  cfg.g_pass = AOM_RC_ONE_PASS;
304
0
  cfg.g_lag_in_frames = 0;
305
0
  cfg.g_error_resilient =
306
0
      (mode_flags & kErrorResilient) ? AOM_ERROR_RESILIENT_DEFAULT : 0;
307
0
  cfg.rc_end_usage = PickRcMode(ctl0);
308
0
  cfg.rc_target_bitrate = bitrate;
309
310
0
  aom_codec_ctx_t codec;
311
0
  memset(&codec, 0, sizeof(codec));
312
0
  if (aom_codec_enc_init(&codec, iface, &cfg, 0) != AOM_CODEC_OK) return 0;
313
314
0
  const int cpu_used = PickCpuUsed(usage, cpu_used_raw);
315
0
  ApplyControls(&codec, usage, mode_flags, cpu_used_raw, ctl0, ctl1);
316
317
0
  const unsigned int superblocks0 =
318
0
      ((width0 + 63u) / 64u) * ((height0 + 63u) / 64u);
319
0
  const unsigned int superblocks1 =
320
0
      (mode_flags & kUseSecondResolution)
321
0
          ? (((width1 + 63u) / 64u) * ((height1 + 63u) / 64u))
322
0
          : superblocks0;
323
0
  const unsigned int max_frame_superblocks =
324
0
      (superblocks0 > superblocks1) ? superblocks0 : superblocks1;
325
0
  const unsigned int max_frames_by_superblocks =
326
0
      kMaxSuperblocksForCpuUsed[cpu_used] / max_frame_superblocks;
327
0
  unsigned int num_frames = kNumFramesForCpuUsed[cpu_used];
328
0
  if (num_frames > max_frames_by_superblocks) {
329
0
    num_frames =
330
0
        (max_frames_by_superblocks < 3u) ? 3u : max_frames_by_superblocks;
331
0
  }
332
333
0
  unsigned int width = width0;
334
0
  unsigned int height = height0;
335
0
  bool config_failed = false;
336
0
  for (unsigned int frame = 0; frame < num_frames; ++frame) {
337
0
    aom_enc_frame_flags_t flags = 0;
338
0
    if (frame == 1 && (mode_flags & kKeyFrameSecond)) {
339
0
      flags |= AOM_EFLAG_FORCE_KF;
340
0
    }
341
342
0
    if (mode_flags & kUseSecondResolution) {
343
      // Vary the switch frame based on the input so different encodings
344
      // exercise the resolution change at different points in the stream.
345
      // num_frames >= 3 for every input, so switch_frame and restore_frame
346
      // stay within [1, num_frames - 1].
347
0
      const unsigned int window =
348
0
          (num_frames > 4u) ? ((num_frames - 2u) / 2u) : 1u;
349
0
      const unsigned int switch_frame = 1u + (ctl0 % window);
350
0
      const unsigned int restore_frame = switch_frame + 1u + (ctl1 % window);
351
0
      if (frame == switch_frame) {
352
0
        cfg.g_w = width1;
353
0
        cfg.g_h = height1;
354
0
        cfg.g_threads = threads1;
355
0
        cfg.rc_end_usage = PickRcMode(ctl1);
356
0
        if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) {
357
0
          config_failed = true;
358
0
          break;
359
0
        }
360
0
        width = width1;
361
0
        height = height1;
362
0
      } else if (frame == restore_frame) {
363
0
        cfg.g_w = width0;
364
0
        cfg.g_h = height0;
365
0
        cfg.g_threads = threads0;
366
0
        cfg.rc_end_usage = PickRcMode(ctl0);
367
0
        if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) {
368
0
          config_failed = true;
369
0
          break;
370
0
        }
371
0
        width = width0;
372
0
        height = height0;
373
0
      }
374
0
    }
375
376
0
    if (!EncodeFrame(&codec, &reader, width, height, frame, flags)) {
377
0
      config_failed = true;
378
0
      break;
379
0
    }
380
0
  }
381
382
0
  if (!config_failed) {
383
0
    while (aom_codec_encode(&codec, nullptr, 0, 0, 0) == AOM_CODEC_OK &&
384
0
           DrainPackets(&codec)) {
385
0
    }
386
0
  }
387
388
0
  (void)aom_codec_destroy(&codec);
389
0
  return 0;
390
0
}