Coverage Report

Created: 2026-06-14 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/src/codec_aom.c
Line
Count
Source
1
// Copyright 2019 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avif/internal.h"
5
6
// These are for libaom to deal with
7
#ifdef __clang__
8
#pragma clang diagnostic push
9
#pragma clang diagnostic ignored "-Wduplicate-enum"
10
#pragma clang diagnostic ignored "-Wextra-semi"
11
#pragma clang diagnostic ignored "-Wused-but-marked-unused"
12
#endif
13
14
#if defined(AVIF_CODEC_AOM_ENCODE)
15
#include "aom/aom_encoder.h"
16
#include "aom/aomcx.h"
17
#endif
18
19
#if defined(AVIF_CODEC_AOM_DECODE)
20
#include "aom/aom_decoder.h"
21
#include "aom/aomdx.h"
22
#endif
23
24
#ifdef __clang__
25
#pragma clang diagnostic pop
26
27
// This fixes complaints with aom_codec_control() and aom_img_fmt that are from libaom
28
#pragma clang diagnostic push
29
#pragma clang diagnostic ignored "-Wused-but-marked-unused"
30
#pragma clang diagnostic ignored "-Wassign-enum"
31
#endif
32
33
#include <assert.h>
34
#include <limits.h>
35
#include <stdlib.h>
36
#include <string.h>
37
38
#if defined(AVIF_CODEC_AOM_ENCODE)
39
// Detect whether the aom_codec_set_option() function is available. See aom/aom_codec.h
40
// in https://aomedia-review.googlesource.com/c/aom/+/126302.
41
#if AOM_CODEC_ABI_VERSION >= (6 + AOM_IMAGE_ABI_VERSION)
42
#define HAVE_AOM_CODEC_SET_OPTION 1
43
#endif
44
45
// Speeds 7-9 were added to all intra mode in https://aomedia-review.googlesource.com/c/aom/+/140624.
46
#if AOM_ENCODER_ABI_VERSION >= (10 + AOM_CODEC_ABI_VERSION + /*AOM_EXT_PART_ABI_VERSION=*/1)
47
#define ALL_INTRA_HAS_SPEEDS_7_TO_9 1
48
#endif
49
#endif
50
51
struct avifCodecInternal
52
{
53
#if defined(AVIF_CODEC_AOM_DECODE)
54
    avifBool decoderInitialized;
55
    aom_codec_ctx_t decoder;
56
    aom_codec_iter_t iter;
57
    aom_image_t * image;
58
#endif
59
60
#if defined(AVIF_CODEC_AOM_ENCODE)
61
    avifBool encoderInitialized;
62
    aom_codec_ctx_t encoder;
63
    struct aom_codec_enc_cfg cfg;
64
    avifPixelFormatInfo formatInfo;
65
    aom_img_fmt_t aomFormat;
66
    avifBool monochromeEnabled;
67
    // Whether 'tuning' (of the specified distortion metric) was set with an
68
    // avifEncoderSetCodecSpecificOption(encoder, "tune", value) call.
69
    avifBool tuningSet;
70
    uint32_t currentLayer;
71
#endif
72
};
73
74
static void aomCodecDestroyInternal(avifCodec * codec)
75
12.2k
{
76
12.2k
#if defined(AVIF_CODEC_AOM_DECODE)
77
12.2k
    if (codec->internal->decoderInitialized) {
78
12.1k
        aom_codec_destroy(&codec->internal->decoder);
79
12.1k
    }
80
12.2k
#endif
81
82
12.2k
#if defined(AVIF_CODEC_AOM_ENCODE)
83
12.2k
    if (codec->internal->encoderInitialized) {
84
0
        aom_codec_destroy(&codec->internal->encoder);
85
0
    }
86
12.2k
#endif
87
88
12.2k
    avifFree(codec->internal);
89
12.2k
}
90
91
#if defined(AVIF_CODEC_AOM_DECODE)
92
93
static avifBool aomCodecGetNextImage(struct avifCodec * codec,
94
                                     const avifDecodeSample * sample,
95
                                     avifBool alpha,
96
                                     avifBool * isLimitedRangeAlpha,
97
                                     avifImage * image)
98
12.1k
{
99
12.1k
    if (!codec->internal->decoderInitialized) {
100
12.1k
        aom_codec_dec_cfg_t cfg;
101
12.1k
        memset(&cfg, 0, sizeof(aom_codec_dec_cfg_t));
102
12.1k
        cfg.threads = codec->maxThreads;
103
12.1k
        cfg.allow_lowbitdepth = 1;
104
105
12.1k
        aom_codec_iface_t * decoder_interface = aom_codec_av1_dx();
106
12.1k
        if (aom_codec_dec_init(&codec->internal->decoder, decoder_interface, &cfg, 0)) {
107
0
            return AVIF_FALSE;
108
0
        }
109
12.1k
        codec->internal->decoderInitialized = AVIF_TRUE;
110
111
12.1k
        if (aom_codec_control(&codec->internal->decoder, AV1D_SET_OUTPUT_ALL_LAYERS, codec->allLayers)) {
112
0
            return AVIF_FALSE;
113
0
        }
114
12.1k
        if (aom_codec_control(&codec->internal->decoder, AV1D_SET_OPERATING_POINT, codec->operatingPoint)) {
115
0
            return AVIF_FALSE;
116
0
        }
117
118
12.1k
        codec->internal->iter = NULL;
119
12.1k
    }
120
121
12.1k
    aom_image_t * nextFrame = NULL;
122
12.1k
    uint8_t spatialID = AVIF_SPATIAL_ID_UNSET;
123
14.3k
    for (;;) {
124
14.3k
        nextFrame = aom_codec_get_frame(&codec->internal->decoder, &codec->internal->iter);
125
14.3k
        if (nextFrame) {
126
2.15k
            if (spatialID != AVIF_SPATIAL_ID_UNSET) {
127
                // This requires libaom v3.1.2 or later, which has the fix for
128
                // https://crbug.com/aomedia/2993.
129
75
                if (spatialID == nextFrame->spatial_id) {
130
                    // Found the correct spatial_id.
131
1
                    break;
132
1
                }
133
2.08k
            } else {
134
                // Got an image!
135
2.08k
                break;
136
2.08k
            }
137
12.2k
        } else if (sample) {
138
12.1k
            codec->internal->iter = NULL;
139
12.1k
            if (aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL)) {
140
10.0k
                return AVIF_FALSE;
141
10.0k
            }
142
2.12k
            spatialID = sample->spatialID;
143
2.12k
            sample = NULL;
144
2.12k
        } else {
145
44
            break;
146
44
        }
147
14.3k
    }
148
149
2.12k
    if (nextFrame) {
150
2.08k
        codec->internal->image = nextFrame;
151
2.08k
    } else {
152
44
        if (alpha && codec->internal->image) {
153
            // Special case: reuse last alpha frame
154
44
        } else {
155
44
            return AVIF_FALSE;
156
44
        }
157
44
    }
158
159
2.08k
    avifBool isColor = !alpha;
160
2.08k
    if (isColor) {
161
        // Color (YUV) planes - set image to correct size / format, fill color
162
163
2.03k
        avifPixelFormat yuvFormat = AVIF_PIXEL_FORMAT_NONE;
164
2.03k
        switch (codec->internal->image->fmt) {
165
242
            case AOM_IMG_FMT_I420:
166
242
            case AOM_IMG_FMT_AOMI420:
167
543
            case AOM_IMG_FMT_I42016:
168
543
                yuvFormat = AVIF_PIXEL_FORMAT_YUV420;
169
543
                break;
170
70
            case AOM_IMG_FMT_I422:
171
140
            case AOM_IMG_FMT_I42216:
172
140
                yuvFormat = AVIF_PIXEL_FORMAT_YUV422;
173
140
                break;
174
1.00k
            case AOM_IMG_FMT_I444:
175
1.34k
            case AOM_IMG_FMT_I44416:
176
1.34k
                yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
177
1.34k
                break;
178
0
            case AOM_IMG_FMT_NONE:
179
0
#if defined(AOM_HAVE_IMG_FMT_NV12)
180
            // Although the libaom encoder supports the NV12 image format as an input format, the
181
            // libaom decoder does not support NV12 as an output format.
182
0
            case AOM_IMG_FMT_NV12:
183
0
#endif
184
0
            case AOM_IMG_FMT_YV12:
185
0
            case AOM_IMG_FMT_AOMYV12:
186
0
            case AOM_IMG_FMT_YV1216:
187
0
            default:
188
0
                return AVIF_FALSE;
189
2.03k
        }
190
2.03k
        if (codec->internal->image->monochrome) {
191
314
            yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
192
314
        }
193
194
2.03k
        if (image->width && image->height) {
195
0
            if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
196
0
                (image->depth != codec->internal->image->bit_depth) || (image->yuvFormat != yuvFormat)) {
197
                // Throw it all out
198
0
                avifImageFreePlanes(image, AVIF_PLANES_ALL);
199
0
            }
200
0
        }
