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