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