/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 | | 50, // cpu_used 0 |
46 | | 50, // cpu_used 1 |
47 | | 50, // cpu_used 2 |
48 | | 60, // cpu_used 3 |
49 | | 60, // cpu_used 4 |
50 | | 70, // cpu_used 5 |
51 | | 70, // cpu_used 6 |
52 | | 80, // cpu_used 7 |
53 | | 80, // cpu_used 8 |
54 | | 90, // cpu_used 9 |
55 | | 90, // cpu_used 10 |
56 | | 100 // cpu_used 11 |
57 | | }; |
58 | | |
59 | | struct FuzzReader { |
60 | | const uint8_t *data; |
61 | | size_t size; |
62 | | }; |
63 | | |
64 | 0 | uint8_t ReadU8(FuzzReader *reader) { |
65 | 0 | if (reader->size == 0) return 0; |
66 | 0 | const uint8_t value = *reader->data++; |
67 | 0 | --reader->size; |
68 | 0 | return value; |
69 | 0 | } |
70 | | |
71 | 0 | uint16_t ReadU16(FuzzReader *reader) { |
72 | 0 | if (reader->size < 2) return 0; |
73 | 0 | const uint16_t value = reader->data[0] | (reader->data[1] << 8); |
74 | 0 | reader->data += 2; |
75 | 0 | reader->size -= 2; |
76 | 0 | return value; |
77 | 0 | } |
78 | | |
79 | 0 | unsigned int UsageToIndex(unsigned int usage) { |
80 | 0 | switch (usage) { |
81 | 0 | case AOM_USAGE_GOOD_QUALITY: return 0; |
82 | 0 | case AOM_USAGE_REALTIME: return 1; |
83 | 0 | case AOM_USAGE_ALL_INTRA: return 2; |
84 | 0 | default: return 0; |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | 0 | unsigned int PickUsage(uint8_t raw) { |
89 | 0 | switch (raw % 3) { |
90 | 0 | case 0: return AOM_USAGE_GOOD_QUALITY; |
91 | 0 | case 1: return AOM_USAGE_REALTIME; |
92 | 0 | default: return AOM_USAGE_ALL_INTRA; |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | 0 | aom_rc_mode PickRcMode(uint8_t raw) { |
97 | 0 | switch (raw % 4) { |
98 | 0 | case 0: return AOM_VBR; |
99 | 0 | case 1: return AOM_CBR; |
100 | 0 | case 2: return AOM_CQ; |
101 | 0 | default: return AOM_Q; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | 0 | unsigned int PickDimension(uint16_t raw) { return 1u + (raw % kMaxDimension); } |
106 | | |
107 | 0 | int PickCpuUsed(unsigned int usage, uint8_t raw) { |
108 | 0 | const int max_cpu_used = usage == AOM_USAGE_REALTIME ? 11 : 9; |
109 | 0 | return raw % (max_cpu_used + 1); |
110 | 0 | } |
111 | | |
112 | 0 | bool DrainPackets(aom_codec_ctx_t *codec) { |
113 | 0 | bool got_data = false; |
114 | 0 | aom_codec_iter_t iter = nullptr; |
115 | 0 | while (aom_codec_get_cx_data(codec, &iter) != nullptr) { |
116 | 0 | got_data = true; |
117 | 0 | } |
118 | 0 | return got_data; |
119 | 0 | } |
120 | | |
121 | | bool InitDefaultConfig(aom_codec_iface_t *iface, unsigned int usage, |
122 | 0 | aom_codec_enc_cfg_t *cfg) { |
123 | 0 | const unsigned int usages[3] = { AOM_USAGE_GOOD_QUALITY, AOM_USAGE_REALTIME, |
124 | 0 | AOM_USAGE_ALL_INTRA }; |
125 | 0 | const unsigned int requested_index = UsageToIndex(usage); |
126 | 0 | for (unsigned int i = 0; i < 3; ++i) { |
127 | 0 | const unsigned int index = (requested_index + i) % 3; |
128 | 0 | if (aom_codec_enc_config_default(iface, cfg, usages[index]) == |
129 | 0 | AOM_CODEC_OK) { |
130 | 0 | return true; |
131 | 0 | } |
132 | 0 | } |
133 | 0 | return false; |
134 | 0 | } |
135 | | |
136 | | // Deterministic PRNG so that the frame content is derived from the fuzzer |
137 | | // input without requiring the reader to supply a full plane-sized chunk of |
138 | | // data for every frame. |
139 | 0 | uint32_t Rand(uint32_t *state) { |
140 | 0 | *state = *state * 1664525u + 1013904223u; |
141 | 0 | return *state; |
142 | 0 | } |
143 | | |
144 | | void FillPlane(uint8_t *plane, int stride, unsigned int width, |
145 | 0 | unsigned int height, uint32_t *state) { |
146 | 0 | for (unsigned int row = 0; row < height; ++row) { |
147 | 0 | for (unsigned int col = 0; col < width; ++col) { |
148 | 0 | plane[static_cast<size_t>(row) * stride + col] = |
149 | 0 | static_cast<uint8_t>(Rand(state) >> 24); |
150 | 0 | } |
151 | 0 | } |
152 | 0 | } |
153 | | |
154 | | bool BuildImage(FuzzReader *reader, unsigned int width, unsigned int height, |
155 | 0 | aom_image_t *image, unsigned int frame_index) { |
156 | 0 | if (aom_img_alloc(image, AOM_IMG_FMT_I420, width, height, 1) == nullptr) { |
157 | 0 | return false; |
158 | 0 | } |
159 | | |
160 | | // Seed a deterministic PRNG from the fuzzer input and the frame index so |
161 | | // that each encoded frame differs without consuming a plane-sized chunk of |
162 | | // input per frame. |
163 | 0 | uint32_t state = 0; |
164 | 0 | for (unsigned int i = 0; i < 4; ++i) { |
165 | 0 | state = (state << 8) | ReadU8(reader); |
166 | 0 | } |
167 | 0 | const uint32_t golden_ratio = 0x9e3779b9u; |
168 | 0 | state ^= frame_index * golden_ratio; |
169 | |
|
170 | 0 | const unsigned int uv_width = (width + 1) / 2; |
171 | 0 | const unsigned int uv_height = (height + 1) / 2; |
172 | 0 | FillPlane(image->planes[AOM_PLANE_Y], image->stride[AOM_PLANE_Y], width, |
173 | 0 | height, &state); |
174 | 0 | FillPlane(image->planes[AOM_PLANE_U], image->stride[AOM_PLANE_U], uv_width, |
175 | 0 | uv_height, &state); |
176 | 0 | FillPlane(image->planes[AOM_PLANE_V], image->stride[AOM_PLANE_V], uv_width, |
177 | 0 | uv_height, &state); |
178 | 0 | return true; |
179 | 0 | } |
180 | | |
181 | | void ApplyControls(aom_codec_ctx_t *codec, unsigned int usage, |
182 | | uint8_t mode_flags, uint8_t cpu_used_raw, uint8_t ctl0, |
183 | 0 | uint8_t ctl1) { |
184 | 0 | const int cpu_used = PickCpuUsed(usage, cpu_used_raw); |
185 | 0 | const unsigned int lossless = (mode_flags & kLossless) != 0; |
186 | 0 | const unsigned int row_mt = (mode_flags & kRowMt) != 0; |
187 | 0 | const unsigned int aq_mode = ctl0 % 4; |
188 | 0 | const unsigned int deltaq_mode = ctl1 % 4; |
189 | 0 | const unsigned int tile_columns = (ctl0 >> 4) & 0x3; |
190 | 0 | const unsigned int tile_rows = (ctl1 >> 4) & 0x3; |
191 | 0 | const unsigned int enable_cdef = (ctl0 >> 2) & 1; |
192 | 0 | const unsigned int enable_restoration = (ctl1 >> 2) & 1; |
193 | |
|
194 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AOME_SET_CPUUSED, cpu_used); |
195 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_LOSSLESS, lossless); |
196 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ROW_MT, row_mt); |
197 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_AQ_MODE, aq_mode); |
198 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_DELTAQ_MODE, deltaq_mode); |
199 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_COLUMNS, |
200 | 0 | tile_columns); |
201 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_ROWS, tile_rows); |
202 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_CDEF, enable_cdef); |
203 | 0 | (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_RESTORATION, |
204 | 0 | enable_restoration); |
205 | 0 | } |
206 | | |
207 | | bool EncodeFrame(aom_codec_ctx_t *codec, FuzzReader *reader, unsigned int width, |
208 | | unsigned int height, aom_codec_pts_t pts, |
209 | 0 | aom_enc_frame_flags_t flags) { |
210 | 0 | aom_image_t image; |
211 | 0 | memset(&image, 0, sizeof(image)); |
212 | |
|
213 | 0 | if (!BuildImage(reader, width, height, &image, pts)) { |
214 | 0 | return false; |
215 | 0 | } |
216 | | |
217 | 0 | const aom_codec_err_t enc_ret = |
218 | 0 | aom_codec_encode(codec, &image, pts, 1, flags); |
219 | 0 | if (enc_ret == AOM_CODEC_OK) { |
220 | 0 | (void)DrainPackets(codec); |
221 | 0 | } |
222 | |
|
223 | 0 | aom_img_free(&image); |
224 | 0 | return true; |
225 | 0 | } |
226 | | |
227 | | } // namespace |
228 | | |
229 | 0 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
230 | 0 | aom_codec_iface_t *iface = aom_codec_av1_cx(); |
231 | 0 | if (iface == nullptr || size < kMinHeaderSize) return 0; |
232 | | |
233 | 0 | FuzzReader reader = { data, size }; |
234 | 0 | const uint8_t mode_flags = ReadU8(&reader); |
235 | 0 | const uint8_t cpu_used_raw = ReadU8(&reader); |
236 | 0 | const uint8_t ctl0 = ReadU8(&reader); |
237 | 0 | const uint8_t ctl1 = ReadU8(&reader); |
238 | 0 | const unsigned int usage = PickUsage(mode_flags); |
239 | 0 | const unsigned int width0 = PickDimension(ReadU16(&reader)); |
240 | 0 | const unsigned int height0 = PickDimension(ReadU16(&reader)); |
241 | 0 | const unsigned int bitrate = 1u + (ReadU16(&reader) % 4000u); |
242 | 0 | const unsigned int width1 = PickDimension(ReadU16(&reader)); |
243 | 0 | const unsigned int height1 = PickDimension(ReadU16(&reader)); |
244 | 0 | const unsigned int threads0 = ReadU8(&reader) % 9; |
245 | 0 | const unsigned int threads1 = ReadU8(&reader) % 9; |
246 | |
|
247 | 0 | aom_codec_enc_cfg_t cfg; |
248 | 0 | if (!InitDefaultConfig(iface, usage, &cfg)) return 0; |
249 | | |
250 | 0 | cfg.g_usage = usage; |
251 | 0 | cfg.g_w = width0; |
252 | 0 | cfg.g_h = height0; |
253 | 0 | cfg.g_threads = threads0; |
254 | 0 | cfg.g_forced_max_frame_width = kMaxDimension; |
255 | 0 | cfg.g_forced_max_frame_height = kMaxDimension; |
256 | 0 | cfg.g_timebase.num = 1; |
257 | 0 | cfg.g_timebase.den = 1000000; |
258 | 0 | cfg.g_pass = AOM_RC_ONE_PASS; |
259 | 0 | cfg.g_lag_in_frames = 0; |
260 | 0 | cfg.g_error_resilient = |
261 | 0 | (mode_flags & kErrorResilient) ? AOM_ERROR_RESILIENT_DEFAULT : 0; |
262 | 0 | cfg.rc_end_usage = PickRcMode(ctl0); |
263 | 0 | cfg.rc_target_bitrate = bitrate; |
264 | |
|
265 | 0 | aom_codec_ctx_t codec; |
266 | 0 | memset(&codec, 0, sizeof(codec)); |
267 | 0 | if (aom_codec_enc_init(&codec, iface, &cfg, 0) != AOM_CODEC_OK) return 0; |
268 | | |
269 | 0 | const int cpu_used = PickCpuUsed(usage, cpu_used_raw); |
270 | 0 | ApplyControls(&codec, usage, mode_flags, cpu_used_raw, ctl0, ctl1); |
271 | |
|
272 | 0 | const unsigned int num_frames = kNumFramesForCpuUsed[cpu_used]; |
273 | 0 | bool config_failed = false; |
274 | 0 | for (unsigned int frame = 0; frame < num_frames; ++frame) { |
275 | 0 | aom_enc_frame_flags_t flags = 0; |
276 | 0 | if (frame == 1 && (mode_flags & kKeyFrameSecond)) { |
277 | 0 | flags |= AOM_EFLAG_FORCE_KF; |
278 | 0 | } |
279 | |
|
280 | 0 | unsigned int width = width0; |
281 | 0 | unsigned int height = height0; |
282 | 0 | if (mode_flags & kUseSecondResolution) { |
283 | | // Vary the switch frame based on the input so different encodings |
284 | | // exercise the resolution change at different points in the stream. |
285 | | // num_frames >= 50 for every cpu_used, so switch_frame and |
286 | | // restore_frame stay within [1, num_frames - 1]. |
287 | 0 | const unsigned int switch_frame = 1u + (ctl0 % (num_frames / 4u)); |
288 | 0 | const unsigned int restore_frame = |
289 | 0 | switch_frame + 1u + (ctl1 % (num_frames / 4u)); |
290 | 0 | if (frame == switch_frame) { |
291 | 0 | cfg.g_w = width1; |
292 | 0 | cfg.g_h = height1; |
293 | 0 | cfg.g_threads = threads1; |
294 | 0 | cfg.rc_end_usage = PickRcMode(ctl1); |
295 | 0 | if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) { |
296 | 0 | config_failed = true; |
297 | 0 | break; |
298 | 0 | } |
299 | 0 | width = width1; |
300 | 0 | height = height1; |
301 | 0 | } else if (frame == restore_frame) { |
302 | 0 | cfg.g_w = width0; |
303 | 0 | cfg.g_h = height0; |
304 | 0 | cfg.g_threads = threads0; |
305 | 0 | cfg.rc_end_usage = PickRcMode(ctl0); |
306 | 0 | if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) { |
307 | 0 | config_failed = true; |
308 | 0 | break; |
309 | 0 | } |
310 | 0 | width = width0; |
311 | 0 | height = height0; |
312 | 0 | } |
313 | 0 | } |
314 | | |
315 | 0 | if (!EncodeFrame(&codec, &reader, width, height, frame, flags)) { |
316 | 0 | config_failed = true; |
317 | 0 | break; |
318 | 0 | } |
319 | 0 | } |
320 | |
|
321 | 0 | if (!config_failed) { |
322 | 0 | while (aom_codec_encode(&codec, nullptr, 0, 0, 0) == AOM_CODEC_OK && |
323 | 0 | DrainPackets(&codec)) { |
324 | 0 | } |
325 | 0 | } |
326 | |
|
327 | 0 | (void)aom_codec_destroy(&codec); |
328 | 0 | return 0; |
329 | 0 | } |