201
2.03k
        image->width = codec->internal->image->d_w;
202
2.03k
        image->height = codec->internal->image->d_h;
203
2.03k
        image->depth = codec->internal->image->bit_depth;
204
205
2.03k
        image->yuvFormat = yuvFormat;
206
2.03k
        image->yuvRange = (codec->internal->image->range == AOM_CR_STUDIO_RANGE) ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL;
207
2.03k
        image->yuvChromaSamplePosition = (avifChromaSamplePosition)codec->internal->image->csp;
208
209
2.03k
        image->colorPrimaries = (avifColorPrimaries)codec->internal->image->cp;
210
2.03k
        image->transferCharacteristics = (avifTransferCharacteristics)codec->internal->image->tc;
211
2.03k
        image->matrixCoefficients = (avifMatrixCoefficients)codec->internal->image->mc;
212
213
        // Steal the pointers from the decoder's image directly
214
2.03k
        avifImageFreePlanes(image, AVIF_PLANES_YUV);
215
2.03k
        int yuvPlaneCount = (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
216
7.50k
        for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
217
5.46k
            image->yuvPlanes[yuvPlane] = codec->internal->image->planes[yuvPlane];
218
5.46k
            image->yuvRowBytes[yuvPlane] = codec->internal->image->stride[yuvPlane];
219
5.46k
        }
220
2.03k
        image->imageOwnsYUVPlanes = AVIF_FALSE;
221
2.03k
    } else {
222
        // Alpha plane - ensure image is correct size, fill color
223
224
51
        if (image->width && image->height) {
225
0
            if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
226
0
                (image->depth != codec->internal->image->bit_depth)) {
227
                // Alpha plane doesn't match previous alpha plane decode, bail out
228
0
                return AVIF_FALSE;
229
0
            }
230
0
        }
231
51
        image->width = codec->internal->image->d_w;
232
51
        image->height = codec->internal->image->d_h;
233
51
        image->depth = codec->internal->image->bit_depth;
234
235
51
        avifImageFreePlanes(image, AVIF_PLANES_A);
236
51
        image->alphaPlane = codec->internal->image->planes[0];
237
51
        image->alphaRowBytes = codec->internal->image->stride[0];
238
51
        *isLimitedRangeAlpha = (codec->internal->image->range == AOM_CR_STUDIO_RANGE);
239
51
        image->imageOwnsAlphaPlane = AVIF_FALSE;
240
51
    }
241
242
2.08k
    return AVIF_TRUE;
