/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 | } |