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 <limits.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 struct avifCodecInternal
38 {
39 #if defined(AVIF_CODEC_AOM_DECODE)
40     avifBool decoderInitialized;
41     aom_codec_ctx_t decoder;
42     aom_codec_iter_t iter;
43     aom_image_t * image;
44 #endif
45 
46 #if defined(AVIF_CODEC_AOM_ENCODE)
47     avifBool encoderInitialized;
48     aom_codec_ctx_t encoder;
49     avifPixelFormatInfo formatInfo;
50     aom_img_fmt_t aomFormat;
51     avifBool monochromeEnabled;
52 #endif
53 };
54 
aomCodecDestroyInternal(avifCodec * codec)55 static void aomCodecDestroyInternal(avifCodec * codec)
56 {
57 #if defined(AVIF_CODEC_AOM_DECODE)
58     if (codec->internal->decoderInitialized) {
59         aom_codec_destroy(&codec->internal->decoder);
60     }
61 #endif
62 
63 #if defined(AVIF_CODEC_AOM_ENCODE)
64     if (codec->internal->encoderInitialized) {
65         aom_codec_destroy(&codec->internal->encoder);
66     }
67     avifFree(codec->internal);
68 #endif
69 }
70 
71 #if defined(AVIF_CODEC_AOM_DECODE)
aomCodecOpen(struct avifCodec * codec,avifDecoder * decoder)72 static avifBool aomCodecOpen(struct avifCodec * codec, avifDecoder * decoder)
73 {
74     aom_codec_dec_cfg_t cfg;
75     memset(&cfg, 0, sizeof(aom_codec_dec_cfg_t));
76     cfg.threads = decoder->maxThreads;
77     cfg.allow_lowbitdepth = 1;
78 
79     aom_codec_iface_t * decoder_interface = aom_codec_av1_dx();
80     if (aom_codec_dec_init(&codec->internal->decoder, decoder_interface, &cfg, 0)) {
81         return AVIF_FALSE;
82     }
83     codec->internal->decoderInitialized = AVIF_TRUE;
84 
85     // Ensure that we only get the "highest spatial layer" as a single frame
86     // for each input sample, instead of getting each spatial layer as its own
87     // frame one at a time ("all layers").
88     if (aom_codec_control(&codec->internal->decoder, AV1D_SET_OUTPUT_ALL_LAYERS, 0)) {
89         return AVIF_FALSE;
90     }
91 
92     codec->internal->iter = NULL;
93     return AVIF_TRUE;
94 }
95 
aomCodecGetNextImage(struct avifCodec * codec,const avifDecodeSample * sample,avifBool alpha,avifImage * image)96 static avifBool aomCodecGetNextImage(struct avifCodec * codec, const avifDecodeSample * sample, avifBool alpha, avifImage * image)
97 {
98     aom_image_t * nextFrame = NULL;
99     for (;;) {
100         nextFrame = aom_codec_get_frame(&codec->internal->decoder, &codec->internal->iter);
101         if (nextFrame) {
102             // Got an image!
103             break;
104         } else if (sample) {
105             codec->internal->iter = NULL;
106             if (aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL)) {
107                 return AVIF_FALSE;
108             }
109             sample = NULL;
110         } else {
111             break;
112         }
113     }
114 
115     if (nextFrame) {
116         codec->internal->image = nextFrame;
117     } else {
118         if (alpha && codec->internal->image) {
119             // Special case: reuse last alpha frame
120         } else {
121             return AVIF_FALSE;
122         }
123     }
124 
125     avifBool isColor = !alpha;
126     if (isColor) {
127         // Color (YUV) planes - set image to correct size / format, fill color
128 
129         avifPixelFormat yuvFormat = AVIF_PIXEL_FORMAT_NONE;
130         switch (codec->internal->image->fmt) {
131             case AOM_IMG_FMT_I420:
132             case AOM_IMG_FMT_AOMI420:
133             case AOM_IMG_FMT_I42016:
134                 yuvFormat = AVIF_PIXEL_FORMAT_YUV420;
135                 break;
136             case AOM_IMG_FMT_I422:
137             case AOM_IMG_FMT_I42216:
138                 yuvFormat = AVIF_PIXEL_FORMAT_YUV422;
139                 break;
140             case AOM_IMG_FMT_I444:
141             case AOM_IMG_FMT_I44416:
142                 yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
143                 break;
144             case AOM_IMG_FMT_NONE:
145             case AOM_IMG_FMT_YV12:
146             case AOM_IMG_FMT_AOMYV12:
147             case AOM_IMG_FMT_YV1216:
148             default:
149                 return AVIF_FALSE;
150         }
151         if (codec->internal->image->monochrome) {
152             yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
153         }
154 
155         if (image->width && image->height) {
156             if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
157                 (image->depth != codec->internal->image->bit_depth) || (image->yuvFormat != yuvFormat)) {
158                 // Throw it all out
159                 avifImageFreePlanes(image, AVIF_PLANES_ALL);
160             }
161         }
162         image->width = codec->internal->image->d_w;
163         image->height = codec->internal->image->d_h;
164         image->depth = codec->internal->image->bit_depth;
165 
166         image->yuvFormat = yuvFormat;
167         image->yuvRange = (codec->internal->image->range == AOM_CR_STUDIO_RANGE) ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL;
168         image->yuvChromaSamplePosition = (avifChromaSamplePosition)codec->internal->image->csp;
169 
170         image->colorPrimaries = (avifColorPrimaries)codec->internal->image->cp;
171         image->transferCharacteristics = (avifTransferCharacteristics)codec->internal->image->tc;
172         image->matrixCoefficients = (avifMatrixCoefficients)codec->internal->image->mc;
173 
174         avifPixelFormatInfo formatInfo;
175         avifGetPixelFormatInfo(yuvFormat, &formatInfo);
176 
177         // Steal the pointers from the decoder's image directly
178         avifImageFreePlanes(image, AVIF_PLANES_YUV);
179         int yuvPlaneCount = (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
180         for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
181             image->yuvPlanes[yuvPlane] = codec->internal->image->planes[yuvPlane];
182             image->yuvRowBytes[yuvPlane] = codec->internal->image->stride[yuvPlane];
183         }
184         image->imageOwnsYUVPlanes = AVIF_FALSE;
185     } else {
186         // Alpha plane - ensure image is correct size, fill color
187 
188         if (image->width && image->height) {
189             if ((image->width != codec->internal->image->d_w) || (image->height != codec->internal->image->d_h) ||
190                 (image->depth != codec->internal->image->bit_depth)) {
191                 // Alpha plane doesn't match previous alpha plane decode, bail out
192                 return AVIF_FALSE;
193             }
194         }
195         image->width = codec->internal->image->d_w;
196         image->height = codec->internal->image->d_h;
197         image->depth = codec->internal->image->bit_depth;
198 
199         avifImageFreePlanes(image, AVIF_PLANES_A);
200         image->alphaPlane = codec->internal->image->planes[0];
201         image->alphaRowBytes = codec->internal->image->stride[0];
202         image->alphaRange = (codec->internal->image->range == AOM_CR_STUDIO_RANGE) ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL;
203         image->imageOwnsAlphaPlane = AVIF_FALSE;
204     }
205 
206     return AVIF_TRUE;
207 }
208 #endif // defined(AVIF_CODEC_AOM_DECODE)
209 
210 #if defined(AVIF_CODEC_AOM_ENCODE)
211 
avifImageCalcAOMFmt(const avifImage * image,avifBool alpha)212 static aom_img_fmt_t avifImageCalcAOMFmt(const avifImage * image, avifBool alpha)
213 {
214     aom_img_fmt_t fmt;
215     if (alpha) {
216         // We're going monochrome, who cares about chroma quality
217         fmt = AOM_IMG_FMT_I420;
218     } else {
219         switch (image->yuvFormat) {
220             case AVIF_PIXEL_FORMAT_YUV444:
221                 fmt = AOM_IMG_FMT_I444;
222                 break;
223             case AVIF_PIXEL_FORMAT_YUV422:
224                 fmt = AOM_IMG_FMT_I422;
225                 break;
226             case AVIF_PIXEL_FORMAT_YUV420:
227             case AVIF_PIXEL_FORMAT_YUV400:
228                 fmt = AOM_IMG_FMT_I420;
229                 break;
230             case AVIF_PIXEL_FORMAT_NONE:
231             default:
232                 return AOM_IMG_FMT_NONE;
233         }
234     }
235 
236     if (image->depth > 8) {
237         fmt |= AOM_IMG_FMT_HIGHBITDEPTH;
238     }
239 
240     return fmt;
241 }
242 
aomOptionParseInt(const char * str,int * val)243 static avifBool aomOptionParseInt(const char * str, int * val)
244 {
245     char * endptr;
246     const long rawval = strtol(str, &endptr, 10);
247 
248     if (str[0] != '\0' && endptr[0] == '\0' && rawval >= INT_MIN && rawval <= INT_MAX) {
249         *val = (int)rawval;
250         return AVIF_TRUE;
251     }
252 
253     return AVIF_FALSE;
254 }
255 
256 struct aomOptionEnumList
257 {
258     const char * name;
259     int val;
260 };
261 
aomOptionParseEnum(const char * str,const struct aomOptionEnumList * enums,int * val)262 static avifBool aomOptionParseEnum(const char * str, const struct aomOptionEnumList * enums, int * val)
263 {
264     const struct aomOptionEnumList * listptr;
265     long int rawval;
266     char * endptr;
267 
268     // First see if the value can be parsed as a raw value.
269     rawval = strtol(str, &endptr, 10);
270     if (str[0] != '\0' && endptr[0] == '\0') {
271         // Got a raw value, make sure it's valid.
272         for (listptr = enums; listptr->name; listptr++)
273             if (listptr->val == rawval) {
274                 *val = (int)rawval;
275                 return AVIF_TRUE;
276             }
277     }
278 
279     // Next see if it can be parsed as a string.
280     for (listptr = enums; listptr->name; listptr++) {
281         if (!strcmp(str, listptr->name)) {
282             *val = listptr->val;
283             return AVIF_TRUE;
284         }
285     }
286 
287     return AVIF_FALSE;
288 }
289 
290 static const struct aomOptionEnumList endUsageEnum[] = { //
291     { "vbr", AOM_VBR },                                  // Variable Bit Rate (VBR) mode
292     { "cbr", AOM_CBR },                                  // Constant Bit Rate (CBR) mode
293     { "cq", AOM_CQ },                                    // Constrained Quality (CQ)  mode
294     { "q", AOM_Q },                                      // Constrained Quality (CQ)  mode
295     { NULL, 0 }
296 };
297 
avifProcessAOMOptionsPreInit(avifCodec * codec,struct aom_codec_enc_cfg * cfg)298 static avifBool avifProcessAOMOptionsPreInit(avifCodec * codec, struct aom_codec_enc_cfg * cfg)
299 {
300     for (uint32_t i = 0; i < codec->csOptions->count; ++i) {
301         avifCodecSpecificOption * entry = &codec->csOptions->entries[i];
302         int val;
303         if (!strcmp(entry->key, "end-usage")) { // Rate control mode
304             if (!aomOptionParseEnum(entry->value, endUsageEnum, &val)) {
305                 return AVIF_FALSE;
306             }
307             cfg->rc_end_usage = val;
308         }
309     }
310     return AVIF_TRUE;
311 }
312 
313 struct aomOptionDef
314 {
315     const char * name;
316     int controlId;
317     const struct aomOptionEnumList * enums;
318 };
319 
320 static const struct aomOptionEnumList tuningEnum[] = { //
321     { "psnr", AOM_TUNE_PSNR },                         //
322     { "ssim", AOM_TUNE_SSIM },                         //
323     { NULL, 0 }
324 };
325 
326 static const struct aomOptionDef aomOptionDefs[] = {                 //
327     { "aq-mode", AV1E_SET_AQ_MODE, NULL },                           // Adaptive quantization mode
328     { "cq-level", AOME_SET_CQ_LEVEL, NULL },                         // Constant/Constrained Quality level
329     { "enable-chroma-deltaq", AV1E_SET_ENABLE_CHROMA_DELTAQ, NULL }, // Enable delta quantization in chroma planes
330     { "sharpness", AOME_SET_SHARPNESS, NULL },                       // Loop filter sharpness
331     { "tune", AOME_SET_TUNING, tuningEnum },                         // Tune distortion metric
332     { NULL, 0, NULL }
333 };
334 
avifProcessAOMOptionsPostInit(avifCodec * codec)335 static avifBool avifProcessAOMOptionsPostInit(avifCodec * codec)
336 {
337     for (uint32_t i = 0; i < codec->csOptions->count; ++i) {
338         avifCodecSpecificOption * entry = &codec->csOptions->entries[i];
339         // Skip options processed by avifProcessAOMOptionsPreInit.
340         if (!strcmp(entry->key, "end-usage")) {
341             continue;
342         }
343 
344         avifBool match = AVIF_FALSE;
345         for (int j = 0; aomOptionDefs[j].name; ++j) {
346             if (!strcmp(entry->key, aomOptionDefs[j].name)) {
347                 match = AVIF_TRUE;
348                 int val;
349                 avifBool parsed;
350                 if (aomOptionDefs[j].enums) {
351                     parsed = aomOptionParseEnum(entry->value, aomOptionDefs[j].enums, &val);
352                 } else {
353                     parsed = aomOptionParseInt(entry->value, &val);
354                 }
355                 if (!parsed) {
356                     return AVIF_FALSE;
357                 }
358                 if (aom_codec_control(&codec->internal->encoder, aomOptionDefs[j].controlId, val) != AOM_CODEC_OK) {
359                     return AVIF_FALSE;
360                 }
361                 break;
362             }
363         }
364         if (!match) {
365             return AVIF_FALSE;
366         }
367     }
368     return AVIF_TRUE;
369 }
370 
aomCodecEncodeImage(avifCodec * codec,avifEncoder * encoder,const avifImage * image,avifBool alpha,uint32_t addImageFlags,avifCodecEncodeOutput * output)371 static avifResult aomCodecEncodeImage(avifCodec * codec,
372                                       avifEncoder * encoder,
373                                       const avifImage * image,
374                                       avifBool alpha,
375                                       uint32_t addImageFlags,
376                                       avifCodecEncodeOutput * output)
377 {
378     if (!codec->internal->encoderInitialized) {
379         // Map encoder speed to AOM usage + CpuUsed:
380         // Speed  0: GoodQuality CpuUsed 0
381         // Speed  1: GoodQuality CpuUsed 1
382         // Speed  2: GoodQuality CpuUsed 2
383         // Speed  3: GoodQuality CpuUsed 3
384         // Speed  4: GoodQuality CpuUsed 4
385         // Speed  5: GoodQuality CpuUsed 5
386         // Speed  6: GoodQuality CpuUsed 6
387         // Speed  7: GoodQuality CpuUsed 6
388         // Speed  8: RealTime    CpuUsed 6
389         // Speed  9: RealTime    CpuUsed 7
390         // Speed 10: RealTime    CpuUsed 8
391         unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
392         int aomCpuUsed = -1;
393         if (encoder->speed != AVIF_SPEED_DEFAULT) {
394             if (encoder->speed < 8) {
395                 aomUsage = AOM_USAGE_GOOD_QUALITY;
396                 aomCpuUsed = AVIF_CLAMP(encoder->speed, 0, 6);
397             } else {
398                 aomUsage = AOM_USAGE_REALTIME;
399                 aomCpuUsed = AVIF_CLAMP(encoder->speed - 2, 6, 8);
400             }
401         }
402 
403         // aom_codec.h says: aom_codec_version() == (major<<16 | minor<<8 | patch)
404         static const int aomVersion_2_0_0 = (2 << 16);
405         const int aomVersion = aom_codec_version();
406         if ((aomVersion < aomVersion_2_0_0) && (image->depth > 8)) {
407             // Due to a known issue with libavif v1.0.0-errata1-avif, 10bpc and
408             // 12bpc image encodes will call the wrong variant of
409             // aom_subtract_block when cpu-used is 7 or 8, and crash. Until we get
410             // a new tagged release from libaom with the fix and can verify we're
411             // running with that version of libaom, we must avoid using
412             // cpu-used=7/8 on any >8bpc image encodes.
413             //
414             // Context:
415             //   * https://github.com/AOMediaCodec/libavif/issues/49
416             //   * https://bugs.chromium.org/p/aomedia/issues/detail?id=2587
417             //
418             // Continued bug tracking here:
419             //   * https://github.com/AOMediaCodec/libavif/issues/56
420 
421             if (aomCpuUsed > 6) {
422                 aomCpuUsed = 6;
423             }
424         }
425 
426         codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
427         if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
428             return AVIF_RESULT_UNKNOWN_ERROR;
429         }
430 
431         avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
432 
433         aom_codec_iface_t * encoderInterface = aom_codec_av1_cx();
434         struct aom_codec_enc_cfg cfg;
435         aom_codec_enc_config_default(encoderInterface, &cfg, aomUsage);
436 
437         // Profile 0.  8-bit and 10-bit 4:2:0 and 4:0:0 only.
438         // Profile 1.  8-bit and 10-bit 4:4:4
439         // Profile 2.  8-bit and 10-bit 4:2:2
440         //            12-bit 4:0:0, 4:2:0, 4:2:2 and 4:4:4
441         uint8_t seqProfile = 0;
442         if (image->depth == 12) {
443             // Only seqProfile 2 can handle 12 bit
444             seqProfile = 2;
445         } else {
446             // 8-bit or 10-bit
447 
448             if (alpha) {
449                 seqProfile = 0;
450             } else {
451                 switch (image->yuvFormat) {
452                     case AVIF_PIXEL_FORMAT_YUV444:
453                         seqProfile = 1;
454                         break;
455                     case AVIF_PIXEL_FORMAT_YUV422:
456                         seqProfile = 2;
457                         break;
458                     case AVIF_PIXEL_FORMAT_YUV420:
459                         seqProfile = 0;
460                         break;
461                     case AVIF_PIXEL_FORMAT_YUV400:
462                         seqProfile = 0;
463                         break;
464                     case AVIF_PIXEL_FORMAT_NONE:
465                     default:
466                         break;
467                 }
468             }
469         }
470 
471         cfg.g_profile = seqProfile;
472         cfg.g_bit_depth = image->depth;
473         cfg.g_input_bit_depth = image->depth;
474         cfg.g_w = image->width;
475         cfg.g_h = image->height;
476         if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
477             // Set the maximum number of frames to encode to 1. This instructs
478             // libaom to set still_picture and reduced_still_picture_header to
479             // 1 in AV1 sequence headers.
480             cfg.g_limit = 1;
481             // Set g_lag_in_frames to 1 to reduce the number of frame buffers
482             // (from 20 to 2) in libaom's lookahead structure. This reduces
483             // memory consumption when encoding a single image.
484             cfg.g_lag_in_frames = 1;
485         }
486         if (encoder->maxThreads > 1) {
487             cfg.g_threads = encoder->maxThreads;
488         }
489 
490         int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
491         int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
492         if (alpha) {
493             minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
494             maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
495         }
496         avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
497         cfg.rc_min_quantizer = minQuantizer;
498         cfg.rc_max_quantizer = maxQuantizer;
499 
500         codec->internal->monochromeEnabled = AVIF_FALSE;
501         if (aomVersion > aomVersion_2_0_0) {
502             // There exists a bug in libaom's chroma_check() function where it will attempt to
503             // access nonexistent UV planes when encoding monochrome at faster libavif "speeds". It
504             // was fixed shortly after the 2.0.0 libaom release, and the fix exists in both the
505             // master and applejack branches. This ensures that the next version *after* 2.0.0 will
506             // have the fix, and we must avoid cfg.monochrome until then.
507             //
508             // Bugfix Change-Id: https://aomedia-review.googlesource.com/q/I26a39791f820b4d4e1d63ff7141f594c3c7181f5
509 
510             if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
511                 codec->internal->monochromeEnabled = AVIF_TRUE;
512                 cfg.monochrome = 1;
513             }
514         }
515 
516         if (!avifProcessAOMOptionsPreInit(codec, &cfg)) {
517             return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
518         }
519 
520         aom_codec_flags_t encoderFlags = 0;
521         if (image->depth > 8) {
522             encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
523         }
524         aom_codec_enc_init(&codec->internal->encoder, encoderInterface, &cfg, encoderFlags);
525         codec->internal->encoderInitialized = AVIF_TRUE;
526 
527         if (lossless) {
528             aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1);
529         }
530         if (encoder->maxThreads > 1) {
531             aom_codec_control(&codec->internal->encoder, AV1E_SET_ROW_MT, 1);
532         }
533         if (encoder->tileRowsLog2 != 0) {
534             int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
535             aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
536         }
537         if (encoder->tileColsLog2 != 0) {
538             int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
539             aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
540         }
541         if (aomCpuUsed != -1) {
542             aom_codec_control(&codec->internal->encoder, AOME_SET_CPUUSED, aomCpuUsed);
543         }
544         if (!avifProcessAOMOptionsPostInit(codec)) {
545             return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
546         }
547     }
548 
549     int yShift = codec->internal->formatInfo.chromaShiftY;
550     uint32_t uvHeight = (image->height + yShift) >> yShift;
551     aom_image_t * aomImage = aom_img_alloc(NULL, codec->internal->aomFormat, image->width, image->height, 16);
552     avifBool monochromeRequested = AVIF_FALSE;
553 
554     if (alpha) {
555         aomImage->range = (image->alphaRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE;
556         aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_RANGE, aomImage->range);
557         monochromeRequested = AVIF_TRUE;
558         for (uint32_t j = 0; j < image->height; ++j) {
559             uint8_t * srcAlphaRow = &image->alphaPlane[j * image->alphaRowBytes];
560             uint8_t * dstAlphaRow = &aomImage->planes[0][j * aomImage->stride[0]];
561             memcpy(dstAlphaRow, srcAlphaRow, image->alphaRowBytes);
562         }
563 
564         // Ignore UV planes when monochrome
565     } else {
566         aomImage->range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE;
567         aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_RANGE, aomImage->range);
568         int yuvPlaneCount = 3;
569         if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
570             yuvPlaneCount = 1; // Ignore UV planes when monochrome
571             monochromeRequested = AVIF_TRUE;
572         }
573         for (int yuvPlane = 0; yuvPlane < yuvPlaneCount; ++yuvPlane) {
574             uint32_t planeHeight = (yuvPlane == AVIF_CHAN_Y) ? image->height : uvHeight;
575 
576             for (uint32_t j = 0; j < planeHeight; ++j) {
577                 uint8_t * srcRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]];
578                 uint8_t * dstRow = &aomImage->planes[yuvPlane][j * aomImage->stride[yuvPlane]];
579                 memcpy(dstRow, srcRow, image->yuvRowBytes[yuvPlane]);
580             }
581         }
582 
583         aomImage->cp = (aom_color_primaries_t)image->colorPrimaries;
584         aomImage->tc = (aom_transfer_characteristics_t)image->transferCharacteristics;
585         aomImage->mc = (aom_matrix_coefficients_t)image->matrixCoefficients;
586         aomImage->csp = (aom_chroma_sample_position_t)image->yuvChromaSamplePosition;
587         aom_codec_control(&codec->internal->encoder, AV1E_SET_COLOR_PRIMARIES, aomImage->cp);
588         aom_codec_control(&codec->internal->encoder, AV1E_SET_TRANSFER_CHARACTERISTICS, aomImage->tc);
589         aom_codec_control(&codec->internal->encoder, AV1E_SET_MATRIX_COEFFICIENTS, aomImage->mc);
590         aom_codec_control(&codec->internal->encoder, AV1E_SET_CHROMA_SAMPLE_POSITION, aomImage->csp);
591     }
592 
593     if (monochromeRequested && !codec->internal->monochromeEnabled) {
594         // The user requested monochrome (via alpha or YUV400) but libaom cannot currently support
595         // monochrome (see chroma_check comment above). Manually set UV planes to 0.5.
596 
597         // aomImage is always 420 when we're monochrome
598         uint32_t monoUVWidth = (image->width + 1) >> 1;
599         uint32_t monoUVHeight = (image->height + 1) >> 1;
600 
601         for (int yuvPlane = 1; yuvPlane < 3; ++yuvPlane) {
602             if (image->depth > 8) {
603                 const uint16_t half = 1 << (image->depth - 1);
604                 for (uint32_t j = 0; j < monoUVHeight; ++j) {
605                     uint16_t * dstRow = (uint16_t *)&aomImage->planes[yuvPlane][j * aomImage->stride[yuvPlane]];
606                     for (uint32_t i = 0; i < monoUVWidth; ++i) {
607                         dstRow[i] = half;
608                     }
609                 }
610             } else {
611                 const uint8_t half = 128;
612                 size_t planeSize = (size_t)monoUVHeight * aomImage->stride[yuvPlane];
613                 memset(aomImage->planes[yuvPlane], half, planeSize);
614             }
615         }
616     }
617 
618     aom_enc_frame_flags_t encodeFlags = 0;
619     if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) {
620         encodeFlags |= AOM_EFLAG_FORCE_KF;
621     }
622     aom_codec_encode(&codec->internal->encoder, aomImage, 0, 1, encodeFlags);
623 
624     aom_codec_iter_t iter = NULL;
625     for (;;) {
626         const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&codec->internal->encoder, &iter);
627         if (pkt == NULL) {
628             break;
629         }
630         if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
631             avifCodecEncodeOutputAddSample(output, pkt->data.frame.buf, pkt->data.frame.sz, (pkt->data.frame.flags & AOM_FRAME_IS_KEY));
632         }
633     }
634 
635     aom_img_free(aomImage);
636     return AVIF_RESULT_OK;
637 }
638 
aomCodecEncodeFinish(avifCodec * codec,avifCodecEncodeOutput * output)639 static avifBool aomCodecEncodeFinish(avifCodec * codec, avifCodecEncodeOutput * output)
640 {
641     for (;;) {
642         // flush encoder
643         aom_codec_encode(&codec->internal->encoder, NULL, 0, 1, 0);
644 
645         avifBool gotPacket = AVIF_FALSE;
646         aom_codec_iter_t iter = NULL;
647         for (;;) {
648             const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&codec->internal->encoder, &iter);
649             if (pkt == NULL) {
650                 break;
651             }
652             if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
653                 gotPacket = AVIF_TRUE;
654                 avifCodecEncodeOutputAddSample(
655                     output, pkt->data.frame.buf, pkt->data.frame.sz, (pkt->data.frame.flags & AOM_FRAME_IS_KEY));
656             }
657         }
658 
659         if (!gotPacket) {
660             break;
661         }
662     }
663     return AVIF_TRUE;
664 }
665 
666 #endif // defined(AVIF_CODEC_AOM_ENCODE)
667 
avifCodecVersionAOM(void)668 const char * avifCodecVersionAOM(void)
669 {
670     return aom_codec_version_str();
671 }
672 
avifCodecCreateAOM(void)673 avifCodec * avifCodecCreateAOM(void)
674 {
675     avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
676     memset(codec, 0, sizeof(struct avifCodec));
677 
678 #if defined(AVIF_CODEC_AOM_DECODE)
679     codec->open = aomCodecOpen;
680     codec->getNextImage = aomCodecGetNextImage;
681 #endif
682 
683 #if defined(AVIF_CODEC_AOM_ENCODE)
684     codec->encodeImage = aomCodecEncodeImage;
685     codec->encodeFinish = aomCodecEncodeFinish;
686 #endif
687 
688     codec->destroyInternal = aomCodecDestroyInternal;
689     codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
690     memset(codec->internal, 0, sizeof(struct avifCodecInternal));
691     return codec;
692 }
693 
694 #ifdef __clang__
695 #pragma clang diagnostic pop
696 #endif
697