243
2.08k
}
244
#endif // defined(AVIF_CODEC_AOM_DECODE)
245
246
#if defined(AVIF_CODEC_AOM_ENCODE)
247
248
static aom_img_fmt_t avifImageCalcAOMFmt(const avifImage * image, avifBool alpha)
249
0
{
250
0
    aom_img_fmt_t fmt;
251
0
    if (alpha) {
252
        // We're going monochrome, who cares about chroma quality
253
0
        fmt = AOM_IMG_FMT_I420;
254
0
    } else {
255
0
        switch (image->yuvFormat) {
256
0
            case AVIF_PIXEL_FORMAT_YUV444:
257
0
                fmt = AOM_IMG_FMT_I444;
258
0
                break;
259
0
            case AVIF_PIXEL_FORMAT_YUV422:
260
0
                fmt = AOM_IMG_FMT_I422;
261
0
                break;
262
0
            case AVIF_PIXEL_FORMAT_YUV420:
263
0
            case AVIF_PIXEL_FORMAT_YUV400:
264
0
                fmt = AOM_IMG_FMT_I420;
265
0
                break;
266
0
            case AVIF_PIXEL_FORMAT_NONE:
267
0
            case AVIF_PIXEL_FORMAT_COUNT:
268
0
            default:
269
0
                return AOM_IMG_FMT_NONE;
270
0
        }
271
0
    }
272
273
0
    if (image->depth > 8) {
274
0
        fmt |= AOM_IMG_FMT_HIGHBITDEPTH;
275
0
    }
276
277
0
    return fmt;
278
0
}
279
280
#if !defined(HAVE_AOM_CODEC_SET_OPTION)
281
static avifBool aomOptionParseInt(const char * str, int * val)
282
{
283
    char * endptr;
284
    const long rawval = strtol(str, &endptr, 10);
285
286
    if (str[0] != '\0' && endptr[0] == '\0' && rawval >= INT_MIN && rawval <= INT_MAX) {
287
        *val = (int)rawval;
288
        return AVIF_TRUE;
289
    }
290
291
    return AVIF_FALSE;
292
}
293
294
static avifBool aomOptionParseUInt(const char * str, unsigned int * val)
295
{
296
    char * endptr;
297
    const unsigned long rawval = strtoul(str, &endptr, 10);
298
299
    if (str[0] != '\0' && endptr[0] == '\0' && rawval <= UINT_MAX) {
300
        *val = (unsigned int)rawval;
301
        return AVIF_TRUE;
302
    }
303
304
    return AVIF_FALSE;
305
}
306
#endif // !defined(HAVE_AOM_CODEC_SET_OPTION)
307
308
struct aomOptionEnumList
309
{
310
    const char * name;
311
    int val;
312
};
313
314
static avifBool aomOptionParseEnum(const char * str, const struct aomOptionEnumList * enums, int * val)
315
0
{
316
0
    const struct aomOptionEnumList * listptr;
317
0
    long int rawval;
318
0
    char * endptr;
319
320
    // First see if the value can be parsed as a raw value.
321
0
    rawval = strtol(str, &endptr, 10);
322
0
    if (str[0] != '\0' && endptr[0] == '\0') {
323
        // Got a raw value, make sure it's valid.
324
0
        for (listptr = enums; listptr->name; listptr++)
325
0
            if (listptr->val == rawval) {
326
0
                *val = (int)rawval;
327
0
                return AVIF_TRUE;
328
0
            }
329
0
    }
330
331
    // Next see if it can be parsed as a string.
332
0
    for (listptr = enums; listptr->name; listptr++) {
333
0
        if (!strcmp(str, listptr->name)) {
334
0
            *val = listptr->val;
335
0
            return AVIF_TRUE;
336
0
        }
337
0
    }
338
339
0
    return AVIF_FALSE;
340
0
}
341
342
static const struct aomOptionEnumList endUsageEnum[] = { //
343
    { "vbr", AOM_VBR },                                  // Variable Bit Rate (VBR) mode
344
    { "cbr", AOM_CBR },                                  // Constant Bit Rate (CBR) mode
345
    { "cq", AOM_CQ },                                    // Constrained Quality (CQ) mode
346
    { "q", AOM_Q },                                      // Constant Quality (Q) mode
347
    { NULL, 0 }
348
};
349
350
// Returns true if <key> equals <name> or <prefix><name>, where <prefix> is "color:" or "alpha:"
351
// or the abbreviated form "c:" or "a:".
352
static avifBool avifKeyEqualsName(const char * key, const char * name, avifBool alpha)
353
0
{
354
0
    const char * prefix = alpha ? "alpha:" : "color:";
355
0
    size_t prefixLen = 6;
356
0
    const char * shortPrefix = alpha ? "a:" : "c:";
357
0
    size_t shortPrefixLen = 2;
358
0
    return !strcmp(key, name) || (!strncmp(key, prefix, prefixLen) && !strcmp(key + prefixLen, name)) ||
359
0
           (!strncmp(key, shortPrefix, shortPrefixLen) && !strcmp(key + shortPrefixLen, name));
360
0
}
361
362
static avifBool avifProcessAOMOptionsPreInit(avifCodec * codec, avifBool alpha, struct aom_codec_enc_cfg * cfg)
363
0
{
364
0
    for (uint32_t i = 0; i < codec->csOptions->count; ++i) {
365
0
        avifCodecSpecificOption * entry = &codec->csOptions->entries[i];
366
0
        int val;
367
0
        if (avifKeyEqualsName(entry->key, "end-usage", alpha)) { // Rate control mode
368
0
            if (!aomOptionParseEnum(entry->value, endUsageEnum, &val)) {
369
0
                avifDiagnosticsPrintf(codec->diag, "Invalid value for end-usage: %s", entry->value);
370
0
                return AVIF_FALSE;
371
0
            }
372
0
            cfg->rc_end_usage = val;
373
0
        }
374
0
    }
375
0
    return AVIF_TRUE;
376
0
}
377
378
#if !defined(HAVE_AOM_CODEC_SET_OPTION)
379
typedef enum
380
{
381
    AVIF_AOM_OPTION_NUL = 0,
382
    AVIF_AOM_OPTION_STR,
383
    AVIF_AOM_OPTION_INT,
384
    AVIF_AOM_OPTION_UINT,
385
    AVIF_AOM_OPTION_ENUM,
386
} aomOptionType;
387
388
struct aomOptionDef
389
{
390
    const char * name;
391
    int controlId;
392
    aomOptionType type;
393
    // If type is AVIF_AOM_OPTION_ENUM, this must be set. Otherwise should be NULL.
394
    const struct aomOptionEnumList * enums;
395
};
396
397
static const struct aomOptionEnumList tuningEnum[] = { //
398
    { "psnr", AOM_TUNE_PSNR },                         //
399
    { "ssim", AOM_TUNE_SSIM },                         //
400
    { NULL, 0 }
401
};
402
403
static const struct aomOptionDef aomOptionDefs[] = {
404
    // Adaptive quantization mode
405
    { "aq-mode", AV1E_SET_AQ_MODE, AVIF_AOM_OPTION_UINT, NULL },
406
    // Constant/Constrained Quality level
407
    { "cq-level", AOME_SET_CQ_LEVEL, AVIF_AOM_OPTION_UINT, NULL },
408
    // Enable delta quantization in chroma planes
409
    { "enable-chroma-deltaq", AV1E_SET_ENABLE_CHROMA_DELTAQ, AVIF_AOM_OPTION_INT, NULL },
410
    // Bias towards block sharpness in rate-distortion optimization of transform coefficients
411
    { "sharpness", AOME_SET_SHARPNESS, AVIF_AOM_OPTION_UINT, NULL },
412
    // Tune distortion metric
413
    { "tune", AOME_SET_TUNING, AVIF_AOM_OPTION_ENUM, tuningEnum },
414
    // Film grain test vector
415
    { "film-grain-test", AV1E_SET_FILM_GRAIN_TEST_VECTOR, AVIF_AOM_OPTION_INT, NULL },
416
    // Film grain table file
417
    { "film-grain-table", AV1E_SET_FILM_GRAIN_TABLE, AVIF_AOM_OPTION_STR, NULL },
418
419
    // Sentinel
420
    { NULL, 0, AVIF_AOM_OPTION_NUL, NULL }
421
};
422
#endif // !defined(HAVE_AOM_CODEC_SET_OPTION)
423
424
static avifBool avifProcessAOMOptionsPostInit(avifCodec * codec, avifBool alpha)
425
0
{
426
0
    for (uint32_t i = 0; i < codec->csOptions->count; ++i) {
427
0
        avifCodecSpecificOption * entry = &codec->csOptions->entries[i];
428
        // Skip options for the other kind of plane.
429
0
        const char * otherPrefix = alpha ? "color:" : "alpha:";
430
0
        size_t otherPrefixLen = 6;
431
0
        const char * otherShortPrefix = alpha ? "c:" : "a:";
432
0
        size_t otherShortPrefixLen = 2;
433
0
        if (!strncmp(entry->key, otherPrefix, otherPrefixLen) || !strncmp(entry->key, otherShortPrefix, otherShortPrefixLen)) {
434
0
            continue;
435
0
        }
436
437
        // Skip options processed by avifProcessAOMOptionsPreInit.
438
0
        if (avifKeyEqualsName(entry->key, "end-usage", alpha)) {
439
0
            continue;
440
0
        }
441
442
0
#if defined(HAVE_AOM_CODEC_SET_OPTION)
443
0
        const char * prefix = alpha ? "alpha:" : "color:";
444
0
        size_t prefixLen = 6;
445
0
        const char * shortPrefix = alpha ? "a:" : "c:";
446
0
        size_t shortPrefixLen = 2;
447
0
        const char * key = entry->key;
448
0
        if (!strncmp(key, prefix, prefixLen)) {
449
0
            key += prefixLen;
450
0
        } else if (!strncmp(key, shortPrefix, shortPrefixLen)) {
451
0
            key += shortPrefixLen;
452
0
        }
453
0
        if (aom_codec_set_option(&codec->internal->encoder, key, entry->value) != AOM_CODEC_OK) {
454
0
            avifDiagnosticsPrintf(codec->diag,
455
0
                                  "aom_codec_set_option(\"%s\", \"%s\") failed: %s: %s",
456
0
                                  key,
457
0
                                  entry->value,
458
0
                                  aom_codec_error(&codec->internal->encoder),
459
0
                                  aom_codec_error_detail(&codec->internal->encoder));
460
0
            return AVIF_FALSE;
461
0
        }
462
0
        if (!strcmp(key, "tune")) {
463
0
            codec->internal->tuningSet = AVIF_TRUE;
464
0
        }
465
#else  // !defined(HAVE_AOM_CODEC_SET_OPTION)
466
        avifBool match = AVIF_FALSE;
467
        for (int j = 0; aomOptionDefs[j].name; ++j) {
468
            if (avifKeyEqualsName(entry->key, aomOptionDefs[j].name, alpha)) {
469
                match = AVIF_TRUE;
470
                avifBool success = AVIF_FALSE;
471
                int valInt;
472
                unsigned int valUInt;
473
                switch (aomOptionDefs[j].type) {
474
                    case AVIF_AOM_OPTION_NUL:
475
                        success = AVIF_FALSE;
476
                        break;
477
                    case AVIF_AOM_OPTION_STR:
478
                        success = aom_codec_control(&codec->internal->encoder, aomOptionDefs[j].controlId, entry->value) == AOM_CODEC_OK;
479
                        break;
480
                    case AVIF_AOM_OPTION_INT:
481
                        success = aomOptionParseInt(entry->value, &valInt) &&
482
                                  aom_codec_control(&codec->internal->encoder, aomOptionDefs[j].controlId, valInt) == AOM_CODEC_OK;
483
                        break;
484
                    case AVIF_AOM_OPTION_UINT:
485
                        success = aomOptionParseUInt(entry->value, &valUInt) &&
486
                                  aom_codec_control(&codec->internal->encoder, aomOptionDefs[j].controlId, valUInt) == AOM_CODEC_OK;
487
                        break;
488
                    case AVIF_AOM_OPTION_ENUM:
489
                        success = aomOptionParseEnum(entry->value, aomOptionDefs[j].enums, &valInt) &&
490
                                  aom_codec_control(&codec->internal->encoder, aomOptionDefs[j].controlId, valInt) == AOM_CODEC_OK;
491
                        break;
492
                }
493
                if (!success) {
494
                    return AVIF_FALSE;
495
                }
496
                if (aomOptionDefs[j].controlId == AOME_SET_TUNING) {
497
                    codec->internal->tuningSet = AVIF_TRUE;
498
                }
499
                break;
500
            }
501
        }
502
        if (!match) {
503
            return AVIF_FALSE;
504
        }
505
#endif // defined(HAVE_AOM_CODEC_SET_OPTION)
506
0
    }
507
0
    return AVIF_TRUE;
508
0
}
509
510
struct aomScalingModeMapList
511
{
512
    avifFraction avifMode;
513
    AOM_SCALING_MODE aomMode;
514
};
515
516
static const struct aomScalingModeMapList scalingModeMap[] = {
517
    { { 1, 1 }, AOME_NORMAL },    { { 1, 2 }, AOME_ONETWO },    { { 1, 4 }, AOME_ONEFOUR },  { { 1, 8 }, AOME_ONEEIGHT },
518
    { { 3, 4 }, AOME_THREEFOUR }, { { 3, 5 }, AOME_THREEFIVE }, { { 4, 5 }, AOME_FOURFIVE },
519
};
520
521
static const int scalingModeMapSize = sizeof(scalingModeMap) / sizeof(scalingModeMap[0]);
522
523
static avifBool avifFindAOMScalingMode(const avifFraction * avifMode, AOM_SCALING_MODE * aomMode)
524
0
{
525
0
    avifFraction simplifiedFraction = *avifMode;
526
0
    avifFractionSimplify(&simplifiedFraction);
527
0
    for (int i = 0; i < scalingModeMapSize; ++i) {
528
0
        if (scalingModeMap[i].avifMode.n == simplifiedFraction.n && scalingModeMap[i].avifMode.d == simplifiedFraction.d) {
529
0
            *aomMode = scalingModeMap[i].aomMode;
530
0
            return AVIF_TRUE;
531
0
        }
532
0
    }
533
534
0
    return AVIF_FALSE;
535
0
}
536
537
static avifBool doesLevelMatch(int width, int height, int levelWidth, int levelHeight, int levelDimMult)
538
0
{
539
0
    const int64_t levelLumaPels = (int64_t)levelWidth * levelHeight;
540
0
    const int64_t lumaPels = (int64_t)width * height;
541
0
    return lumaPels <= levelLumaPels && width <= levelWidth * levelDimMult && height <= levelHeight * levelDimMult;
542
0
}
543
544
static avifBool aomCodecEncodeFinish(avifCodec * codec, avifCodecEncodeOutput * output);
545
546
static avifResult aomCodecEncodeImage(avifCodec * codec,
547
                                      avifEncoder * encoder,
548
                                      const avifImage * image,
549
                                      avifBool alpha,
550
                                      int tileRowsLog2,
551
                                      int tileColsLog2,
552
                                      int quantizer,
553
                                      avifEncoderChanges encoderChanges,
554
                                      avifBool disableLaggedOutput,
555
                                      avifAddImageFlags addImageFlags,
556
                                      avifCodecEncodeOutput * output)
557
0
{
558
0
    struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;
559
0
    avifBool quantizerUpdated = AVIF_FALSE;
560
561
    // For encoder->scalingMode.horizontal and encoder->scalingMode.vertical to take effect in AOM
562
    // encoder, config should be applied for each frame, so we don't care about changes on these
563
    // two fields.
564
0
    encoderChanges &= ~AVIF_ENCODER_CHANGE_SCALING_MODE;
565
566
0
    if (!codec->internal->encoderInitialized) {
567
        // Map encoder speed to AOM usage + CpuUsed:
568
        // Speed  0: GoodQuality CpuUsed 0
569
        // Speed  1: GoodQuality CpuUsed 1
570
        // Speed  2: GoodQuality CpuUsed 2
571
        // Speed  3: GoodQuality CpuUsed 3
572
        // Speed  4: GoodQuality CpuUsed 4
573
        // Speed  5: GoodQuality CpuUsed 5
574
        // Speed  6: GoodQuality CpuUsed 6
575
        // Speed  7: RealTime    CpuUsed 7
576
        // Speed  8: RealTime    CpuUsed 8
577
        // Speed  9: RealTime    CpuUsed 9
578
        // Speed 10: RealTime    CpuUsed 9
579
0
        unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
580
        // Use the new AOM_USAGE_ALL_INTRA (added in https://crbug.com/aomedia/2959) for still
581
        // image encoding if it is available.
582
0
#if defined(AOM_USAGE_ALL_INTRA)
583
0
        if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
584
0
            aomUsage = AOM_USAGE_ALL_INTRA;
585
0
        }
586
0
#endif
587
0
        int aomCpuUsed = -1;
588
0
        if (encoder->speed != AVIF_SPEED_DEFAULT) {
589
0
            aomCpuUsed = AVIF_CLAMP(encoder->speed, 0, 9);
590
0
            if (aomCpuUsed >= 7) {
591
0
#if defined(AOM_USAGE_ALL_INTRA) && defined(ALL_INTRA_HAS_SPEEDS_7_TO_9)
592
0
                if (!(addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) {
593
0
                    aomUsage = AOM_USAGE_REALTIME;
594
0
                }
595
#else
596
                aomUsage = AOM_USAGE_REALTIME;
597
#endif
598
0
            }
599
0
        }
600
601
        // aom_codec.h says: aom_codec_version() == (major<<16 | minor<<8 | patch)
602
0
        static const int aomVersion_2_0_0 = (2 << 16);
603
0
        const int aomVersion = aom_codec_version();
604
0
        if ((aomVersion < aomVersion_2_0_0) && (image->depth > 8)) {
605
            // Due to a known issue with libaom v1.0.0-errata1-avif, 10bpc and
606
            // 12bpc image encodes will call the wrong variant of
607
            // aom_subtract_block when cpu-used is 7 or 8, and crash. Until we get
608
            // a new tagged release from libaom with the fix and can verify we're
609
            // running with that version of libaom, we must avoid using
610
            // cpu-used=7/8 on any >8bpc image encodes.
611
            //
612
            // Context:
613
            //   * https://github.com/AOMediaCodec/libavif/issues/49
614
            //   * https://bugs.chromium.org/p/aomedia/issues/detail?id=2587
615
            //
616
            // Continued bug tracking here:
617
            //   * https://github.com/AOMediaCodec/libavif/issues/56
618
619
0
            if (aomCpuUsed > 6) {
620
0
                aomCpuUsed = 6;
621
0
            }
622
0
        }
623
624
0
        codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
625
0
        if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
626
0
            return AVIF_RESULT_UNKNOWN_ERROR;
627
0
        }
628
629
0
        avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
630
631
0
        aom_codec_iface_t * encoderInterface = aom_codec_av1_cx();
632
0
        aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, cfg, aomUsage);
633
0
        if (err != AOM_CODEC_OK) {
634
0
            avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
635
0
            return AVIF_RESULT_UNKNOWN_ERROR;
636
0
        }
637
638
        // Set our own default cfg->rc_end_usage value, which may differ from libaom's default.
639
0
        switch (aomUsage) {
640
0
            case AOM_USAGE_GOOD_QUALITY:
641
                // libaom's default is AOM_VBR. Change the default to AOM_Q since we don't need to
642
                // hit a certain target bit rate. It's easier to control the worst quality in Q
643
                // mode.
644
0
                cfg->rc_end_usage = AOM_Q;
645
0
                break;
646
0
            case AOM_USAGE_REALTIME:
647
                // For real-time mode we need to use CBR rate control mode. AOM_Q doesn't fit the
648
                // rate control requirements for real-time mode. CBR does.
649
0
                cfg->rc_end_usage = AOM_CBR;
650
0
                break;
651
0
#if defined(AOM_USAGE_ALL_INTRA)
652
0
            case AOM_USAGE_ALL_INTRA:
653
0
                cfg->rc_end_usage = AOM_Q;
654
0
                break;
655
0
#endif
656
0
        }
657
658
        // Profile 0.  8-bit and 10-bit 4:2:0 and 4:0:0 only.
659
        // Profile 1.  8-bit and 10-bit 4:4:4
660
        // Profile 2.  8-bit and 10-bit 4:2:2
661
        //            12-bit 4:0:0, 4:2:0, 4:2:2 and 4:4:4
662
0
        uint8_t seqProfile = 0;
663
0
        if (image->depth == 12) {
664
            // Only seqProfile 2 can handle 12 bit
665
0
            seqProfile = 2;
666
0
        } else {
667
            // 8-bit or 10-bit
668
669
0
            if (alpha) {
670
0
                seqProfile = 0;
671
0
            } else {
672
0
                switch (image->yuvFormat) {
673
0
                    case AVIF_PIXEL_FORMAT_YUV444:
674
0
                        seqProfile = 1;
675
0
                        break;
676
0
                    case AVIF_PIXEL_FORMAT_YUV422:
677
0
                        seqProfile = 2;
678
0
                        break;
679
0
                    case AVIF_PIXEL_FORMAT_YUV420:
680
0
                        seqProfile = 0;
681
0
                        break;
682
0
                    case AVIF_PIXEL_FORMAT_YUV400:
683
0
                        seqProfile = 0;
684
0
                        break;
685
0
                    case AVIF_PIXEL_FORMAT_NONE:
686
0
                    case AVIF_PIXEL_FORMAT_COUNT:
687
0
                    default:
688
0
                        break;
689
0
                }
690
0
            }
691
0
        }
692
693
0
        cfg->g_profile = seqProfile;
694
0
        cfg->g_bit_depth = image->depth;
695
0
        cfg->g_input_bit_depth = image->depth;
696
0
        cfg->g_w = image->width;
697
0
        cfg->g_h = image->height;
698
699
        // Detect the libaom v3.6.0 bug described in
700
        // https://crbug.com/aomedia/2871#c12. See the changes to
701
        // av1/encoder/encoder.c in
702
        // https://aomedia-review.googlesource.com/c/aom/+/174421.
703
0
        static const int aomVersion_3_6_0 = (3 << 16) | (6 << 8);
704
0
        if (aomVersion == aomVersion_3_6_0) {
705
            // Detect the use of levels 7.x and 8.x, which use a larger max
706
            // tile area (4096 * 4608) than MAX_TILE_AREA (4096 * 2304). The
707
            // larger max tile area may not result in a different bitstream
708
            // (see the tile_info() function in the AV1 spec, Section 5.9.15),
709
            // so this is just a necessary condition for the bug.
710
0
            if (!doesLevelMatch(image->width, image->height, 8192, 4352, 2) &&
711
0
                (doesLevelMatch(image->width, image->height, 16384, 8704, 2) ||
712
0
                 doesLevelMatch(image->width, image->height, 32768, 17408, 2))) {
713
0
                avifDiagnosticsPrintf(codec->diag, "Detected libaom v3.6.0 bug with large images. Upgrade to libaom v3.6.1 or later.");
714
0
                return AVIF_RESULT_UNKNOWN_ERROR;
715
0
            }
716
0
        }
717
718
0
        if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
719
            // Set the maximum number of frames to encode to 1. This instructs
720
            // libaom to set still_picture and reduced_still_picture_header to
721
            // 1 in AV1 sequence headers.
722
0
            cfg->g_limit = 1;
723
724
            // Use the default settings of the new AOM_USAGE_ALL_INTRA (added in
725
            // https://crbug.com/aomedia/2959).
726
            //
727
            // Set g_lag_in_frames to 0 to reduce the number of frame buffers
728
            // (from 20 to 2) in libaom's lookahead structure. This reduces
729
            // memory consumption when encoding a single image.
730
0
            cfg->g_lag_in_frames = 0;
731
            // Disable automatic placement of key frames by the encoder.
732
0
            cfg->kf_mode = AOM_KF_DISABLED;
733
            // Tell libaom that all frames will be key frames.
734
0
            cfg->kf_max_dist = 0;
735
0
        } else {
736
0
            if (encoder->keyframeInterval > 0) {
737
0
                cfg->kf_max_dist = encoder->keyframeInterval;
738
0
            }
739
0
        }
740
0
        if (encoder->extraLayerCount > 0) {
741
0
            cfg->g_limit = encoder->extraLayerCount + 1;
742
            // For layered image, disable lagged encoding to always get output
743
            // frame for each input frame.
744
0
            cfg->g_lag_in_frames = 0;
745
0
        }
746
0
        if (disableLaggedOutput) {
747
0
            cfg->g_lag_in_frames = 0;
748
0
        }
749
0
        if (encoder->maxThreads > 1) {
750
            // libaom fails if cfg->g_threads is greater than 64 threads. See MAX_NUM_THREADS in
751
            // aom/aom_util/aom_thread.h.
752
0
            cfg->g_threads = AVIF_MIN(encoder->maxThreads, 64);
753
0
        }
754
755
0
        codec->internal->monochromeEnabled = AVIF_FALSE;
756
0
        if (aomVersion > aomVersion_2_0_0) {
757
            // There exists a bug in libaom's chroma_check() function where it will attempt to
758
            // access nonexistent UV planes when encoding monochrome at faster libavif "speeds". It
759
            // was fixed shortly after the 2.0.0 libaom release, and the fix exists in both the
760
            // master and applejack branches. This ensures that the next version *after* 2.0.0 will
761
            // have the fix, and we must avoid cfg->monochrome until then.
762
            //
763
            // Bugfix Change-Id: https://aomedia-review.googlesource.com/q/I26a39791f820b4d4e1d63ff7141f594c3c7181f5
764
765
0
            if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
766
0
                codec->internal->monochromeEnabled = AVIF_TRUE;
767
0
                cfg->monochrome = 1;
768
0
            }
769
0
        }
770
771
0
        if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) {
772
0
            return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
773
0
        }
774
775
0
        int minQuantizer;
776
0
        int maxQuantizer;
777
0
        if (alpha) {
778
0
            minQuantizer = encoder->minQuantizerAlpha;
779
0
            maxQuantizer = encoder->maxQuantizerAlpha;
780
0
        } else {
781
0
            minQuantizer = encoder->minQuantizer;
782
0
            maxQuantizer = encoder->maxQuantizer;
783
0
        }
784
0
        minQuantizer = AVIF_CLAMP(minQuantizer, 0, 63);
785
0
        maxQuantizer = AVIF_CLAMP(maxQuantizer, 0, 63);
786
0
        if ((cfg->rc_end_usage == AOM_VBR) || (cfg->rc_end_usage == AOM_CBR)) {
787
            // cq-level is ignored in these two end-usage modes, so adjust minQuantizer and
788
            // maxQuantizer to the target quantizer.
789
0
            if (quantizer == AVIF_QUANTIZER_LOSSLESS) {
790
0
                minQuantizer = AVIF_QUANTIZER_LOSSLESS;
791
0
                maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
792
0
            } else {
793
0
                minQuantizer = AVIF_MAX(quantizer - 4, minQuantizer);
794
0
                maxQuantizer = AVIF_MIN(quantizer + 4, maxQuantizer);
795
0
            }
796
0
        }
797
0
        cfg->rc_min_quantizer = minQuantizer;
798
0
        cfg->rc_max_quantizer = maxQuantizer;
799
0
        quantizerUpdated = AVIF_TRUE;
800
801
0
        aom_codec_flags_t encoderFlags = 0;
802
0
        if (image->depth > 8) {
803
0
            encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
804
0
        }
805
0
        if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, cfg, encoderFlags) != AOM_CODEC_OK) {
806
0
            avifDiagnosticsPrintf(codec->diag,
807
0
                                  "aom_codec_enc_init() failed: %s: %s",
808
0
                                  aom_codec_error(&codec->internal->encoder),
809
0
                                  aom_codec_error_detail(&codec->internal->encoder));
810
0
            return AVIF_RESULT_UNKNOWN_ERROR;
811
0
        }
812
0
        codec->internal->encoderInitialized = AVIF_TRUE;
813
814
0
        if ((cfg->rc_end_usage == AOM_CQ) || (cfg->rc_end_usage == AOM_Q)) {
815
0
            aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, quantizer);
816
0
        }
817
0
        avifBool lossless = (quantizer == AVIF_QUANTIZER_LOSSLESS);
818
0
        if (lossless) {
819
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1);
820
0
        }
821
0
        if (tileRowsLog2 != 0) {
822
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
823
0
        }
824
0
        if (tileColsLog2 != 0) {
825
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
826
0
        }
827
0
        if (encoder->extraLayerCount > 0) {
828
0
            int layerCount = encoder->extraLayerCount + 1;
829
0
            if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) {
830
0
                return AVIF_RESULT_UNKNOWN_ERROR;
831
0
            }
832
0
        }
833
0
        if (aomCpuUsed != -1) {
834
0
            if (aom_codec_control(&codec->internal->encoder, AOME_SET_CPUUSED, aomCpuUsed) != AOM_CODEC_OK) {
835
0
                return AVIF_RESULT_UNKNOWN_ERROR;
836
0
            }
837
0
        }
838
839
        // Set color_config() in the sequence header OBU.
840
0
        if (alpha) {
841
            // AVIF specification, Section 4 "Auxiliary Image Items and Sequences":
842
            //   The color_range field in the Sequence Header OBU shall be set to 1.
843
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_RANGE, AOM_CR_FULL_RANGE);
844
845
            // Keep the default AOM_CSP_UNKNOWN value.
846
847
            // CICP (CP/TC/MC) does not apply to the alpha auxiliary image.
848
            // Keep default Unspecified (2) colour primaries, transfer characteristics,
849
            // and matrix coefficients.
850
0
        } else {
851
            // libaom's defaults are AOM_CSP_UNKNOWN and 0 (studio/limited range).
852
            // Call aom_codec_control() only if the values are not the defaults.
853
854
            // AVIF specification, Section 2.2.1. "AV1 Item Configuration Property":
855
            //   The values of the fields in the AV1CodecConfigurationBox shall match those
856
            //   of the Sequence Header OBU in the AV1 Image Item Data.
857
0
            if (image->yuvChromaSamplePosition != AVIF_CHROMA_SAMPLE_POSITION_UNKNOWN) {
858
0
                aom_codec_control(&codec->internal->encoder, AV1E_SET_CHROMA_SAMPLE_POSITION, (int)image->yuvChromaSamplePosition);
859
0
            }
860
861
            // AV1-ISOBMFF specification, Section 2.3.4:
862
            //   The value of full_range_flag in the 'colr' box SHALL match the color_range
863
            //   flag in the Sequence Header OBU.
864
0
            if (image->yuvRange != AVIF_RANGE_LIMITED) {
865
0
                aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_RANGE, (int)image->yuvRange);
866
0
            }
867
868
            // Section 2.3.4 of AV1-ISOBMFF says 'colr' with 'nclx' should be present and shall match CICP
869
            // values in the Sequence Header OBU, unless the latter has 2/2/2 (Unspecified).
870
            // So set CICP values to 2/2/2 (Unspecified) in the Sequence Header OBU for simplicity.
871
            // It may also save 3 bytes since the AV1 encoder can set color_description_present_flag to 0
872
            // (see Section 5.5.2 "Color config syntax" of the AV1 specification).
873
            // libaom's defaults are AOM_CICP_CP_UNSPECIFIED, AOM_CICP_TC_UNSPECIFIED, and
874
            // AOM_CICP_MC_UNSPECIFIED. No need to call aom_codec_control().
875
            // aom_image_t::cp, aom_image_t::tc and aom_image_t::mc are ignored by aom_codec_encode().
876
0
        }
877
878
0
#if defined(AOM_CTRL_AV1E_SET_SKIP_POSTPROC_FILTERING)
879
0
        if (cfg->g_usage == AOM_USAGE_ALL_INTRA) {
880
            // Enable AV1E_SET_SKIP_POSTPROC_FILTERING for still-picture encoding, which is
881
            // disabled by default.
882
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_SKIP_POSTPROC_FILTERING, 1);
883
0
        }
884
0
#endif
885
886
0
        if (!avifProcessAOMOptionsPostInit(codec, alpha)) {
887
0
            return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
888
0
        }
889
0
        if (!codec->internal->tuningSet) {
890
0
            if (aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, AOM_TUNE_SSIM) != AOM_CODEC_OK) {
891
0
                return AVIF_RESULT_UNKNOWN_ERROR;
892
0
            }
893
0
        }
894
895
0
        if (image->depth == 12) {
896
            // The encoder may produce integer overflows with 12-bit input when loop restoration is enabled. See crbug.com/aomedia/42302587.
897
0
            if (aom_codec_control(&codec->internal->encoder, AV1E_SET_ENABLE_RESTORATION, 0) != AOM_CODEC_OK) {
898
0
                return AVIF_RESULT_UNKNOWN_ERROR;
899
0
            }
900
0
        }
901
0
    } else {
902
0
        avifBool dimensionsChanged = AVIF_FALSE;
903
0
        if ((cfg->g_w != image->width) || (cfg->g_h != image->height)) {
904
            // We are not ready for dimension change for now.
905
0
            return AVIF_RESULT_NOT_IMPLEMENTED;
906
0
        }
907
0
        if (alpha) {
908
0
            if (encoderChanges & (AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA | AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA)) {
909
0
                cfg->rc_min_quantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
910
0
                cfg->rc_max_quantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
911
0
                quantizerUpdated = AVIF_TRUE;
912
0
            }
913
0
        } else {
914
0
            if (encoderChanges & (AVIF_ENCODER_CHANGE_MIN_QUANTIZER | AVIF_ENCODER_CHANGE_MAX_QUANTIZER)) {
915
0
                cfg->rc_min_quantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
916
0
                cfg->rc_max_quantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
917
0
                quantizerUpdated = AVIF_TRUE;
918
0
            }
919
0
        }
920
0
        const int quantizerChangedBit = alpha ? AVIF_ENCODER_CHANGE_QUANTIZER_ALPHA : AVIF_ENCODER_CHANGE_QUANTIZER;
921
0
        if (encoderChanges & quantizerChangedBit) {
922
0
            if ((cfg->rc_end_usage == AOM_VBR) || (cfg->rc_end_usage == AOM_CBR)) {
923
                // cq-level is ignored in these two end-usage modes, so adjust minQuantizer and
924
                // maxQuantizer to the target quantizer.
925
0
                if (quantizer == AVIF_QUANTIZER_LOSSLESS) {
926
0
                    cfg->rc_min_quantizer = AVIF_QUANTIZER_LOSSLESS;
927
0
                    cfg->rc_max_quantizer = AVIF_QUANTIZER_LOSSLESS;
928
0
                } else {
929
0
                    int minQuantizer;
930
0
                    int maxQuantizer;
931
0
                    if (alpha) {
932
0
                        minQuantizer = encoder->minQuantizerAlpha;
933
0
                        maxQuantizer = encoder->maxQuantizerAlpha;
934
0
                    } else {
935
0
                        minQuantizer = encoder->minQuantizer;
936
0
                        maxQuantizer = encoder->maxQuantizer;
937
0
                    }
938
0
                    minQuantizer = AVIF_CLAMP(minQuantizer, 0, 63);
939
0
                    maxQuantizer = AVIF_CLAMP(maxQuantizer, 0, 63);
940
0
                    cfg->rc_min_quantizer = AVIF_MAX(quantizer - 4, minQuantizer);
941
0
                    cfg->rc_max_quantizer = AVIF_MIN(quantizer + 4, maxQuantizer);
942
0
                }
943
0
                quantizerUpdated = AVIF_TRUE;
944
0
            }
945
0
        }
946
0
        if (quantizerUpdated || dimensionsChanged) {
947
0
            aom_codec_err_t err = aom_codec_enc_config_set(&codec->internal->encoder, cfg);
948
0
            if (err != AOM_CODEC_OK) {
949
0
                avifDiagnosticsPrintf(codec->diag,
950
0
                                      "aom_codec_enc_config_set() failed: %s: %s",
951
0
                                      aom_codec_error(&codec->internal->encoder),
952
0
                                      aom_codec_error_detail(&codec->internal->encoder));
953
0
                return AVIF_RESULT_UNKNOWN_ERROR;
954
0
            }
955
0
        }
956
0
        if (encoderChanges & AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2) {
957
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
958
0
        }
959
0
        if (encoderChanges & AVIF_ENCODER_CHANGE_TILE_COLS_LOG2) {
960
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
961
0
        }
962
0
        if (encoderChanges & quantizerChangedBit) {
963
0
            if ((cfg->rc_end_usage == AOM_CQ) || (cfg->rc_end_usage == AOM_Q)) {
964
0
                aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, quantizer);
965
0
            }
966
0
            avifBool lossless = (quantizer == AVIF_QUANTIZER_LOSSLESS);
967
0
            aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
968
0
        }
969
0
        if (encoderChanges & AVIF_ENCODER_CHANGE_CODEC_SPECIFIC) {
970
0
            if (!avifProcessAOMOptionsPostInit(codec, alpha)) {
971
0
                return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
972
0
            }
973
0
        }
974
0
    }
975
976
0
    if (codec->internal->currentLayer > encoder->extraLayerCount) {
977
0
        avifDiagnosticsPrintf(codec->diag,
978
0
                              "Too many layers sent. Expected %u layers, but got %u layers.",
979
0
                              encoder->extraLayerCount + 1,
980
0
                              codec->internal->currentLayer + 1);
981
0
        return AVIF_RESULT_INVALID_ARGUMENT;
982
0
    }
983
0
    if (encoder->extraLayerCount > 0) {
984
0
        aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, codec->internal->currentLayer);
985
0
    }
986
987
0
    aom_scaling_mode_t aomScalingMode;
988
0
    if (!avifFindAOMScalingMode(&encoder->scalingMode.horizontal, &aomScalingMode.h_scaling_mode)) {
989
0
        return AVIF_RESULT_NOT_IMPLEMENTED;
990
0
    }
991
0
    if (!avifFindAOMScalingMode(&encoder->scalingMode.vertical, &aomScalingMode.v_scaling_mode)) {
992
0
        return AVIF_RESULT_NOT_IMPLEMENTED;
993
0
    }
994
0
    if ((aomScalingMode.h_scaling_mode != AOME_NORMAL) || (aomScalingMode.v_scaling_mode != AOME_NORMAL)) {
995
        // AOME_SET_SCALEMODE only applies to next frame (layer), so we have to set it every time.
996
0
        aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &aomScalingMode);
997
0
    }
998
999
0
    aom_image_t aomImage;
1000
    // We prefer to simply set the aomImage.planes[] pointers to the plane buffers in 'image'. When
1001
    // doing this, we set aomImage.w equal to aomImage.d_w and aomImage.h equal to aomImage.d_h and
1002
    // do not "align" aomImage.w and aomImage.h. Unfortunately this exposes a bug in libaom
1003
    // (https://crbug.com/aomedia/3113) if chroma is subsampled and image->width or image->height is
1004
    // equal to 1. To work around this libaom bug, we allocate the aomImage.planes[] buffers and
1005
    // copy the image YUV data if image->width or image->height is equal to 1. This bug has been
1006
    // fixed in libaom v3.1.3.
1007
    //
1008
    // Note: The exact condition for the bug is
1009
    //   ((image->width == 1) && (chroma is subsampled horizontally)) ||
1010
    //   ((image->height == 1) && (chroma is subsampled vertically))
1011
    // Since an image width or height of 1 is uncommon in practice, we test an inexact but simpler
1012
    // condition.
1013
0
    avifBool aomImageAllocated = (image->width == 1) || (image->height == 1);
1014
0
    if (aomImageAllocated) {
1015
0
        aom_img_alloc(&aomImage, codec->internal->aomFormat, image->width, image->height, 16);
1016
0
    } else {
1017
0
        memset(&aomImage, 0, sizeof(aomImage));
1018
0
        aomImage.fmt = codec->internal->aomFormat;
1019
0
        aomImage.bit_depth = (image->depth > 8) ? 16 : 8;
1020
0
        aomImage.w = image->width;
1021
0
        aomImage.h = image->height;
1022
0
        aomImage.d_w = image->width;
1023
0
        aomImage.d_h = image->height;
1024
        // Get sample size for this format.
1025
0
        unsigned int bps;
1026
0
        if (codec->internal->aomFormat == AOM_IMG_FMT_I420) {
1027
0
            bps = 12;
1028
0
        } else if (codec->internal->aomFormat == AOM_IMG_FMT_I422) {
1029
0
            bps = 16;
1030
0
        } else if (codec->internal->aomFormat == AOM_IMG_FMT_I444) {
1031
0
            bps = 24;
1032
0
        } else if (codec->internal->aomFormat == AOM_IMG_FMT_I42016) {
1033
0
            bps = 24;
1034
0
        } else if (codec->internal->aomFormat == AOM_IMG_FMT_I42216) {
1035
0
            bps = 32;
1036
0
        } else if (codec->internal->aomFormat == AOM_IMG_FMT_I44416) {
1037
0
            bps = 48;
1038
0
        } else {
1039
0
            bps = 16;
1040
0
        }
1041
0
        aomImage.bps = bps;
1042
        // See avifImageCalcAOMFmt(). libaom doesn't have AOM_IMG_FMT_I400, so we use AOM_IMG_FMT_I420 as a substitute for monochrome.
1043
0
        aomImage.x_chroma_shift = (alpha || codec->internal->formatInfo.monochrome) ? 1 : codec->internal->formatInfo.chromaShiftX;
1044
0
        aomImage.y_chroma_shift = (alpha || codec->internal->formatInfo.monochrome) ? 1 : codec->internal->formatInfo.chromaShiftY;
1045
0
    }
1046
1047
0
    avifBool monochromeRequested = AVIF_FALSE;
1048
1049
0
    if (alpha) {
1050
        // AVIF specification, Section 4 "Auxiliary Image Items and Sequences":
1051
        //   The color_range field in the Sequence Header OBU shall be set to 1.
1052
0
        aomImage.range = AOM_CR_FULL_RANGE;
1053
1054
        // AVIF specification, Section 4 "Auxiliary Image Items and Sequences":
1055
        //   The mono_chrome field in the Sequence Header OBU shall be set to 1.
1056
        // Some encoders do not support 4:0:0 and encode alpha as 4:2:0 so it is not always respected.
1057
0
        monochromeRequested = AVIF_TRUE;
1058
0
        if (aomImageAllocated) {
1059
0
            const uint32_t bytesPerRow = ((image->depth > 8) ? 2 : 1) * image->width;
1060
0
            for (uint32_t j = 0; j < image->height; ++j) {
1061
0
                const uint8_t * srcAlphaRow = &image->alphaPlane[j * image->alphaRowBytes];
1062
0
                uint8_t * dstAlphaRow = &aomImage.planes[0][j * aomImage.stride[0]];
1063
0
                memcpy(dstAlphaRow, srcAlphaRow, bytesPerRow);
1064
0
            }
1065
0
        } else {
1066
0
            aomImage.planes[0] = image->alphaPlane;
1067
0
            aomImage.stride[0] = image->alphaRowBytes;
1068
0
        }
1069
1070
        // Ignore UV planes when monochrome. Keep the default AOM_CSP_UNKNOWN value.
1071
0
    } else {
1072
0
        int yuvPlaneCount = 3;
1073
0
        if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
1074
0
            yuvPlaneCount = 1; // Ignore UV planes when monochrome
1075
0
            monochromeRequested = AVIF_TRUE;
1076
0
        }
1077
0
        if (aomImageAllocated) {
1078
0
            uint32_t bytesPerPixel = (image->depth > 8) ? 2 : 1;
1079
0
            for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
1080
0
                uint32_t planeWidth = avifImagePlaneWidth(image, yuvPlane);
1081
0
                uint32_t planeHeight = avifImagePlaneHeight(image, yuvPlane);
1082
0
                uint32_t bytesPerRow = bytesPerPixel * planeWidth;
1083
1084
0
                for (uint32_t j = 0; j < planeHeight; ++j) {
1085
0
                    const uint8_t * srcRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]];
1086
0
                    uint8_t * dstRow = &aomImage.planes[yuvPlane][j * aomImage.stride[yuvPlane]];
1087
0
                    memcpy(dstRow, srcRow, bytesPerRow);
1088
0
                }
1089
0
            }
1090
0
        } else {
1091
0
            for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
1092
0
                aomImage.planes[yuvPlane] = image->yuvPlanes[yuvPlane];
1093
0
                aomImage.stride[yuvPlane] = image->yuvRowBytes[yuvPlane];
1094
0
            }
1095
0
        }
1096
1097
        // AVIF specification, Section 2.2.1. "AV1 Item Configuration Property":
1098
        //   The values of the fields in the AV1CodecConfigurationBox shall match those
1099
        //   of the Sequence Header OBU in the AV1 Image Item Data.
1100
0
        aomImage.csp = (aom_chroma_sample_position_t)image->yuvChromaSamplePosition;
1101
1102
        // AV1-ISOBMFF specification, Section 2.3.4:
1103
        //   The value of full_range_flag in the 'colr' box SHALL match the color_range
1104
        //   flag in the Sequence Header OBU.
1105
0
        aomImage.range = (aom_color_range_t)image->yuvRange;
1106
0
    }
1107
1108
0
    unsigned char * monoUVPlane = NULL;
1109
0
    if (monochromeRequested) {
1110
0
        if (codec->internal->monochromeEnabled) {
1111
0
            aomImage.monochrome = 1;
1112
0
        } else {
1113
            // The user requested monochrome (via alpha or YUV400) but libaom cannot currently support
1114
            // monochrome (see chroma_check comment above). Manually set UV planes to 0.5.
1115
1116
            // aomImage is always 420 when we're monochrome
1117
0
            uint32_t monoUVWidth = (image->width + 1) >> 1;
1118
0
            uint32_t monoUVHeight = (image->height + 1) >> 1;
1119
1120
            // Allocate the U plane if necessary.
1121
0
            if (!aomImageAllocated) {
1122
0
                uint32_t channelSize = avifImageUsesU16(image) ? 2 : 1;
1123
0
                uint32_t monoUVRowBytes = channelSize * monoUVWidth;
1124
0
                size_t monoUVSize = (size_t)monoUVHeight * monoUVRowBytes;
1125
1126
0
                monoUVPlane = avifAlloc(monoUVSize);
1127
0
                AVIF_CHECKERR(monoUVPlane != NULL, AVIF_RESULT_OUT_OF_MEMORY); // No need for aom_img_free() because !aomImageAllocated
1128
0
                aomImage.planes[1] = monoUVPlane;
1129
0
                aomImage.stride[1] = monoUVRowBytes;
1130
0
            }
1131
            // Set the U plane to 0.5.
1132
0
            if (image->depth > 8) {
1133
0
                const uint16_t half = (uint16_t)(1 << (image->depth - 1));
1134
0
                for (uint32_t j = 0; j < monoUVHeight; ++j) {
1135
0
                    uint16_t * dstRow = (uint16_t *)&aomImage.planes[1][(size_t)j * aomImage.stride[1]];
1136
0
                    for (uint32_t i = 0; i < monoUVWidth; ++i) {
1137
0
                        dstRow[i] = half;
1138
0
                    }
1139
0
                }
1140
0
            } else {
1141
0
                const uint8_t half = 128;
1142
0
                size_t planeSize = (size_t)monoUVHeight * aomImage.stride[1];
1143
0
                memset(aomImage.planes[1], half, planeSize);
1144
0
            }
1145
            // Make the V plane the same as the U plane.
1146
0
            aomImage.planes[2] = aomImage.planes[1];
1147
0
            aomImage.stride[2] = aomImage.stride[1];
1148
0
        }
1149
0
    }
1150
1151
0
    aom_enc_frame_flags_t encodeFlags = 0;
1152
0
    if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) {
1153
0
        encodeFlags |= AOM_EFLAG_FORCE_KF;
1154
0
    }
1155
0
    if (codec->internal->currentLayer > 0) {
1156
0
        encodeFlags |= AOM_EFLAG_NO_REF_GF | AOM_EFLAG_NO_REF_ARF | AOM_EFLAG_NO_REF_BWD | AOM_EFLAG_NO_REF_ARF2 |
1157
0
                       AOM_EFLAG_NO_UPD_GF | AOM_EFLAG_NO_UPD_ARF;
1158
0
    }
1159
0
    aom_codec_err_t encodeErr = aom_codec_encode(&codec->internal->encoder, &aomImage, 0, 1, encodeFlags);
1160
0
    avifFree(monoUVPlane);
1161
0
    if (aomImageAllocated) {
1162
0
        aom_img_free(&aomImage);
1163
0
    }
1164
0
    if (encodeErr != AOM_CODEC_OK) {
1165
0
        avifDiagnosticsPrintf(codec->diag,
1166
0
                              "aom_codec_encode() failed: %s: %s",
1167
0
                              aom_codec_error(&codec->internal->encoder),
1168
0
                              aom_codec_error_detail(&codec->internal->encoder));
1169
0
        return AVIF_RESULT_UNKNOWN_ERROR;
1170
0
    }
1171
1172
0
    aom_codec_iter_t iter = NULL;
1173
0
    for (;;) {
1174
0
        const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&codec->internal->encoder, &iter);
1175
0
        if (pkt == NULL) {
1176
0
            break;
1177
0
        }
1178
0
        if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
1179
0
            AVIF_CHECKRES(
1180
0
                avifCodecEncodeOutputAddSample(output, pkt->data.frame.buf, pkt->data.frame.sz, (pkt->data.frame.flags & AOM_FRAME_IS_KEY)));
1181
0
        }
1182
0
    }
1183
1184
0
    if ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) ||
1185
0
        ((encoder->extraLayerCount > 0) && (encoder->extraLayerCount == codec->internal->currentLayer))) {
1186
        // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images,
1187
        // as encoding is finished now. For layered image, encoding finishes when the last layer is encoded.
1188
1189
0
        if (!aomCodecEncodeFinish(codec, output)) {
1190
0
            return AVIF_RESULT_UNKNOWN_ERROR;
1191
0
        }
1192
0
        aom_codec_destroy(&codec->internal->encoder);
1193
0
        codec->internal->encoderInitialized = AVIF_FALSE;
1194
0
    }
1195
0
    if (encoder->extraLayerCount > 0) {
1196
0
        ++codec->internal->currentLayer;
1197
0
    }
1198
0
    return AVIF_RESULT_OK;
1199
0
}
1200
1201
static avifBool aomCodecEncodeFinish(avifCodec * codec, avifCodecEncodeOutput * output)
1202
0
{
1203
0
    if (!codec->internal->encoderInitialized) {
1204
0
        return AVIF_TRUE;
1205
0
    }
1206
0
    for (;;) {
1207
        // flush encoder
1208
0
        if (aom_codec_encode(&codec->internal->encoder, NULL, 0, 1, 0) != AOM_CODEC_OK) {
1209
0
            avifDiagnosticsPrintf(codec->diag,
1210
0
                                  "aom_codec_encode() with img=NULL failed: %s: %s",
1211
0
                                  aom_codec_error(&codec->internal->encoder),
1212
0
                                  aom_codec_error_detail(&codec->internal->encoder));
1213
0
            return AVIF_FALSE;
1214
0
        }
1215
1216
0
        avifBool gotPacket = AVIF_FALSE;
1217
0
        aom_codec_iter_t iter = NULL;
1218
0
        for (;;) {
1219
0
            const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&codec->internal->encoder, &iter);
1220
0
            if (pkt == NULL) {
1221
0
                break;
1222
0
            }
1223
0
            if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
1224
0
                gotPacket = AVIF_TRUE;
1225
0
                const avifResult result = avifCodecEncodeOutputAddSample(output,
1226
0
                                                                         pkt->data.frame.buf,
1227
0
                                                                         pkt->data.frame.sz,
1228
0
                                                                         (pkt->data.frame.flags & AOM_FRAME_IS_KEY));
1229
0
                if (result != AVIF_RESULT_OK) {
1230
0
                    avifDiagnosticsPrintf(codec->diag, "avifCodecEncodeOutputAddSample() failed: %s", avifResultToString(result));
1231
0
                    return AVIF_FALSE;
1232
0
                }
1233
0
            }
1234
0
        }
1235
1236
0
        if (!gotPacket) {
1237
0
            break;
1238
0
        }
1239
0
    }
1240
0
    return AVIF_TRUE;
1241
0
}
1242
1243
#endif // defined(AVIF_CODEC_AOM_ENCODE)
1244
1245
const char * avifCodecVersionAOM(void)
1246
0
{
1247
0
    return aom_codec_version_str();
1248
0
}
1249
1250
avifCodec * avifCodecCreateAOM(void)
1251
12.2k
{
1252
12.2k
    avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
1253
12.2k
    if (codec == NULL) {
1254
0
        return NULL;
1255
0
    }
1256
12.2k
    memset(codec, 0, sizeof(struct avifCodec));
1257
1258
12.2k
#if defined(AVIF_CODEC_AOM_DECODE)
1259
12.2k
    codec->getNextImage = aomCodecGetNextImage;
1260
12.2k
#endif
1261
1262
12.2k
#if defined(AVIF_CODEC_AOM_ENCODE)
1263
12.2k
    codec->encodeImage = aomCodecEncodeImage;
1264
12.2k
    codec->encodeFinish = aomCodecEncodeFinish;
1265
12.2k
#endif
1266
1267
12.2k
    codec->destroyInternal = aomCodecDestroyInternal;
1268
12.2k
    codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
1269
12.2k
    if (codec->internal == NULL) {
1270
0
        avifFree(codec);
1271
0
        return NULL;
1272
0
    }
1273
12.2k
    memset(codec->internal, 0, sizeof(struct avifCodecInternal));
1274
12.2k
    return codec;
1275
12.2k
}
1276
1277
#ifdef __clang__
1278
#pragma clang diagnostic pop
1279
#endif