From 29189e4a6cad1d58a759899999b261a2d3237367 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Tue, 21 Sep 2021 15:00:23 +0800 Subject: [PATCH 01/14] Support for Progressive AVIF encoding --- apps/avifenc.c | 322 +++++++++++++++++++++++++++++++++++++++- include/avif/avif.h | 32 +++- include/avif/internal.h | 1 + src/codec_aom.c | 191 +++++++++++++++++------- src/codec_rav1e.c | 5 + src/codec_svt.c | 5 + src/read.c | 6 +- src/write.c | 178 +++++++++++++++++++--- 8 files changed, 653 insertions(+), 87 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index a8c2aaf5f6..b4af73da6f 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -9,6 +9,7 @@ #include "y4m.h" #include +#include #include #include #include @@ -104,6 +105,35 @@ static void syntax(void) printf(" --clap WN,WD,HN,HD,HON,HOD,VON,VOD: Add clap property (clean aperture). Width, Height, HOffset, VOffset (in num/denom pairs)\n"); printf(" --irot ANGLE : Add irot property (rotation). [0-3], makes (90 * ANGLE) degree rotation anti-clockwise\n"); printf(" --imir MODE : Add imir property (mirroring). 0=top-to-bottom, 1=left-to-right\n"); + printf(" --progressive LAYER_CONFIG : Encode progressive AVIF with given layer config\n"); + printf("\n"); + printf("progressive layer config format:\n"); + printf(" LAYER_CONFIG can be one of the two forms:\n"); + printf(" 1. SUB_CONFIG apply SUB_CONFIG to both color (YUV) planes and alpha plane\n"); + printf(" 2. SUB_CONFIG;SUB_CONFIG apply first SUB_CONFIG to color planes, second SUB_CONFIG to alpha plane\n"); + printf("\n"); + printf(" SUB_CONFIG is 0-4 LAYER_CONFIG joined by colon(:), and LAYER_CONFIG is in this form:\n"); + printf(" MinQ[,MaxQ][-ScaleH[,ScaleV]]\n"); + printf("\n"); + printf(" MinQ and MaxQ are min and max quantizers for this layer, and will overwrite values given by --min and --max.\n"); + printf(" Specially, when using aom with end-usage set to q or cq, min and max quantizers will use values given by --min and --max,\n"); + printf(" and cq-level of this layer will set to average of MinQ and MaxQ.\n"); + printf(" ScaleH and ScaleV are horizontal and vertical scale ratios [default=1, 1/2, 1/4, 1/8, 3/4, 3/5, 4/5].\n"); + printf(" If MaxQ is eliminated it uses the value of MinQ.\n"); + printf(" If ScaleH is eliminated it uses default value 1 (no scaling); if ScaleV is eliminated it uses the value of ScaleH.\n"); + printf("\n"); + printf(" Examples:\n"); + printf(" 40,62-1/2,1/4:30-1/2:10\n"); + printf(" Color and alpha planes both have 3 layers and share the same following config:\n"); + printf(" #0: min quantizer 40, max quantizer 62, 1/2 width, 1/4 height\n"); + printf(" #1: min quantizer 30, max quantizer 30, 1/2 width, 1/2 height\n"); + printf(" #2: min quantizer 10, max quantizer 10, full width, full height\n"); + printf("\n"); + printf(" 30,1/2:10;\n"); + printf(" Color planes have 2 layers, alpha plane is not layered.\n"); + printf("\n"); + printf(" ;30,1/2:10\n"); + printf(" Color planes is not layered, alpha plane have 2 layers.\n"); printf("\n"); if (avifCodecName(AVIF_CODEC_CHOICE_AOM, 0)) { printf("aom-specific advanced options:\n"); @@ -230,6 +260,271 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat return AVIF_TRUE; } +struct avifEncoderLayerConfig +{ + uint8_t layerCount; // Image layers for color sub image; 0 to disable layer image (default). + uint8_t layerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). + avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; + avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; +}; + +struct avifOptionEnumList +{ + const char * name; + int val; +}; + +static const struct avifOptionEnumList scalingModeList[] = { // + { "1/2", AVIF_SCALING_ONETWO }, { "1/4", AVIF_SCALING_ONEFOUR }, + { "1/8", AVIF_SCALING_ONEEIGHT }, { "3/4", AVIF_SCALING_THREEFOUR }, + { "3/5", AVIF_SCALING_THREEFIVE }, { "4/5", AVIF_SCALING_FOURFIVE }, + { "1", AVIF_SCALING_NORMAL }, { NULL, 0 } +}; + +static avifBool avifParseScalingMode(const char ** pArg, avifScalingMode * mode) +{ + const struct avifOptionEnumList * listptr; + for (listptr = scalingModeList; listptr->name; ++listptr) { + size_t matchLength = strlen(listptr->name); + if (!strncmp(*pArg, listptr->name, matchLength)) { + *mode = (avifScalingMode)listptr->val; + *pArg += matchLength; + return AVIF_TRUE; + } + } + + return AVIF_FALSE; +} + +#define FAIL_IF(condition, reason) \ + do { \ + if (condition) { \ + failReason = reason; \ + goto finish; \ + } \ + } while (0) + +static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * config, const char * arg) +{ + uint8_t * currLayerCount = &config->layerCount; + avifLayerConfig * currLayers = config->layers; + uint8_t currLayer = 0; + const char * failReason = NULL; + + enum scanState + { + VALUE, + COMMA, + HYPHEN, + COLON, + SEMICOLON + }; + enum scanState currState = *arg == ';' ? SEMICOLON : VALUE; + enum scanState targetState = SEMICOLON; + + enum + { + NONE, + MIN_Q, + MAX_Q, + H_SCALE, + V_SCALE, + } prevReadValue = NONE; + + for (;;) { + switch (currState) { + case VALUE: { + int64_t value; + avifScalingMode mode; + char * end; + switch (prevReadValue) { + case NONE: + value = strtoll(arg, &end, 10); + FAIL_IF(errno == ERANGE, "overflowed while reading min quantizer"); + FAIL_IF(end == arg, "can't parse min quantizer"); + FAIL_IF(value > 63, "min quantizer too big"); + + arg = end; + currLayers[currLayer].minQuantizer = (int)value; + currState = COMMA; + prevReadValue = MIN_Q; + break; + + case MIN_Q: + value = strtoll(arg, &end, 10); + FAIL_IF(errno == ERANGE, "overflowed while reading max quantizer"); + FAIL_IF(end == arg, "can't parse max quantizer"); + FAIL_IF(value > 63, "max quantizer too big"); + + arg = end; + currLayers[currLayer].maxQuantizer = (int)value; + currState = HYPHEN; + prevReadValue = MAX_Q; + break; + + case MAX_Q: + FAIL_IF(!avifParseScalingMode(&arg, &mode), "unknown scaling mode"); + + currLayers[currLayer].horizontalMode = mode; + currState = COMMA; + prevReadValue = H_SCALE; + break; + + case H_SCALE: + FAIL_IF(!avifParseScalingMode(&arg, &mode), "unknown scaling mode"); + + currLayers[currLayer].verticalMode = mode; + currState = COLON; + prevReadValue = V_SCALE; + break; + + case V_SCALE: + FAIL_IF(AVIF_TRUE, "too many values in layer config"); + } + break; + } + + case COMMA: + case HYPHEN: + case COLON: + case SEMICOLON: + switch (*arg) { + case ',': + targetState = COMMA; + break; + case '-': + targetState = HYPHEN; + break; + case ':': + targetState = COLON; + break; + case ';': + case '\0': + targetState = SEMICOLON; + break; + default: + FAIL_IF(AVIF_TRUE, "unexpected separator"); + } + + FAIL_IF(currState > targetState, "too many config entries"); + + avifBool earlyEnd = currState < targetState; + switch (targetState) { + case VALUE: + FAIL_IF(AVIF_TRUE, "unknown state"); + break; + + case COMMA: + FAIL_IF(earlyEnd, "unknown state"); + break; + + case HYPHEN: + if (!earlyEnd) { + FAIL_IF(prevReadValue != MAX_Q, "unknown state"); + break; + } + + FAIL_IF(prevReadValue != MIN_Q, "unknown state"); + currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; + prevReadValue = MAX_Q; + break; + + case COLON: + if (earlyEnd) { + switch (prevReadValue) { + case MIN_Q: + currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; + currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; + currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + break; + + case MAX_Q: + currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; + currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + break; + + case H_SCALE: + currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + break; + + case V_SCALE: + case NONE: + FAIL_IF(AVIF_TRUE, "unknown state"); + } + } + + ++currLayer; + FAIL_IF(currLayer >= MAX_AV1_LAYER_COUNT, "too many layers"); + prevReadValue = NONE; + break; + + case SEMICOLON: + if (earlyEnd) { + switch (prevReadValue) { + case MIN_Q: + currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; + currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; + currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + break; + + case MAX_Q: + currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; + currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + break; + + case H_SCALE: + currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + break; + + case V_SCALE: + break; + + case NONE: + FAIL_IF(AVIF_TRUE, "unknown state"); + } + + ++currLayer; + } + + *currLayerCount = currLayer; + if (*arg == ';') { + FAIL_IF(currLayers != config->layers, "too many sub image configurations"); + currLayers = config->layersAlpha; + currLayerCount = &config->layerCountAlpha; + + if (*(arg + 1) == '\0') { + goto finish; + } + + prevReadValue = NONE; + currLayer = 0; + } else { + // reached \0 + if (currLayers == config->layers) { + memcpy(config->layersAlpha, config->layers, sizeof(config->layers)); + config->layerCountAlpha = config->layerCount; + } + + goto finish; + } + break; + } + + ++arg; + currState = VALUE; + break; + } + } + +finish: + if (failReason == NULL) { + return AVIF_TRUE; + } + + fprintf(stderr, "ERROR: Failed reading progressive config: %s", failReason); + return AVIF_FALSE; +} + static avifInputFile * avifInputGetNextFile(avifInput * input) { if (input->useStdin) { @@ -475,6 +770,9 @@ int main(int argc, char * argv[]) avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; + struct avifEncoderLayerConfig layerConfig; + memset(&layerConfig, 0, sizeof(layerConfig)); + int argIndex = 1; while (argIndex < argc) { const char * arg = argv[argIndex]; @@ -754,6 +1052,12 @@ int main(int argc, char * argv[]) matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; // this is key for lossless } else if (!strcmp(arg, "-p") || !strcmp(arg, "--premultiply")) { premultiplyAlpha = AVIF_TRUE; + } else if (!strcmp(arg, "--progressive")) { + NEXTARG(); + if (!avifParseProgressiveConfig(&layerConfig, arg)) { + returnCode = 1; + goto cleanup; + } } else { // Positional argument input.files[input.filesCount].filename = arg; @@ -809,6 +1113,12 @@ int main(int argc, char * argv[]) } } + if (input.filesCount > 1 && (layerConfig.layerCount > 1 || layerConfig.layerCountAlpha > 1)) { + fprintf(stderr, "Progressive animated AVIF currently not supported.\n"); + returnCode = 1; + goto cleanup; + } + avifInputFile * firstFile = avifInputGetNextFile(&input); uint32_t sourceDepth = 0; avifAppSourceTiming firstSourceTiming; @@ -1078,7 +1388,11 @@ int main(int argc, char * argv[]) lossyHint = " (Lossless)"; } printf("AVIF to be written:%s\n", lossyHint); - avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1], AVIF_PROGRESSIVE_STATE_UNAVAILABLE); + avifBool progressive = layerConfig.layerCount > 1 || layerConfig.layerCountAlpha > 1; + avifImageDump(gridCells ? gridCells[0] : image, + gridDims[0], + gridDims[1], + progressive ? AVIF_PROGRESSIVE_STATE_AVAILABLE : AVIF_PROGRESSIVE_STATE_UNAVAILABLE); printf("Encoding with AV1 codec '%s' speed [%d], color QP [%d (%s) <-> %d (%s)], alpha QP [%d (%s) <-> %d (%s)], tileRowsLog2 [%d], tileColsLog2 [%d], %d worker thread(s), please wait...\n", avifCodecName(codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE), @@ -1099,6 +1413,12 @@ int main(int argc, char * argv[]) encoder->maxQuantizer = maxQuantizer; encoder->minQuantizerAlpha = minQuantizerAlpha; encoder->maxQuantizerAlpha = maxQuantizerAlpha; + + encoder->layerCount = layerConfig.layerCount; + encoder->layerCountAlpha = layerConfig.layerCountAlpha; + memcpy(encoder->layers, layerConfig.layers, sizeof(encoder->layers)); + memcpy(encoder->layersAlpha, layerConfig.layersAlpha, sizeof(encoder->layersAlpha)); + encoder->tileRowsLog2 = tileRowsLog2; encoder->tileColsLog2 = tileColsLog2; encoder->codecChoice = codecChoice; diff --git a/include/avif/avif.h b/include/avif/avif.h index e4f01c63cf..da85e3bd25 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -85,6 +85,8 @@ typedef int avifBool; #define AVIF_SPEED_SLOWEST 0 #define AVIF_SPEED_FASTEST 10 +#define MAX_AV1_LAYER_COUNT 4 + typedef enum avifPlanesFlag { AVIF_PLANES_YUV = (1 << 0), @@ -778,9 +780,11 @@ typedef enum avifProgressiveState // for an image sequence. AVIF_PROGRESSIVE_STATE_UNAVAILABLE = 0, - // The current AVIF/Source offers a progressive image, but avifDecoder.allowProgressive is not - // enabled, so it will behave as if the image was not progressive and will simply decode the - // best version of this item. + // For decoder, this means the current AVIF/Source offers a progressive image, but + // avifDecoder.allowProgressive is not enabled, so it will behave as if the image was not + // progressive and will simply decode the best version of this item. + // For encoder, this means at least one of color and alpha image has multiple layers and + // indicates this is a progressive image. AVIF_PROGRESSIVE_STATE_AVAILABLE, // The current AVIF/Source offers a progressive image, and avifDecoder.allowProgressive is true. @@ -971,6 +975,23 @@ AVIF_API avifResult avifDecoderNthImageMaxExtent(const avifDecoder * decoder, ui struct avifEncoderData; struct avifCodecSpecificOptions; +typedef enum avifScalingMode { + AVIF_SCALING_NORMAL = 0, + AVIF_SCALING_FOURFIVE = 1, + AVIF_SCALING_THREEFIVE = 2, + AVIF_SCALING_THREEFOUR = 3, + AVIF_SCALING_ONEFOUR = 4, + AVIF_SCALING_ONEEIGHT = 5, + AVIF_SCALING_ONETWO = 6 +} avifScalingMode; + +typedef struct avifLayerConfig { + int minQuantizer; + int maxQuantizer; + avifScalingMode horizontalMode; + avifScalingMode verticalMode; +} avifLayerConfig; + // Notes: // * If avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree() // * If (maxThreads < 2), multithreading is disabled @@ -999,6 +1020,11 @@ typedef struct avifEncoder int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default). uint64_t timescale; // timescale of the media (Hz) + uint8_t layerCount; // Image layers for color sub image; 0 to disable layer image (default). + uint8_t layerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). + avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; + avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; + // stats from the most recent write avifIOStats ioStats; diff --git a/include/avif/internal.h b/include/avif/internal.h index cf3f231727..df7b86d6d6 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -243,6 +243,7 @@ typedef avifResult (*avifCodecEncodeImageFunc)(struct avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + int layerIndex, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output); typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output); diff --git a/src/codec_aom.c b/src/codec_aom.c index 655c1687c4..a9c7190ff9 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -64,8 +64,9 @@ struct avifCodecInternal aom_codec_ctx_t encoder; avifPixelFormatInfo formatInfo; aom_img_fmt_t aomFormat; + aom_codec_enc_cfg_t cfg; avifBool monochromeEnabled; - // Whether cfg.rc_end_usage was set with an + // Whether cfg->rc_end_usage was set with an // avifEncoderSetCodecSpecificOption(encoder, "end-usage", value) call. avifBool endUsageSet; // Whether cq-level was set with an @@ -139,7 +140,9 @@ static avifBool aomCodecGetNextImage(struct avifCodec * codec, } } else if (sample) { codec->internal->iter = NULL; - if (aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL)) { + aom_codec_err_t ret; + ret = aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL); + if (ret) { return AVIF_FALSE; } spatialID = sample->spatialID; @@ -511,23 +514,31 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + int layerIndex, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output) { + avifBool layerCount = alpha ? encoder->layerCountAlpha : encoder->layerCount; + if (layerCount > 1) { + addImageFlags &= ~AVIF_ADD_IMAGE_FLAG_SINGLE; + } + + aom_codec_enc_cfg_t * cfg = &codec->internal->cfg; + // 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; + if (!codec->internal->encoderInitialized) { - // 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 the new AOM_USAGE_ALL_INTRA (added in https://crbug.com/aomedia/2959) for still // image encoding if it is available. #if defined(AOM_USAGE_ALL_INTRA) @@ -580,8 +591,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo); aom_codec_iface_t * encoderInterface = aom_codec_av1_cx(); - struct aom_codec_enc_cfg cfg; - aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, &cfg, aomUsage); + 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; @@ -621,44 +631,37 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - 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; + 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; 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; + cfg->g_limit = 1; // Use the default settings of the new AOM_USAGE_ALL_INTRA (added in // https://crbug.com/aomedia/2959). Note that AOM_USAGE_ALL_INTRA - // also sets cfg.rc_end_usage to AOM_Q by default, which we do not + // also sets cfg->rc_end_usage to AOM_Q by default, which we do not // set here. // // 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; + cfg->g_lag_in_frames = 0; // Disable automatic placement of key frames by the encoder. - cfg.kf_mode = AOM_KF_DISABLED; + cfg->kf_mode = AOM_KF_DISABLED; // Tell libaom that all frames will be key frames. - cfg.kf_max_dist = 0; + cfg->kf_max_dist = 0; } - if (encoder->maxThreads > 1) { - cfg.g_threads = encoder->maxThreads; + if (layerCount > 1) { + cfg->g_lag_in_frames = 0; } - - int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63); - int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63); - if (alpha) { - minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63); - maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63); + if (encoder->maxThreads > 1) { + cfg->g_threads = encoder->maxThreads; } - avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS)); - cfg.rc_min_quantizer = minQuantizer; - cfg.rc_max_quantizer = maxQuantizer; codec->internal->monochromeEnabled = AVIF_FALSE; if (aomVersion > aomVersion_2_0_0) { @@ -666,17 +669,17 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // access nonexistent UV planes when encoding monochrome at faster libavif "speeds". It // was fixed shortly after the 2.0.0 libaom release, and the fix exists in both the // master and applejack branches. This ensures that the next version *after* 2.0.0 will - // have the fix, and we must avoid cfg.monochrome until then. + // have the fix, and we must avoid cfg->monochrome until then. // // Bugfix Change-Id: https://aomedia-review.googlesource.com/q/I26a39791f820b4d4e1d63ff7141f594c3c7181f5 if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) { codec->internal->monochromeEnabled = AVIF_TRUE; - cfg.monochrome = 1; + cfg->monochrome = 1; } } - if (!avifProcessAOMOptionsPreInit(codec, alpha, &cfg)) { + if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) { return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION; } @@ -684,7 +687,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (image->depth > 8) { encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH; } - if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, &cfg, encoderFlags) != AOM_CODEC_OK) { + 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), @@ -693,9 +696,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } codec->internal->encoderInitialized = AVIF_TRUE; - if (lossless) { - aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1); - } if (encoder->maxThreads > 1) { aom_codec_control(&codec->internal->encoder, AV1E_SET_ROW_MT, 1); } @@ -712,23 +712,71 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, return AVIF_RESULT_UNKNOWN_ERROR; } } + + if (layerCount > 1) { + if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } + if (!avifProcessAOMOptionsPostInit(codec, alpha)) { return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION; } -#if defined(AOM_USAGE_ALL_INTRA) - if (aomUsage == AOM_USAGE_ALL_INTRA && !codec->internal->endUsageSet && !codec->internal->cqLevelSet) { - // The default rc_end_usage in all intra mode is AOM_Q, which requires cq-level to - // function. A libavif user may not know this internal detail and therefore may only - // set the min and max quantizers in the avifEncoder struct. If this is the case, set - // cq-level to a reasonable value for the user, otherwise the default cq-level - // (currently 10) will be unknowingly used. - assert(cfg.rc_end_usage == AOM_Q); - unsigned int cqLevel = (cfg.rc_min_quantizer + cfg.rc_max_quantizer) / 2; + } + + int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63); + int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63); + if (alpha) { + minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63); + } + + if (layerCount > 1) { + minQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].minQuantizer, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].maxQuantizer, 0, 63); + if (alpha) { + minQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].minQuantizer, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].maxQuantizer, 0, 63); + } + + // It's hard to modify the --advanced option to distinguish each layer, so convert min and max quantizers + // to cq-level to allow usage of q and cq mode in layer image. + if (cfg->rc_end_usage == AOM_Q || cfg->rc_end_usage == AOM_CQ) { + unsigned int cqLevel; + if (alpha) { + cqLevel = (encoder->layersAlpha[layerIndex].minQuantizer + encoder->layersAlpha[layerIndex].maxQuantizer) / 2; + } else { + cqLevel = (encoder->layers[layerIndex].minQuantizer + encoder->layers[layerIndex].maxQuantizer) / 2; + } aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); } -#endif } + avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS)); + cfg->rc_min_quantizer = minQuantizer; + cfg->rc_max_quantizer = maxQuantizer; + + if (lossless) { + aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1); + } + + if (aom_codec_enc_config_set(&codec->internal->encoder, cfg) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + +#if defined(AOM_USAGE_ALL_INTRA) + if (aomUsage == AOM_USAGE_ALL_INTRA && !codec->internal->endUsageSet && !codec->internal->cqLevelSet) { + // The default rc_end_usage in all intra mode is AOM_Q, which requires cq-level to + // function. A libavif user may not know this internal detail and therefore may only + // set the min and max quantizers in the avifEncoder struct. If this is the case, set + // cq-level to a reasonable value for the user, otherwise the default cq-level + // (currently 10) will be unknowingly used. + assert(cfg->rc_end_usage == AOM_Q); + unsigned int cqLevel = (cfg->rc_min_quantizer + cfg->rc_max_quantizer) / 2; + aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); + } +#endif + 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 @@ -792,6 +840,19 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aomImage.stride[0] = image->alphaRowBytes; } + if (encoder->layerCountAlpha > 1) { + aom_scaling_mode_t scaling_mode = { (AOM_SCALING_MODE)encoder->layersAlpha[layerIndex].horizontalMode, + (AOM_SCALING_MODE)encoder->layersAlpha[layerIndex].verticalMode }; + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } + // Ignore UV planes when monochrome } else { aomImage.range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE; @@ -833,6 +894,19 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aom_codec_control(&codec->internal->encoder, AV1E_SET_TRANSFER_CHARACTERISTICS, aomImage.tc); aom_codec_control(&codec->internal->encoder, AV1E_SET_MATRIX_COEFFICIENTS, aomImage.mc); aom_codec_control(&codec->internal->encoder, AV1E_SET_CHROMA_SAMPLE_POSITION, aomImage.csp); + + if (encoder->layerCount > 1) { + aom_scaling_mode_t scaling_mode = { (AOM_SCALING_MODE)encoder->layers[layerIndex].horizontalMode, + (AOM_SCALING_MODE)encoder->layers[layerIndex].verticalMode }; + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } } unsigned char * monoUVPlane = NULL; @@ -877,6 +951,11 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) { encodeFlags |= AOM_EFLAG_FORCE_KF; } + if (layerIndex > 0) { + encodeFlags |= AOM_EFLAG_NO_REF_LAST2 | AOM_EFLAG_NO_REF_LAST3 | AOM_EFLAG_NO_REF_GF | AOM_EFLAG_NO_REF_ARF | + AOM_EFLAG_NO_REF_BWD | AOM_EFLAG_NO_REF_ARF2 | AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_NO_UPD_GF | + AOM_EFLAG_NO_UPD_ARF | AOM_EFLAG_NO_UPD_ENTROPY; + } aom_codec_err_t encodeErr = aom_codec_encode(&codec->internal->encoder, &aomImage, 0, 1, encodeFlags); avifFree(monoUVPlane); if (aomImageAllocated) { @@ -901,7 +980,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) { + if ((layerIndex + 1 == layerCount) || (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) { // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images if (!aomCodecEncodeFinish(codec, output)) { diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 2ae38323bb..3fcb15f1a1 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -51,6 +51,7 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + int layerIndex, uint32_t addImageFlags, avifCodecEncodeOutput * output) { @@ -59,6 +60,10 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, RaConfig * rav1eConfig = NULL; RaFrame * rav1eFrame = NULL; + if (layerIndex != 0) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + if (!codec->internal->rav1eContext) { if (codec->csOptions->count > 0) { // None are currently supported! diff --git a/src/codec_svt.c b/src/codec_svt.c index d7a7e88e10..37ba5547a4 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -28,6 +28,7 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, + int layerIndex, uint32_t addImageFlags, avifCodecEncodeOutput * output) { @@ -36,6 +37,10 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, EbBufferHeaderType * input_buffer = NULL; EbErrorType res = EB_ErrorNone; + if (layerIndex != 0) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + int y_shift = 0; // EbColorRange svt_range; if (alpha) { diff --git a/src/read.c b/src/read.c index e35bcb23cd..baae5d5219 100644 --- a/src/read.c +++ b/src/read.c @@ -37,8 +37,6 @@ static const size_t xmpContentTypeSize = sizeof(xmpContentType); // can't be more than 4 unique tuples right now. #define MAX_IPMA_VERSION_AND_FLAGS_SEEN 4 -#define MAX_AV1_LAYER_COUNT 4 - // --------------------------------------------------------------------------- // Box data structures @@ -1534,7 +1532,7 @@ static avifBool avifParseItemLocationBox(avifMeta * meta, const uint8_t * raw, s } } - uint16_t dataReferenceIndex; // unsigned int(16) data_ref rence_index; + uint16_t dataReferenceIndex; // unsigned int(16) data_reference_index; CHECK(avifROStreamReadU16(&s, &dataReferenceIndex)); // uint64_t baseOffset; // unsigned int(base_offset_size*8) base_offset; CHECK(avifROStreamReadUX8(&s, &baseOffset, baseOffsetSize)); // @@ -1827,7 +1825,7 @@ static avifBool avifParseAV1LayeredImageIndexingProperty(avifProperty * prop, co } } - // Layer sizes will be validated layer (when the item's size is known) + // Layer sizes will be validated later (when the item's size is known) return AVIF_TRUE; } diff --git a/src/write.c b/src/write.c index eff6d2c15e..670dd51e0e 100644 --- a/src/write.c +++ b/src/write.c @@ -93,12 +93,25 @@ typedef struct avifEncoderItem uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item uint32_t gridRows; // if non-zero (legal range [1-256]), this is a grid item + uint8_t layerCount; + uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id struct ipmaArray ipma; } avifEncoderItem; AVIF_ARRAY_DECLARE(avifEncoderItemArray, avifEncoderItem, item); +// --------------------------------------------------------------------------- +// avifEncoderItemReference + +// pointer to one "item" interested in + +typedef struct avifEncoderItemReference +{ + avifEncoderItem * item; +} avifEncoderItemReference; +AVIF_ARRAY_DECLARE(avifEncoderItemReferenceArray, avifEncoderItemReference, ref); + // --------------------------------------------------------------------------- // avifEncoderFrame @@ -264,6 +277,8 @@ avifEncoder * avifEncoderCreate(void) encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; + encoder->layerCount = 0; + encoder->layerCountAlpha = 0; encoder->tileRowsLog2 = 0; encoder->tileColsLog2 = 0; encoder->speed = AVIF_SPEED_DEFAULT; @@ -518,6 +533,12 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_NO_CODEC_AVAILABLE; } + + // Currently, layered image can only have one frame. + if ((encoder->layerCount > 1 || encoder->layerCountAlpha > 1) && encoder->data->frames.count > 0) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + // ----------------------------------------------------------------------- // Validate images @@ -747,14 +768,18 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->codec) { + item->layerCount = item->alpha ? encoder->layerCountAlpha : encoder->layerCount; + item->layerCount = item->layerCount == 0 ? 1 : item->layerCount; const avifImage * cellImage = cellImages[item->cellIndex]; - avifResult encodeResult = - item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, addImageFlags, item->encodeOutput); - if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { - encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; - } - if (encodeResult != AVIF_RESULT_OK) { - return encodeResult; + for (int layerIndex = 0; layerIndex < item->layerCount; ++layerIndex) { + avifResult encodeResult = + item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, layerIndex, addImageFlags, item->encodeOutput); + if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { + encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; + } + if (encodeResult != AVIF_RESULT_OK) { + return encodeResult; + } } } } @@ -816,7 +841,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } - if (item->encodeOutput->samples.count != encoder->data->frames.count) { + if (item->encodeOutput->samples.count != encoder->data->frames.count * item->layerCount) { return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } } @@ -916,6 +941,21 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; + avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; + avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; + + if (item->layerCount > 1) { + // Layered Image, write location for all samples + avifRWStreamWriteU16(&s, item->layerCount); // unsigned int(16) extent_count; + for (int i = 0; i < item->layerCount; ++i) { + avifEncoderItemAddMdatFixup(item, &s); // + avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; + avifRWStreamWriteU32(&s, (uint32_t)item->encodeOutput->samples.sample[i].data.size); // unsigned int(length_size*8) extent_length; + } + + continue; + } + uint32_t contentSize = (uint32_t)item->metadataPayload.size; if (item->encodeOutput->samples.count > 0) { // This is choosing sample 0's size as there are two cases here: @@ -929,8 +969,6 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size; } - avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; - avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; avifEncoderItemAddMdatFixup(item, &s); // avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; @@ -1024,15 +1062,16 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) continue; } - if (item->dimgFromID) { - // All image cells from a grid should share the exact same properties, so see if we've - // already written properties out for another cell in this grid, and if so, just steal - // their ipma and move on. This is a sneaky way to provide iprp deduplication. + if (item->dimgFromID && item->layerCount < 2) { + // All image cells from a grid should share the exact same properties unless they are + // layered image which have different al1x, so see if we've already written properties + // out for another cell in this grid, and if so, just steal their ipma and move on. + // This is a sneaky way to provide iprp deduplication. avifBool foundPreviousCell = AVIF_FALSE; for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) { avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex]; - if (item->dimgFromID == dedupItem->dimgFromID) { + if (item->dimgFromID == dedupItem->dimgFromID && dedupItem->layerCount < 2) { // We've already written dedup's items out. Steal their ipma indices and move on! memcpy(&item->ipma, &dedupItem->ipma, sizeof(struct ipmaArray)); foundPreviousCell = AVIF_TRUE; @@ -1089,6 +1128,36 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup); } + + if (item->layerCount > 1) { + // Layered Image Indexing Property + + avifItemPropertyDedupStart(dedup); + avifBoxMarker a1lx = avifRWStreamWriteBox(&dedup->s, "a1lx", AVIF_BOX_SIZE_TBD); + uint32_t layerSize[MAX_AV1_LAYER_COUNT - 1] = { 0 }; + uint8_t largeSize = 0; + + for (uint8_t validLayer = 0; validLayer < item->layerCount - 1; ++validLayer) { + uint32_t size = (uint32_t)item->encodeOutput->samples.sample[validLayer].data.size; + layerSize[validLayer] = size; + if (size > 65535) { + largeSize = 1; + } + } + + avifRWStreamWriteU8(&dedup->s, largeSize); + if (largeSize) { + for (uint32_t layer = 0; layer < MAX_AV1_LAYER_COUNT - 1; ++layer) { + avifRWStreamWriteU32(&dedup->s, layerSize[layer]); + } + } else { + for (uint32_t layer = 0; layer < MAX_AV1_LAYER_COUNT - 1; ++layer) { + avifRWStreamWriteU16(&dedup->s, (uint16_t)layerSize[layer]); + } + } + avifRWStreamFinishBox(&dedup->s, a1lx); + ipmaPush(&item->ipma, avifItemPropertyDedupFinish(dedup, &s), AVIF_FALSE); + } } avifRWStreamFinishBox(&s, ipco); avifItemPropertyDedupDestroy(dedup); @@ -1345,11 +1414,17 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifBoxMarker mdat = avifRWStreamWriteBox(&s, "mdat", AVIF_BOX_SIZE_TBD); const size_t mdatStartOffset = avifRWStreamOffset(&s); - for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) { + + avifEncoderItemReferenceArray layeredAlphaItems; + avifEncoderItemReferenceArray layeredColorItems; + avifArrayCreate(&layeredAlphaItems, sizeof(avifEncoderItemReference), 1); + avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1); + avifBool useInterleave = encoder->layerCount > 1 || encoder->layerCountAlpha > 1; + + for (uint32_t itemPasses = 0; itemPasses < 2; ++itemPasses) { // Use multiple passes to pack in the following order: // * Pass 0: metadata (Exif/XMP) - // * Pass 1: alpha (AV1) - // * Pass 2: all other item data (AV1 color) + // * Pass 1: item data (AV1 color & alpha) // // See here for the discussion on alpha coming before color: // https://github.com/AOMediaCodec/libavif/issues/287 @@ -1359,7 +1434,6 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // and ignoreExif are enabled. // const avifBool metadataPass = (itemPasses == 0); - const avifBool alphaPass = (itemPasses == 1); for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; @@ -1372,13 +1446,24 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // only process metadata (XMP/Exif) payloads when metadataPass is true continue; } - if (alphaPass != item->alpha) { - // only process alpha payloads when alphaPass is true - continue; - } size_t chunkOffset = 0; + // Interleave - Pick out and record layered image items, interleave them later. + // Expect layer image item has same number of samples and fixups. + if (useInterleave && item->encodeOutput->samples.count > 0 && + item->encodeOutput->samples.count == item->mdatFixups.count) { + + avifEncoderItemReference * ref; + if (item->alpha) { + ref = (avifEncoderItemReference *)avifArrayPushPtr(&layeredAlphaItems); + } else { + ref = (avifEncoderItemReference *)avifArrayPushPtr(&layeredColorItems); + } + ref->item = item; + continue; + } + // Deduplication - See if an identical chunk to this has already been written if (item->encodeOutput->samples.count > 0) { avifEncodeSample * sample = &item->encodeOutput->samples.sample[0]; @@ -1415,6 +1500,53 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) } } } + + // Interleave each sample (layer) of each item. + // + // * Pass 0: alpha item + // * Pass 1: color item + // + // See here for the discussion on alpha coming before color: + // https://github.com/AOMediaCodec/libavif/issues/287 + + if (useInterleave) { + avifBool hasMoreSample; + uint32_t layerIndex = 0; + do { + hasMoreSample = AVIF_FALSE; + for (int samplePass = 0; samplePass < 2; ++samplePass) { + avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? &layeredAlphaItems : &layeredColorItems; + for (uint32_t itemIndex = 0; itemIndex < currentItems->count; ++itemIndex) { + avifEncoderItem * item = currentItems->ref[itemIndex].item; + if (item->encodeOutput->samples.count <= layerIndex) { + // We've already written all samples from this item + continue; + } else if (item->encodeOutput->samples.count > layerIndex + 1) { + hasMoreSample = AVIF_TRUE; + } + avifRWData * data = &item->encodeOutput->samples.sample[layerIndex].data; + size_t chunkOffset = avifEncoderFindExistingChunk(&s, mdatStartOffset, data->data, data->size); + if (!chunkOffset) { + // We've never seen this chunk before; write it out + chunkOffset = avifRWStreamOffset(&s); + avifRWStreamWrite(&s, data->data, data->size); + if (samplePass == 0) { + encoder->ioStats.alphaOBUSize += data->size; + } else { + encoder->ioStats.colorOBUSize += data->size; + } + + size_t prevOffset = avifRWStreamOffset(&s); + avifRWStreamSetOffset(&s, item->mdatFixups.fixup[layerIndex].offset); + avifRWStreamWriteU32(&s, (uint32_t)chunkOffset); + avifRWStreamSetOffset(&s, prevOffset); + } + } + } + ++layerIndex; + } while (hasMoreSample); + } + avifRWStreamFinishBox(&s, mdat); // ----------------------------------------------------------------------- From 7e82a522a377112e022bbae6c51e5f9d5f40be09 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Thu, 23 Sep 2021 20:35:34 +0800 Subject: [PATCH 02/14] Accept arbitrary scale ratio in layer config --- apps/avifenc.c | 248 ++++++++++++++++++++++---------------------- include/avif/avif.h | 11 +- src/codec_aom.c | 134 +++++++++++++++++++----- 3 files changed, 234 insertions(+), 159 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index b4af73da6f..b2112ec5c6 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -118,7 +118,7 @@ static void syntax(void) printf(" MinQ and MaxQ are min and max quantizers for this layer, and will overwrite values given by --min and --max.\n"); printf(" Specially, when using aom with end-usage set to q or cq, min and max quantizers will use values given by --min and --max,\n"); printf(" and cq-level of this layer will set to average of MinQ and MaxQ.\n"); - printf(" ScaleH and ScaleV are horizontal and vertical scale ratios [default=1, 1/2, 1/4, 1/8, 3/4, 3/5, 4/5].\n"); + printf(" ScaleH and ScaleV are horizontal and vertical scale ratios [default=1, or any fraction].\n"); printf(" If MaxQ is eliminated it uses the value of MinQ.\n"); printf(" If ScaleH is eliminated it uses default value 1 (no scaling); if ScaleV is eliminated it uses the value of ScaleH.\n"); printf("\n"); @@ -260,227 +260,228 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat return AVIF_TRUE; } +#define CHECK(condition, reason) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "ERROR: Failed reading progressive config: %s\n", reason); \ + return AVIF_FALSE; \ + } \ + } while (0) + struct avifEncoderLayerConfig { - uint8_t layerCount; // Image layers for color sub image; 0 to disable layer image (default). + uint8_t layerCount; // Image layers for color sub image; 0 to disable layer image (default). uint8_t layerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; }; -struct avifOptionEnumList -{ - const char * name; - int val; -}; - -static const struct avifOptionEnumList scalingModeList[] = { // - { "1/2", AVIF_SCALING_ONETWO }, { "1/4", AVIF_SCALING_ONEFOUR }, - { "1/8", AVIF_SCALING_ONEEIGHT }, { "3/4", AVIF_SCALING_THREEFOUR }, - { "3/5", AVIF_SCALING_THREEFIVE }, { "4/5", AVIF_SCALING_FOURFIVE }, - { "1", AVIF_SCALING_NORMAL }, { NULL, 0 } -}; +static const avifScalingMode avifScalingModeNormal = { 1, 1 }; static avifBool avifParseScalingMode(const char ** pArg, avifScalingMode * mode) { - const struct avifOptionEnumList * listptr; - for (listptr = scalingModeList; listptr->name; ++listptr) { - size_t matchLength = strlen(listptr->name); - if (!strncmp(*pArg, listptr->name, matchLength)) { - *mode = (avifScalingMode)listptr->val; - *pArg += matchLength; + char * end; + uint64_t value = strtoull(*pArg, &end, 10); + CHECK(errno != ERANGE, "overflowed while reading scale nominator"); + CHECK(end != *pArg, "can't parse scale nominator"); + if (*end != '/') { + if (value == 1) { + *mode = avifScalingModeNormal; + *pArg = end; return AVIF_TRUE; } + return AVIF_FALSE; } - - return AVIF_FALSE; + mode->numerator = value; + *pArg = end + 1; + value = strtoull(*pArg, &end, 10); + CHECK(errno != ERANGE, "overflowed while reading scale denominator"); + CHECK(end != *pArg, "can't parse scale denominator"); + mode->denominator = value; + *pArg = end; + return AVIF_TRUE; } -#define FAIL_IF(condition, reason) \ - do { \ - if (condition) { \ - failReason = reason; \ - goto finish; \ - } \ - } while (0) +enum avifProgressiveConfigScannerState +{ + AVIF_PROGRESSIVE_SCANNER_STATE_VALUE, + AVIF_PROGRESSIVE_SCANNER_STATE_COMMA, + AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN, + AVIF_PROGRESSIVE_SCANNER_STATE_COLON, + AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON +}; + +enum avifProgressiveConfigValueType +{ + AVIF_PROGRESSIVE_VALUE_TYPE_NONE, + AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q, + AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q, + AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE, + AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE, +}; static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * config, const char * arg) { uint8_t * currLayerCount = &config->layerCount; avifLayerConfig * currLayers = config->layers; uint8_t currLayer = 0; - const char * failReason = NULL; - - enum scanState - { - VALUE, - COMMA, - HYPHEN, - COLON, - SEMICOLON - }; - enum scanState currState = *arg == ';' ? SEMICOLON : VALUE; - enum scanState targetState = SEMICOLON; - - enum - { - NONE, - MIN_Q, - MAX_Q, - H_SCALE, - V_SCALE, - } prevReadValue = NONE; + + enum avifProgressiveConfigScannerState currState = *arg == ';' ? AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON + : AVIF_PROGRESSIVE_SCANNER_STATE_VALUE; + enum avifProgressiveConfigScannerState targetState = AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON; + + enum avifProgressiveConfigValueType prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; for (;;) { switch (currState) { - case VALUE: { + case AVIF_PROGRESSIVE_SCANNER_STATE_VALUE: { int64_t value; avifScalingMode mode; char * end; switch (prevReadValue) { - case NONE: + case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: value = strtoll(arg, &end, 10); - FAIL_IF(errno == ERANGE, "overflowed while reading min quantizer"); - FAIL_IF(end == arg, "can't parse min quantizer"); - FAIL_IF(value > 63, "min quantizer too big"); + CHECK(errno != ERANGE, "overflowed while reading min quantizer"); + CHECK(end != arg, "can't parse min quantizer"); + CHECK(value <= 63, "min quantizer too big"); arg = end; currLayers[currLayer].minQuantizer = (int)value; - currState = COMMA; - prevReadValue = MIN_Q; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q; break; - case MIN_Q: + case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: value = strtoll(arg, &end, 10); - FAIL_IF(errno == ERANGE, "overflowed while reading max quantizer"); - FAIL_IF(end == arg, "can't parse max quantizer"); - FAIL_IF(value > 63, "max quantizer too big"); + CHECK(errno != ERANGE, "overflowed while reading max quantizer"); + CHECK(end != arg, "can't parse max quantizer"); + CHECK(value <= 63, "max quantizer too big"); arg = end; currLayers[currLayer].maxQuantizer = (int)value; - currState = HYPHEN; - prevReadValue = MAX_Q; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q; break; - case MAX_Q: - FAIL_IF(!avifParseScalingMode(&arg, &mode), "unknown scaling mode"); + case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: + CHECK(avifParseScalingMode(&arg, &mode), "unknown scaling mode"); currLayers[currLayer].horizontalMode = mode; - currState = COMMA; - prevReadValue = H_SCALE; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE; break; - case H_SCALE: - FAIL_IF(!avifParseScalingMode(&arg, &mode), "unknown scaling mode"); + case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: + CHECK(avifParseScalingMode(&arg, &mode), "unknown scaling mode"); currLayers[currLayer].verticalMode = mode; - currState = COLON; - prevReadValue = V_SCALE; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_COLON; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE; break; - case V_SCALE: - FAIL_IF(AVIF_TRUE, "too many values in layer config"); + case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: + CHECK(AVIF_FALSE, "too many values in layer config"); } break; } - case COMMA: - case HYPHEN: - case COLON: - case SEMICOLON: + case AVIF_PROGRESSIVE_SCANNER_STATE_COMMA: + case AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN: + case AVIF_PROGRESSIVE_SCANNER_STATE_COLON: + case AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON: switch (*arg) { case ',': - targetState = COMMA; + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; break; case '-': - targetState = HYPHEN; + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN; break; case ':': - targetState = COLON; + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_COLON; break; case ';': case '\0': - targetState = SEMICOLON; + targetState = AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON; break; default: - FAIL_IF(AVIF_TRUE, "unexpected separator"); + CHECK(AVIF_FALSE, "unexpected separator"); } - FAIL_IF(currState > targetState, "too many config entries"); + CHECK(currState <= targetState, "too many config entries"); avifBool earlyEnd = currState < targetState; switch (targetState) { - case VALUE: - FAIL_IF(AVIF_TRUE, "unknown state"); + case AVIF_PROGRESSIVE_SCANNER_STATE_VALUE: + CHECK(AVIF_FALSE, "unknown state"); break; - case COMMA: - FAIL_IF(earlyEnd, "unknown state"); + case AVIF_PROGRESSIVE_SCANNER_STATE_COMMA: + CHECK(!earlyEnd, "unknown state"); break; - case HYPHEN: + case AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN: if (!earlyEnd) { - FAIL_IF(prevReadValue != MAX_Q, "unknown state"); + CHECK(prevReadValue == AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q, "unknown state"); break; } - FAIL_IF(prevReadValue != MIN_Q, "unknown state"); + CHECK(prevReadValue == AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q, "unknown state"); currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; - prevReadValue = MAX_Q; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q; break; - case COLON: + case AVIF_PROGRESSIVE_SCANNER_STATE_COLON: if (earlyEnd) { switch (prevReadValue) { - case MIN_Q: + case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; - currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; - currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; break; - case MAX_Q: - currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; - currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; break; - case H_SCALE: - currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: + currLayers[currLayer].verticalMode = currLayers[currLayer].horizontalMode; break; - case V_SCALE: - case NONE: - FAIL_IF(AVIF_TRUE, "unknown state"); + case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: + case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: + CHECK(AVIF_FALSE, "unknown state"); } } ++currLayer; - FAIL_IF(currLayer >= MAX_AV1_LAYER_COUNT, "too many layers"); - prevReadValue = NONE; + CHECK(currLayer < MAX_AV1_LAYER_COUNT, "too many layers"); + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; break; - case SEMICOLON: + case AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON: if (earlyEnd) { switch (prevReadValue) { - case MIN_Q: + case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; - currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; - currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; break; - case MAX_Q: - currLayers[currLayer].horizontalMode = AVIF_SCALING_NORMAL; - currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: + currLayers[currLayer].horizontalMode = avifScalingModeNormal; + currLayers[currLayer].verticalMode = avifScalingModeNormal; break; - case H_SCALE: - currLayers[currLayer].verticalMode = AVIF_SCALING_NORMAL; + case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: + currLayers[currLayer].verticalMode = currLayers[currLayer].horizontalMode; break; - case V_SCALE: + case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: break; - case NONE: - FAIL_IF(AVIF_TRUE, "unknown state"); + case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: + CHECK(AVIF_FALSE, "unknown state"); } ++currLayer; @@ -488,7 +489,7 @@ static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * confi *currLayerCount = currLayer; if (*arg == ';') { - FAIL_IF(currLayers != config->layers, "too many sub image configurations"); + CHECK(currLayers == config->layers, "too many sub image configurations"); currLayers = config->layersAlpha; currLayerCount = &config->layerCountAlpha; @@ -496,7 +497,7 @@ static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * confi goto finish; } - prevReadValue = NONE; + prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; currLayer = 0; } else { // reached \0 @@ -511,18 +512,13 @@ static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * confi } ++arg; - currState = VALUE; + currState = AVIF_PROGRESSIVE_SCANNER_STATE_VALUE; break; } } finish: - if (failReason == NULL) { - return AVIF_TRUE; - } - - fprintf(stderr, "ERROR: Failed reading progressive config: %s", failReason); - return AVIF_FALSE; + return AVIF_TRUE; } static avifInputFile * avifInputGetNextFile(avifInput * input) diff --git a/include/avif/avif.h b/include/avif/avif.h index da85e3bd25..ba283e83fb 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -975,14 +975,9 @@ AVIF_API avifResult avifDecoderNthImageMaxExtent(const avifDecoder * decoder, ui struct avifEncoderData; struct avifCodecSpecificOptions; -typedef enum avifScalingMode { - AVIF_SCALING_NORMAL = 0, - AVIF_SCALING_FOURFIVE = 1, - AVIF_SCALING_THREEFIVE = 2, - AVIF_SCALING_THREEFOUR = 3, - AVIF_SCALING_ONEFOUR = 4, - AVIF_SCALING_ONEEIGHT = 5, - AVIF_SCALING_ONETWO = 6 +typedef struct avifScalingMode { + uint64_t numerator; + uint64_t denominator; } avifScalingMode; typedef struct avifLayerConfig { diff --git a/src/codec_aom.c b/src/codec_aom.c index a9c7190ff9..be1745bc82 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -50,6 +50,15 @@ #endif #endif +typedef enum avifAOMScaleConfigMethod +{ + // Set using AV1E_SET_SVC_PARAMS + AVIF_AOM_SCALE_SVC_PARAMS, + + // Set using AOME_SET_SCALEMODE + AVIF_AOM_SCALE_SCALEMODE +} avifAOMScaleConfigMethod; + struct avifCodecInternal { #if defined(AVIF_CODEC_AOM_DECODE) @@ -72,6 +81,7 @@ struct avifCodecInternal // Whether cq-level was set with an // avifEncoderSetCodecSpecificOption(encoder, "cq-level", value) call. avifBool cqLevelSet; + avifAOMScaleConfigMethod scaleConfigMethod; #endif }; @@ -280,6 +290,31 @@ static aom_img_fmt_t avifImageCalcAOMFmt(const avifImage * image, avifBool alpha return fmt; } +struct aomScalingModeMapList +{ + avifScalingMode 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(avifScalingMode * avifMode, AOM_SCALING_MODE * aomMode) +{ + for (int i = 0; i < scalingModeMapSize; ++i) { + if (scalingModeMap[i].avifMode.numerator == avifMode->numerator && scalingModeMap[i].avifMode.denominator == avifMode->denominator) { + *aomMode = scalingModeMap[i].aomMode; + return AVIF_TRUE; + } + } + + return AVIF_FALSE; +} + #if !defined(HAVE_AOM_CODEC_SET_OPTION) static avifBool aomOptionParseInt(const char * str, int * val) { @@ -518,7 +553,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output) { - avifBool layerCount = alpha ? encoder->layerCountAlpha : encoder->layerCount; + uint8_t layerCount = alpha ? encoder->layerCountAlpha : encoder->layerCount; + avifLayerConfig * layers = alpha ? encoder->layersAlpha : encoder->layers; if (layerCount > 1) { addImageFlags &= ~AVIF_ADD_IMAGE_FLAG_SINGLE; } @@ -714,8 +750,52 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } if (layerCount > 1) { - if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; +#if defined(AVIF_AOM_LAYER_CONFIG_PREFER_SVC_PARAMS) + avifBool useSvcParams = AVIF_TRUE; + for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { + avifLayerConfig * layer = &layers[configIndex]; + if (layer->horizontalMode.numerator != layer->verticalMode.numerator || + layer->horizontalMode.denominator != layer->verticalMode.denominator) { + useSvcParams = AVIF_FALSE; + break; + } + } +#else + avifBool useSvcParams = AVIF_FALSE; + for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { + avifLayerConfig * layer = &layers[configIndex]; + AOM_SCALING_MODE mode; + if (layer->horizontalMode.numerator == layer->verticalMode.numerator && + layer->horizontalMode.denominator == layer->verticalMode.denominator && + !avifFindAOMScalingMode(&layer->horizontalMode, &mode)) { + useSvcParams = AVIF_TRUE; + break; + } + } +#endif + + codec->internal->scaleConfigMethod = useSvcParams ? AVIF_AOM_SCALE_SVC_PARAMS : AVIF_AOM_SCALE_SCALEMODE; + if (useSvcParams) { + aom_svc_params_t svcParams; + memset(&svcParams, 0, sizeof(aom_svc_params_t)); + svcParams.number_temporal_layers = 1; + svcParams.number_spatial_layers = layerCount; + svcParams.framerate_factor[0] = 1; + for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { + avifLayerConfig * layer = &layers[configIndex]; + svcParams.min_quantizers[configIndex] = layer->minQuantizer; + svcParams.max_quantizers[configIndex] = layer->maxQuantizer; + svcParams.scaling_factor_num[configIndex] = (int)layer->horizontalMode.numerator; + svcParams.scaling_factor_den[configIndex] = (int)layer->horizontalMode.denominator; + } + + if (aom_codec_control(&codec->internal->encoder, AV1E_SET_SVC_PARAMS, &svcParams) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } else { + if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } } } @@ -840,19 +920,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aomImage.stride[0] = image->alphaRowBytes; } - if (encoder->layerCountAlpha > 1) { - aom_scaling_mode_t scaling_mode = { (AOM_SCALING_MODE)encoder->layersAlpha[layerIndex].horizontalMode, - (AOM_SCALING_MODE)encoder->layersAlpha[layerIndex].verticalMode }; - - if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } - - if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } - } - // Ignore UV planes when monochrome } else { aomImage.range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE; @@ -894,18 +961,35 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aom_codec_control(&codec->internal->encoder, AV1E_SET_TRANSFER_CHARACTERISTICS, aomImage.tc); aom_codec_control(&codec->internal->encoder, AV1E_SET_MATRIX_COEFFICIENTS, aomImage.mc); aom_codec_control(&codec->internal->encoder, AV1E_SET_CHROMA_SAMPLE_POSITION, aomImage.csp); + } - if (encoder->layerCount > 1) { - aom_scaling_mode_t scaling_mode = { (AOM_SCALING_MODE)encoder->layers[layerIndex].horizontalMode, - (AOM_SCALING_MODE)encoder->layers[layerIndex].verticalMode }; + if (layerCount > 1) { + switch (codec->internal->scaleConfigMethod) { + case AVIF_AOM_SCALE_SVC_PARAMS: { + aom_svc_layer_id_t layerId = { layerIndex, 0 }; + if (aom_codec_control(&codec->internal->encoder, AV1E_SET_SVC_LAYER_ID, &layerId) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } break; - if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } + case AVIF_AOM_SCALE_SCALEMODE: { + aom_scaling_mode_t scaling_mode; + if (!avifFindAOMScalingMode(&layers[layerIndex].horizontalMode, &scaling_mode.h_scaling_mode)) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } - if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } + if (!avifFindAOMScalingMode(&layers[layerIndex].verticalMode, &scaling_mode.v_scaling_mode)) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } break; } } From 50a7ae9ecf6b4579753f12bc1f2a7373e1204005 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Tue, 5 Oct 2021 12:49:32 +0800 Subject: [PATCH 03/14] Allow different input for different layers (experimental, mostly reusing grid functionality) --- apps/avifenc.c | 66 ++++++++++++++++++++++++++--------------- include/avif/avif.h | 13 +++++++- include/avif/internal.h | 2 +- src/avif.c | 1 + src/codec_aom.c | 2 +- src/codec_rav1e.c | 2 +- src/codec_svt.c | 2 +- src/write.c | 46 +++++++++++++++++++++++----- 8 files changed, 98 insertions(+), 36 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index b2112ec5c6..2b0dc826fe 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -84,9 +84,9 @@ static void syntax(void) AVIF_QUANTIZER_LOSSLESS); printf(" --tilerowslog2 R : Set log2 of number of tile rows (0-6, default: 0)\n"); printf(" --tilecolslog2 C : Set log2 of number of tile columns (0-6, default: 0)\n"); - printf(" -g,--grid MxN : Encode a single-image grid AVIF with M cols & N rows. Either supply MxN identical W/H/D images, or a single\n"); - printf(" image that can be evenly split into the MxN grid and follow AVIF grid image restrictions. The grid will adopt\n"); - printf(" the color profile of the first image supplied.\n"); + printf(" -g,--grid MxN(xL) : Encode a single-image grid AVIF with M cols & N rows & L layers. Either supply MxNxL identical W/H/D images,\n"); + printf(" or L identical W/H/D images that each can be evenly split into the MxN grid and follow AVIF grid image restrictions.\n"); + printf(" The grid will adopt the color profile of the first image supplied.\n"); printf(" -s,--speed S : Encoder speed (%d-%d, slowest-fastest, 'default' or 'd' for codec internal defaults. default speed: 6)\n", AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST); @@ -628,7 +628,7 @@ static avifBool readEntireFile(const char * filename, avifRWData * raw) return AVIF_TRUE; } -static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, avifImage ** gridCells) +static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, uint32_t layerCount, avifImage ** gridCells) { if ((gridSplitImage->width % gridCols) != 0) { fprintf(stderr, "ERROR: Can't split image width (%u) evenly into %u columns.\n", gridSplitImage->width, gridCols); @@ -654,7 +654,7 @@ static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gr for (uint32_t gridX = 0; gridX < gridCols; ++gridX) { uint32_t gridIndex = gridX + (gridY * gridCols); avifImage * cellImage = avifImageCreateEmpty(); - gridCells[gridIndex] = cellImage; + gridCells[gridIndex * layerCount] = cellImage; avifImageCopy(cellImage, gridSplitImage, 0); cellImage->width = cellWidth; @@ -746,8 +746,9 @@ int main(int argc, char * argv[]) avifBool cicpExplicitlySet = AVIF_FALSE; avifBool premultiplyAlpha = AVIF_FALSE; int gridDimsCount = 0; - uint32_t gridDims[8]; // only the first two are used + uint32_t gridDims[8]; // only the first two or three are used uint32_t gridCellCount = 0; + uint32_t gridCellLayerCount = 0; avifImage ** gridCells = NULL; avifImage * gridSplitImage = NULL; // used for cleanup tracking memset(gridDims, 0, sizeof(gridDims)); @@ -877,7 +878,9 @@ int main(int argc, char * argv[]) } else if (!strcmp(arg, "-g") || !strcmp(arg, "--grid")) { NEXTARG(); gridDimsCount = parseU32List(gridDims, arg); - if (gridDimsCount != 2) { + if (gridDimsCount == 2) { + gridDims[2] = 1; + } else if (gridDimsCount != 3) { fprintf(stderr, "ERROR: Invalid grid dims: %s\n", arg); returnCode = 1; goto cleanup; @@ -887,6 +890,11 @@ int main(int argc, char * argv[]) returnCode = 1; goto cleanup; } + if ((gridDims[2] == 0 || gridDims[2] > MAX_AV1_LAYER_COUNT)) { + fprintf(stderr, "ERROR: Invalid layer count (valid layer range [1-4]): %s\n", arg); + returnCode = 1; + goto cleanup; + } } else if (!strcmp(arg, "--cicp") || !strcmp(arg, "--nclx")) { NEXTARG(); int cicp[3]; @@ -1109,7 +1117,7 @@ int main(int argc, char * argv[]) } } - if (input.filesCount > 1 && (layerConfig.layerCount > 1 || layerConfig.layerCountAlpha > 1)) { + if (gridDimsCount == 0 && input.filesCount > 1 && (layerConfig.layerCount > 1 || layerConfig.layerCountAlpha > 1)) { fprintf(stderr, "Progressive animated AVIF currently not supported.\n"); returnCode = 1; goto cleanup; @@ -1298,17 +1306,18 @@ int main(int argc, char * argv[]) gridCellCount = gridDims[0] * gridDims[1]; printf("Preparing to encode a %ux%u grid (%u cells)...\n", gridDims[0], gridDims[1], gridCellCount); - gridCells = calloc(gridCellCount, sizeof(avifImage *)); + gridCellLayerCount = gridCellCount * gridDims[2]; + gridCells = calloc(gridCellLayerCount, sizeof(avifImage *)); gridCells[0] = image; // take ownership of image - uint32_t gridCellIndex = 0; + uint32_t gridCellLayerIndex = 0; avifInputFile * nextFile; while ((nextFile = avifInputGetNextFile(&input)) != NULL) { - if (!gridCellIndex) { + if (!gridCellLayerIndex) { printf("Loading additional cells for grid image (%u cells)...\n", gridCellCount); } - ++gridCellIndex; - if (gridCellIndex >= gridCellCount) { + ++gridCellLayerIndex; + if (gridCellLayerIndex >= gridCellLayerCount) { // We have enough, warn and continue fprintf(stderr, "WARNING: [--grid] More than %u images were supplied for this %ux%u grid. The rest will be ignored.\n", @@ -1324,7 +1333,7 @@ int main(int argc, char * argv[]) cellImage->matrixCoefficients = image->matrixCoefficients; cellImage->yuvRange = image->yuvRange; cellImage->alphaPremultiplied = image->alphaPremultiplied; - gridCells[gridCellIndex] = cellImage; + gridCells[gridCellLayerIndex] = cellImage; avifAppFileFormat nextInputFormat = avifInputReadImage(&input, cellImage, NULL, NULL); if (nextInputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) { @@ -1360,19 +1369,22 @@ int main(int argc, char * argv[]) } } - if (gridCellIndex == 0) { + if (gridCellLayerIndex == gridDims[2] - 1 && (gridDims[0] != 1 || gridDims[1] != 1)) { printf("Single image input for a grid image. Attempting to split into %u cells...\n", gridCellCount); - gridSplitImage = image; - gridCells[0] = NULL; - if (!avifImageSplitGrid(gridSplitImage, gridDims[0], gridDims[1], gridCells)) { - returnCode = 1; - goto cleanup; + for (uint32_t layerIndex = 0; layerIndex < gridDims[2]; ++layerIndex) { + gridSplitImage = gridCells[layerIndex]; + gridCells[layerIndex] = NULL; + if (!avifImageSplitGrid(gridSplitImage, gridDims[0], gridDims[1], gridDims[2], gridCells + layerIndex)) { + returnCode = 1; + goto cleanup; + } } - gridCellIndex = gridCellCount - 1; + + gridCellLayerIndex = gridCellLayerCount - 1; } - if (gridCellIndex != gridCellCount - 1) { + if (gridCellLayerIndex != gridCellLayerCount - 1) { fprintf(stderr, "ERROR: Not enough input files for grid image! (expecting %u, or a single image to be split)\n", gridCellCount); returnCode = 1; goto cleanup; @@ -1423,8 +1435,14 @@ int main(int argc, char * argv[]) encoder->keyframeInterval = keyframeInterval; if (gridDimsCount > 0) { - avifResult addImageResult = - avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); + avifResult addImageResult; + if (gridDims[2] > 1) { + addImageResult = + avifEncoderAddImageProgressiveGrid(encoder, gridDims[0], gridDims[1], gridDims[2], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); + } else { + addImageResult = + avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); + } if (addImageResult != AVIF_RESULT_OK) { fprintf(stderr, "ERROR: Failed to encode image grid: %s\n", avifResultToString(addImageResult)); returnCode = 1; diff --git a/include/avif/avif.h b/include/avif/avif.h index ba283e83fb..50f73082cc 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -152,7 +152,8 @@ typedef enum avifResult AVIF_RESULT_IO_ERROR, AVIF_RESULT_WAITING_ON_IO, // similar to EAGAIN/EWOULDBLOCK, this means the avifIO doesn't have necessary data available yet AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid - AVIF_RESULT_NOT_IMPLEMENTED // a requested code path is not (yet) implemented + AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented + AVIF_RESULT_INVALID_LAYERS, } avifResult; AVIF_API const char * avifResultToString(avifResult result); @@ -1067,6 +1068,16 @@ AVIF_API avifResult avifEncoderAddImageGrid(avifEncoder * encoder, uint32_t gridRows, const avifImage * const * cellImages, avifAddImageFlags addImageFlags); +avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags); +avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, + uint32_t gridCols, + uint32_t gridRows, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags); AVIF_API avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output); // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. These diff --git a/include/avif/internal.h b/include/avif/internal.h index df7b86d6d6..c1e7b43991 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -243,7 +243,7 @@ typedef avifResult (*avifCodecEncodeImageFunc)(struct avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, - int layerIndex, + uint32_t layerIndex, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output); typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output); diff --git a/src/avif.c b/src/avif.c index 64b961fb74..b565130639 100644 --- a/src/avif.c +++ b/src/avif.c @@ -93,6 +93,7 @@ const char * avifResultToString(avifResult result) case AVIF_RESULT_WAITING_ON_IO: return "Waiting on IO"; case AVIF_RESULT_INVALID_ARGUMENT: return "Invalid argument"; case AVIF_RESULT_NOT_IMPLEMENTED: return "Not implemented"; + case AVIF_RESULT_INVALID_LAYERS: return "Invalid layer image"; case AVIF_RESULT_UNKNOWN_ERROR: default: break; diff --git a/src/codec_aom.c b/src/codec_aom.c index be1745bc82..dfe91e2b82 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -549,7 +549,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, - int layerIndex, + uint32_t layerIndex, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output) { diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 3fcb15f1a1..8c300f019f 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -51,7 +51,7 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, - int layerIndex, + uint32_t layerIndex, uint32_t addImageFlags, avifCodecEncodeOutput * output) { diff --git a/src/codec_svt.c b/src/codec_svt.c index 37ba5547a4..e2112d81ed 100644 --- a/src/codec_svt.c +++ b/src/codec_svt.c @@ -28,7 +28,7 @@ static avifResult svtCodecEncodeImage(avifCodec * codec, avifEncoder * encoder, const avifImage * image, avifBool alpha, - int layerIndex, + uint32_t layerIndex, uint32_t addImageFlags, avifCodecEncodeOutput * output) { diff --git a/src/write.c b/src/write.c index 670dd51e0e..68c0a458a8 100644 --- a/src/write.c +++ b/src/write.c @@ -522,6 +522,7 @@ static void avifWriteGridPayload(avifRWData * data, uint32_t gridCols, uint32_t static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, + uint32_t layerCount, const avifImage * const * cellImages, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) @@ -543,6 +544,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, // Validate images const uint32_t cellCount = gridCols * gridRows; + const uint32_t imageCount = cellCount * layerCount; if (cellCount == 0) { return AVIF_RESULT_INVALID_ARGUMENT; } @@ -647,8 +649,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, // when encoding a single image. encoder->data->alphaPresent = AVIF_FALSE; - for (uint32_t cellIndex = 0; cellIndex < cellCount; ++cellIndex) { - const avifImage * cellImage = cellImages[cellIndex]; + for (uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex) { + const avifImage * cellImage = cellImages[imageIndex]; if (!avifImageIsOpaque(cellImage)) { encoder->data->alphaPresent = AVIF_TRUE; break; @@ -770,10 +772,11 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, if (item->codec) { item->layerCount = item->alpha ? encoder->layerCountAlpha : encoder->layerCount; item->layerCount = item->layerCount == 0 ? 1 : item->layerCount; - const avifImage * cellImage = cellImages[item->cellIndex]; - for (int layerIndex = 0; layerIndex < item->layerCount; ++layerIndex) { + for (uint32_t layerIndex = 0; layerIndex < item->layerCount; ++layerIndex) { + const uint32_t index = (layerIndex > (layerCount - 1)) ? (layerCount - 1) : layerIndex; + const avifImage * layerImage = cellImages[item->cellIndex * layerCount + index]; avifResult encodeResult = - item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, layerIndex, addImageFlags, item->encodeOutput); + item->codec->encodeImage(item->codec, encoder, layerImage, item->alpha, layerIndex, addImageFlags, item->encodeOutput); if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } @@ -792,7 +795,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags); + return avifEncoderAddImageInternal(encoder, 1, 1, 1, &image, durationInTimescales, addImageFlags); } avifResult avifEncoderAddImageGrid(avifEncoder * encoder, @@ -805,7 +808,36 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { return AVIF_RESULT_INVALID_IMAGE_GRID; } - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, 1, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported +} + + +avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags) +{ + avifDiagnosticsClearError(&encoder->diag); + if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { + return AVIF_RESULT_INVALID_LAYERS; + } + return avifEncoderAddImageInternal(encoder, 1, 1, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported +} + +avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, + uint32_t gridCols, + uint32_t gridRows, + uint32_t layerCount, + const avifImage * const * layerImages, + avifAddImageFlags addImageFlags) { + avifDiagnosticsClearError(&encoder->diag); + if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { + return AVIF_RESULT_INVALID_LAYERS; + } + if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, layerCount, layerImages, 1, addImageFlags); } static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) From 2016e2e7e83a81d117060a76f54e821f7b7cafd3 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Tue, 7 Jun 2022 15:17:58 +0800 Subject: [PATCH 04/14] address review changes --- apps/avifenc.c | 20 ++++---- include/avif/avif.h | 6 +-- src/codec_aom.c | 82 ++++++++++++++++--------------- src/write.c | 116 ++++++++++++++++++++++---------------------- 4 files changed, 115 insertions(+), 109 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index 1ba2c557dd..7e83db171a 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -270,8 +270,8 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat struct avifEncoderLayerConfig { - uint8_t layerCount; // Image layers for color sub image; 0 to disable layer image (default). - uint8_t layerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). + int extraLayerCount; // Image layers for color sub image; 0 to disable layer image (default). + int extraLayerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; }; @@ -322,7 +322,7 @@ enum avifProgressiveConfigValueType static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * config, const char * arg) { - uint8_t * currLayerCount = &config->layerCount; + int * currLayerCount = &config->extraLayerCount; avifLayerConfig * currLayers = config->layers; uint8_t currLayer = 0; @@ -487,11 +487,11 @@ static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * confi ++currLayer; } - *currLayerCount = currLayer; + *currLayerCount = currLayer - 1; if (*arg == ';') { CHECK(currLayers == config->layers, "too many sub image configurations"); currLayers = config->layersAlpha; - currLayerCount = &config->layerCountAlpha; + currLayerCount = &config->extraLayerCountAlpha; if (*(arg + 1) == '\0') { goto finish; @@ -503,7 +503,7 @@ static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * confi // reached \0 if (currLayers == config->layers) { memcpy(config->layersAlpha, config->layers, sizeof(config->layers)); - config->layerCountAlpha = config->layerCount; + config->extraLayerCountAlpha = config->extraLayerCount; } goto finish; @@ -1119,7 +1119,7 @@ int main(int argc, char * argv[]) } } - if (gridDimsCount == 0 && input.filesCount > 1 && (layerConfig.layerCount > 1 || layerConfig.layerCountAlpha > 1)) { + if (gridDimsCount == 0 && input.filesCount > 1 && (layerConfig.extraLayerCount > 0 || layerConfig.extraLayerCountAlpha > 0)) { fprintf(stderr, "Progressive animated AVIF currently not supported.\n"); returnCode = 1; goto cleanup; @@ -1398,7 +1398,7 @@ int main(int argc, char * argv[]) lossyHint = " (Lossless)"; } printf("AVIF to be written:%s\n", lossyHint); - avifBool progressive = layerConfig.layerCount > 1 || layerConfig.layerCountAlpha > 1; + avifBool progressive = layerConfig.extraLayerCount > 0 || layerConfig.extraLayerCountAlpha > 0; avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1], @@ -1424,8 +1424,8 @@ int main(int argc, char * argv[]) encoder->minQuantizerAlpha = minQuantizerAlpha; encoder->maxQuantizerAlpha = maxQuantizerAlpha; - encoder->layerCount = layerConfig.layerCount; - encoder->layerCountAlpha = layerConfig.layerCountAlpha; + encoder->extraLayerCount = layerConfig.extraLayerCount; + encoder->extraLayerCountAlpha = layerConfig.extraLayerCountAlpha; memcpy(encoder->layers, layerConfig.layers, sizeof(encoder->layers)); memcpy(encoder->layersAlpha, layerConfig.layersAlpha, sizeof(encoder->layersAlpha)); diff --git a/include/avif/avif.h b/include/avif/avif.h index 9bd331b008..768350a014 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -154,7 +154,7 @@ typedef enum avifResult AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented AVIF_RESULT_OUT_OF_MEMORY, - AVIF_RESULT_INVALID_LAYERS, + AVIF_RESULT_INVALID_LAYERS } avifResult; AVIF_API const char * avifResultToString(avifResult result); @@ -1042,8 +1042,8 @@ typedef struct avifEncoder int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default). uint64_t timescale; // timescale of the media (Hz) - uint8_t layerCount; // Image layers for color sub image; 0 to disable layer image (default). - uint8_t layerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). + int extraLayerCount; // Extra layers for color sub image; 0 to disable layer image (default). + int extraLayerCountAlpha; // Extra layers for alpha sub image; 0 to disable layer image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; diff --git a/src/codec_aom.c b/src/codec_aom.c index 80e79aaa0a..4e29a21515 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -152,9 +152,7 @@ static avifBool aomCodecGetNextImage(struct avifCodec * codec, } } else if (sample) { codec->internal->iter = NULL; - aom_codec_err_t ret; - ret = aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL); - if (ret) { + if (aom_codec_decode(&codec->internal->decoder, sample->data.data, sample->data.size, NULL)) { return AVIF_FALSE; } spatialID = sample->spatialID; @@ -310,7 +308,7 @@ static const struct aomScalingModeMapList scalingModeMap[] = { static const int scalingModeMapSize = sizeof(scalingModeMap) / sizeof(scalingModeMap[0]); -static avifBool avifFindAOMScalingMode(avifScalingMode * avifMode, AOM_SCALING_MODE * aomMode) +static avifBool avifFindAOMScalingMode(const avifScalingMode * avifMode, AOM_SCALING_MODE * aomMode) { for (int i = 0; i < scalingModeMapSize; ++i) { if (scalingModeMap[i].avifMode.numerator == avifMode->numerator && scalingModeMap[i].avifMode.denominator == avifMode->denominator) { @@ -567,9 +565,11 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, avifAddImageFlags addImageFlags, avifCodecEncodeOutput * output) { - uint8_t layerCount = alpha ? encoder->layerCountAlpha : encoder->layerCount; - avifLayerConfig * layers = alpha ? encoder->layersAlpha : encoder->layers; - if (layerCount > 1) { + uint32_t extraLayerCount = alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; + const avifLayerConfig * layers = alpha ? encoder->layersAlpha : encoder->layers; + // Disable single image flag to allow interlayer compression + // todo: we clearly don't want ALL_INTRA, but other tweaks should be reviewed as well. + if (extraLayerCount > 0) { addImageFlags &= ~AVIF_ADD_IMAGE_FLAG_SINGLE; } @@ -653,16 +653,16 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // 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; + 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; + cfg->rc_end_usage = AOM_CBR; break; #if defined(AOM_USAGE_ALL_INTRA) case AOM_USAGE_ALL_INTRA: - cfg.rc_end_usage = AOM_Q; + cfg->rc_end_usage = AOM_Q; break; #endif } @@ -724,7 +724,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // Tell libaom that all frames will be key frames. cfg->kf_max_dist = 0; } - if (layerCount > 1) { + if (extraLayerCount > 0) { cfg->g_lag_in_frames = 0; } if (encoder->maxThreads > 1) { @@ -781,7 +781,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - if (layerCount > 1) { + if (extraLayerCount > 0) { + int layerCount = extraLayerCount + 1; + #if defined(AVIF_AOM_LAYER_CONFIG_PREFER_SVC_PARAMS) avifBool useSvcParams = AVIF_TRUE; for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { @@ -794,8 +796,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } #else avifBool useSvcParams = AVIF_FALSE; - for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { - avifLayerConfig * layer = &layers[configIndex]; + for (int configIndex = 0; configIndex < layerCount; ++configIndex) { + const avifLayerConfig * layer = &layers[configIndex]; AOM_SCALING_MODE mode; if (layer->horizontalMode.numerator == layer->verticalMode.numerator && layer->horizontalMode.denominator == layer->verticalMode.denominator && @@ -813,8 +815,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, svcParams.number_temporal_layers = 1; svcParams.number_spatial_layers = layerCount; svcParams.framerate_factor[0] = 1; - for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { - avifLayerConfig * layer = &layers[configIndex]; + for (int configIndex = 0; configIndex < layerCount; ++configIndex) { + const avifLayerConfig * layer = &layers[configIndex]; svcParams.min_quantizers[configIndex] = layer->minQuantizer; svcParams.max_quantizers[configIndex] = layer->maxQuantizer; svcParams.scaling_factor_num[configIndex] = (int)layer->horizontalMode.numerator; @@ -834,6 +836,12 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (!avifProcessAOMOptionsPostInit(codec, alpha)) { return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION; } + + if (!codec->internal->tuningSet) { + if (aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, AOM_TUNE_SSIM) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } + } } int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63); @@ -843,16 +851,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63); } - if (layerCount > 1) { - minQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].minQuantizer, 0, 63); - maxQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].maxQuantizer, 0, 63); - if (alpha) { - minQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].minQuantizer, 0, 63); - maxQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].maxQuantizer, 0, 63); - } - - // It's hard to modify the --advanced option to distinguish each layer, so convert min and max quantizers - // to cq-level to allow usage of q and cq mode in layer image. + if (extraLayerCount > 0) { + // Provide a way to set per-layer cq-level to allow using q and cq mode in layered image. if (cfg->rc_end_usage == AOM_Q || cfg->rc_end_usage == AOM_CQ) { unsigned int cqLevel; if (alpha) { @@ -861,6 +861,13 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, cqLevel = (encoder->layers[layerIndex].minQuantizer + encoder->layers[layerIndex].maxQuantizer) / 2; } aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); + } else { + minQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].minQuantizer, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->layers[layerIndex].maxQuantizer, 0, 63); + if (alpha) { + minQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].minQuantizer, 0, 63); + maxQuantizer = AVIF_CLAMP(encoder->layersAlpha[layerIndex].maxQuantizer, 0, 63); + } } } @@ -888,12 +895,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); } #endif - if (!codec->internal->tuningSet) { - if (aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, AOM_TUNE_SSIM) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } - } - } aom_image_t aomImage; // We prefer to simply set the aomImage.planes[] pointers to the plane buffers in 'image'. When @@ -1003,7 +1004,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, aom_codec_control(&codec->internal->encoder, AV1E_SET_CHROMA_SAMPLE_POSITION, aomImage.csp); } - if (layerCount > 1) { + if (extraLayerCount > 0) { switch (codec->internal->scaleConfigMethod) { case AVIF_AOM_SCALE_SVC_PARAMS: { aom_svc_layer_id_t layerId = { layerIndex, 0 }; @@ -1075,11 +1076,13 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) { encodeFlags |= AOM_EFLAG_FORCE_KF; } - if (layerIndex > 0) { - encodeFlags |= AOM_EFLAG_NO_REF_LAST2 | AOM_EFLAG_NO_REF_LAST3 | AOM_EFLAG_NO_REF_GF | AOM_EFLAG_NO_REF_ARF | - AOM_EFLAG_NO_REF_BWD | AOM_EFLAG_NO_REF_ARF2 | AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_NO_UPD_GF | - AOM_EFLAG_NO_UPD_ARF | AOM_EFLAG_NO_UPD_ENTROPY; + if (extraLayerCount > 0) { + if (layerIndex > 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); avifFree(monoUVPlane); if (aomImageAllocated) { @@ -1104,8 +1107,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - if ((layerIndex + 1 == layerCount) || (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) { - // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images + if ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) || ((extraLayerCount > 0) && (layerIndex == extraLayerCount))) { + // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images. + // (For layered image, this is when the last layer is encoded.) if (!aomCodecEncodeFinish(codec, output)) { return AVIF_RESULT_UNKNOWN_ERROR; diff --git a/src/write.c b/src/write.c index 390fbdfb9a..1296567dfc 100644 --- a/src/write.c +++ b/src/write.c @@ -100,7 +100,7 @@ typedef struct avifEncoderItem uint32_t gridCols; // if non-zero (legal range [1-256]), this is a grid item uint32_t gridRows; // if non-zero (legal range [1-256]), this is a grid item - uint8_t layerCount; + uint32_t extraLayerCount; uint16_t dimgFromID; // if non-zero, make an iref from dimgFromID -> this id @@ -310,8 +310,8 @@ avifEncoder * avifEncoderCreate(void) encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; - encoder->layerCount = 0; - encoder->layerCountAlpha = 0; + encoder->extraLayerCount = 0; + encoder->extraLayerCountAlpha = 0; encoder->tileRowsLog2 = 0; encoder->tileColsLog2 = 0; encoder->speed = AVIF_SPEED_DEFAULT; @@ -622,9 +622,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_NO_CODEC_AVAILABLE; } - // Currently, layered image can only have one frame. - if ((encoder->layerCount > 1 || encoder->layerCountAlpha > 1) && encoder->data->frames.count > 0) { + if (((encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0)) && encoder->data->frames.count > 0) { return AVIF_RESULT_NOT_IMPLEMENTED; } @@ -843,9 +842,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; if (item->codec) { - item->layerCount = item->alpha ? encoder->layerCountAlpha : encoder->layerCount; - item->layerCount = item->layerCount == 0 ? 1 : item->layerCount; - for (uint32_t layerIndex = 0; layerIndex < item->layerCount; ++layerIndex) { + item->extraLayerCount = item->alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; + for (uint32_t layerIndex = 0; layerIndex < item->extraLayerCount + 1; ++layerIndex) { const uint32_t index = (layerIndex > (layerCount - 1)) ? (layerCount - 1) : layerIndex; const avifImage * layerImage = cellImages[item->cellIndex * layerCount + index]; avifResult encodeResult = @@ -946,7 +944,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } - if (item->encodeOutput->samples.count != encoder->data->frames.count * item->layerCount) { + if (item->encodeOutput->samples.count != encoder->data->frames.count * (item->extraLayerCount + 1)) { return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED; } } @@ -1047,38 +1045,29 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; - avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; - avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; + avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; + avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; + avifRWStreamWriteU16(&s, item->extraLayerCount + 1); // unsigned int(16) extent_count; + + for (uint32_t i = 0; i < item->extraLayerCount + 1; ++i) { + avifEncoderItemAddMdatFixup(item, &s); + avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; - if (item->layerCount > 1) { - // Layered Image, write location for all samples - avifRWStreamWriteU16(&s, item->layerCount); // unsigned int(16) extent_count; - for (int i = 0; i < item->layerCount; ++i) { - avifEncoderItemAddMdatFixup(item, &s); // - avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; + if (item->encodeOutput->samples.count == 0) { + avifRWStreamWriteU32(&s, (uint32_t)item->metadataPayload.size); // unsigned int(length_size*8) extent_length; + assert(item->extraLayerCount == 0); + } else { + // For non-layered image, this is choosing sample 0's size as there are two cases here: + // * This is a single image, in which case this is correct + // * This is an image sequence, but this file should still be a valid single-image avif, + // so there must still be a primary item pointing at a sync sample. Since the first + // frame of the image sequence is guaranteed to be a sync sample, it is chosen here. + // + // TODO: Offer the ability for a user to specify which frame in the sequence should + // become the primary item's image, and force that frame to be a keyframe. avifRWStreamWriteU32(&s, (uint32_t)item->encodeOutput->samples.sample[i].data.size); // unsigned int(length_size*8) extent_length; } - - continue; - } - - uint32_t contentSize = (uint32_t)item->metadataPayload.size; - if (item->encodeOutput->samples.count > 0) { - // This is choosing sample 0's size as there are two cases here: - // * This is a single image, in which case this is correct - // * This is an image sequence, but this file should still be a valid single-image avif, - // so there must still be a primary item pointing at a sync sample. Since the first - // frame of the image sequence is guaranteed to be a sync sample, it is chosen here. - // - // TODO: Offer the ability for a user to specify which frame in the sequence should - // become the primary item's image, and force that frame to be a keyframe. - contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size; } - - avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count; - avifEncoderItemAddMdatFixup(item, &s); // - avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; - avifRWStreamWriteU32(&s, (uint32_t)contentSize); // unsigned int(length_size*8) extent_length; } avifRWStreamFinishBox(&s, iloc); @@ -1169,16 +1158,16 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) continue; } - if (item->dimgFromID && item->layerCount < 2) { + if (item->dimgFromID && item->extraLayerCount == 0) { // All image cells from a grid should share the exact same properties unless they are - // layered image which have different al1x, so see if we've already written properties + // layered image which have different a1lx, so see if we've already written properties // out for another cell in this grid, and if so, just steal their ipma and move on. // This is a sneaky way to provide iprp deduplication. avifBool foundPreviousCell = AVIF_FALSE; for (uint32_t dedupIndex = 0; dedupIndex < itemIndex; ++dedupIndex) { avifEncoderItem * dedupItem = &encoder->data->items.item[dedupIndex]; - if (item->dimgFromID == dedupItem->dimgFromID && dedupItem->layerCount < 2) { + if (item->dimgFromID == dedupItem->dimgFromID && dedupItem->extraLayerCount == 0) { // We've already written dedup's items out. Steal their ipma indices and move on! item->ipma = dedupItem->ipma; foundPreviousCell = AVIF_TRUE; @@ -1236,29 +1225,31 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifEncoderWriteColorProperties(&s, imageMetadata, &item->ipma, dedup); } - if (item->layerCount > 1) { + if (item->extraLayerCount != 0) { // Layered Image Indexing Property avifItemPropertyDedupStart(dedup); avifBoxMarker a1lx = avifRWStreamWriteBox(&dedup->s, "a1lx", AVIF_BOX_SIZE_TBD); uint32_t layerSize[MAX_AV1_LAYER_COUNT - 1] = { 0 }; - uint8_t largeSize = 0; + avifBool largeSize = AVIF_FALSE; - for (uint8_t validLayer = 0; validLayer < item->layerCount - 1; ++validLayer) { + for (uint32_t validLayer = 0; validLayer < item->extraLayerCount; ++validLayer) { uint32_t size = (uint32_t)item->encodeOutput->samples.sample[validLayer].data.size; layerSize[validLayer] = size; - if (size > 65535) { - largeSize = 1; + if (size > 0xffff) { + largeSize = AVIF_TRUE; } } - avifRWStreamWriteU8(&dedup->s, largeSize); - if (largeSize) { - for (uint32_t layer = 0; layer < MAX_AV1_LAYER_COUNT - 1; ++layer) { + avifRWStreamWriteU8(&dedup->s, largeSize); // unsigned int(7) reserved = 0; + // unsigned int(1) large_size; + + // FieldLength = (large_size + 1) * 16; + // unsigned int(FieldLength) layer_size[3]; + for (uint32_t layer = 0; layer < MAX_AV1_LAYER_COUNT - 1; ++layer) { + if (largeSize) { avifRWStreamWriteU32(&dedup->s, layerSize[layer]); - } - } else { - for (uint32_t layer = 0; layer < MAX_AV1_LAYER_COUNT - 1; ++layer) { + } else { avifRWStreamWriteU16(&dedup->s, (uint16_t)layerSize[layer]); } } @@ -1547,7 +1538,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifEncoderItemReferenceArray layeredColorItems; avifArrayCreate(&layeredAlphaItems, sizeof(avifEncoderItemReference), 1); avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1); - avifBool useInterleave = encoder->layerCount > 1 || encoder->layerCountAlpha > 1; + avifBool useInterleave = (encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0); for (uint32_t itemPasses = 0; itemPasses < 2; ++itemPasses) { // Use multiple passes to pack in the following order: @@ -1631,8 +1622,10 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // Interleave each sample (layer) of each item. // - // * Pass 0: alpha item - // * Pass 1: color item + // - for each layer + // - for each grid cell + // - write alpha of this layer of this grid cell + // - write color of this layer of this grid cell // // See here for the discussion on alpha coming before color: // https://github.com/AOMediaCodec/libavif/issues/287 @@ -1642,9 +1635,15 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) uint32_t layerIndex = 0; do { hasMoreSample = AVIF_FALSE; - for (int samplePass = 0; samplePass < 2; ++samplePass) { - avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? &layeredAlphaItems : &layeredColorItems; - for (uint32_t itemIndex = 0; itemIndex < currentItems->count; ++itemIndex) { + // Assume color and alpha having same number of items (both single image, or both grid of same dimension) + for (uint32_t itemIndex = 0; itemIndex < layeredColorItems.count; ++itemIndex) { + for (int samplePass = 0; samplePass < 2; ++samplePass) { + avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? &layeredAlphaItems : &layeredColorItems; + if (itemIndex >= currentItems->count) { + break; + } + + // TODO: Offer the ability for a user to specify which grid cell should be written first. avifEncoderItem * item = currentItems->ref[itemIndex].item; if (item->encodeOutput->samples.count <= layerIndex) { // We've already written all samples from this item @@ -1675,6 +1674,9 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) } while (hasMoreSample); } + avifArrayDestroy(&layeredColorItems); + avifArrayDestroy(&layeredAlphaItems); + avifRWStreamFinishBox(&s, mdat); // ----------------------------------------------------------------------- From c9005bc414c7d41d0118f080b95862a8e96acc0c Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 1 Jul 2022 14:08:01 +0800 Subject: [PATCH 05/14] address review changes --- include/avif/avif.h | 5 ++-- src/codec_aom.c | 12 ++++----- src/write.c | 59 ++++++++++++++++++--------------------------- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index f23186307b..0930ed84e8 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -1043,8 +1043,9 @@ typedef struct avifEncoder int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default). uint64_t timescale; // timescale of the media (Hz) - int extraLayerCount; // Extra layers for color sub image; 0 to disable layer image (default). - int extraLayerCountAlpha; // Extra layers for alpha sub image; 0 to disable layer image (default). + // Layers (used by progressive rendering) + int extraLayerCount; // Extra color layers; 0 for regular single-layer color image (default). + int extraLayerCountAlpha; // Extra alpha layers; 0 for regular single-layer alpha image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; diff --git a/src/codec_aom.c b/src/codec_aom.c index 4e29a21515..e3e6e156b1 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -786,8 +786,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, #if defined(AVIF_AOM_LAYER_CONFIG_PREFER_SVC_PARAMS) avifBool useSvcParams = AVIF_TRUE; - for (uint8_t configIndex = 0; configIndex < layerCount; ++configIndex) { - avifLayerConfig * layer = &layers[configIndex]; + for (int configIndex = 0; configIndex < layerCount; ++configIndex) { + const avifLayerConfig * layer = &layers[configIndex]; if (layer->horizontalMode.numerator != layer->verticalMode.numerator || layer->horizontalMode.denominator != layer->verticalMode.denominator) { useSvcParams = AVIF_FALSE; @@ -1076,11 +1076,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) { encodeFlags |= AOM_EFLAG_FORCE_KF; } - if (extraLayerCount > 0) { - if (layerIndex > 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; - } + if ((extraLayerCount > 0) && (layerIndex > 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); diff --git a/src/write.c b/src/write.c index 1296567dfc..addb43d39c 100644 --- a/src/write.c +++ b/src/write.c @@ -630,11 +630,16 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, // ----------------------------------------------------------------------- // Validate images + if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { + return AVIF_RESULT_INVALID_LAYERS; + } + + if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { + return AVIF_RESULT_INVALID_IMAGE_GRID; + } + const uint32_t cellCount = gridCols * gridRows; const uint32_t imageCount = cellCount * layerCount; - if (cellCount == 0) { - return AVIF_RESULT_INVALID_ARGUMENT; - } const avifImage * firstCell = cellImages[0]; if ((firstCell->depth != 8) && (firstCell->depth != 10) && (firstCell->depth != 12)) { @@ -844,7 +849,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, if (item->codec) { item->extraLayerCount = item->alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; for (uint32_t layerIndex = 0; layerIndex < item->extraLayerCount + 1; ++layerIndex) { - const uint32_t index = (layerIndex > (layerCount - 1)) ? (layerCount - 1) : layerIndex; + const uint32_t index = AVIF_MIN(layerIndex, layerCount - 1); const avifImage * layerImage = cellImages[item->cellIndex * layerCount + index]; avifResult encodeResult = item->codec->encodeImage(item->codec, encoder, layerImage, item->alpha, layerIndex, addImageFlags, item->encodeOutput); @@ -876,9 +881,6 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { - return AVIF_RESULT_INVALID_IMAGE_GRID; - } return avifEncoderAddImageInternal(encoder, gridCols, gridRows, 1, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported } @@ -889,10 +891,7 @@ avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { - return AVIF_RESULT_INVALID_LAYERS; - } - return avifEncoderAddImageInternal(encoder, 1, 1, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported + return avifEncoderAddImageInternal(encoder, 1, 1, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single frame progressive images are supported } avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, @@ -902,13 +901,7 @@ avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, const avifImage * const * layerImages, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { - return AVIF_RESULT_INVALID_LAYERS; - } - if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { - return AVIF_RESULT_INVALID_IMAGE_GRID; - } - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, layerCount, layerImages, 1, addImageFlags); + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); } static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) @@ -1540,10 +1533,11 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1); avifBool useInterleave = (encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0); - for (uint32_t itemPasses = 0; itemPasses < 2; ++itemPasses) { + for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) { // Use multiple passes to pack in the following order: // * Pass 0: metadata (Exif/XMP) - // * Pass 1: item data (AV1 color & alpha) + // * Pass 1: alpha (AV1) + // * Pass 2: all other item data (AV1 color) // // See here for the discussion on alpha coming before color: // https://github.com/AOMediaCodec/libavif/issues/287 @@ -1553,6 +1547,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // and ignoreExif are enabled. // const avifBool metadataPass = (itemPasses == 0); + const avifBool alphaPass = (itemPasses == 1); for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; @@ -1565,11 +1560,15 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // only process metadata (XMP/Exif) payloads when metadataPass is true continue; } + if (alphaPass != item->alpha) { + // only process alpha payloads when alphaPass is true + continue; + } size_t chunkOffset = 0; // Interleave - Pick out and record layered image items, interleave them later. - // Expect layer image item has same number of samples and fixups. + // Layer image items have same number of samples and fixups. if (useInterleave && item->encodeOutput->samples.count > 0 && item->encodeOutput->samples.count == item->mdatFixups.count) { @@ -1620,33 +1619,23 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) } } - // Interleave each sample (layer) of each item. - // - // - for each layer - // - for each grid cell - // - write alpha of this layer of this grid cell - // - write color of this layer of this grid cell - // - // See here for the discussion on alpha coming before color: - // https://github.com/AOMediaCodec/libavif/issues/287 - if (useInterleave) { avifBool hasMoreSample; uint32_t layerIndex = 0; do { hasMoreSample = AVIF_FALSE; - // Assume color and alpha having same number of items (both single image, or both grid of same dimension) - for (uint32_t itemIndex = 0; itemIndex < layeredColorItems.count; ++itemIndex) { + for (uint32_t itemIndex = 0; itemIndex < AVIF_MAX(layeredColorItems.count, layeredAlphaItems.count); ++itemIndex) { for (int samplePass = 0; samplePass < 2; ++samplePass) { + // Alpha coming before color avifEncoderItemReferenceArray * currentItems = (samplePass == 0) ? &layeredAlphaItems : &layeredColorItems; if (itemIndex >= currentItems->count) { - break; + continue; } // TODO: Offer the ability for a user to specify which grid cell should be written first. avifEncoderItem * item = currentItems->ref[itemIndex].item; if (item->encodeOutput->samples.count <= layerIndex) { - // We've already written all samples from this item + // We've already written all samples of this item continue; } else if (item->encodeOutput->samples.count > layerIndex + 1) { hasMoreSample = AVIF_TRUE; From c1383eef17f9c0e189d9befadb09e08ef5765662 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 8 Jul 2022 20:33:06 +0800 Subject: [PATCH 06/14] address review changes --- src/write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/write.c b/src/write.c index addb43d39c..9e09b1c3c7 100644 --- a/src/write.c +++ b/src/write.c @@ -623,7 +623,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } // Currently, layered image can only have one frame. - if (((encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0)) && encoder->data->frames.count > 0) { + if (((encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0)) && (encoder->data->frames.count > 0)) { return AVIF_RESULT_NOT_IMPLEMENTED; } From 4a7690fd9a771eeab449ee43b323df102fc3f835 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 8 Jul 2022 21:08:04 +0800 Subject: [PATCH 07/14] remove `layerCount` parameter --- apps/avifenc.c | 51 ++++++++++++++++++++++++++++++++++----------- include/avif/avif.h | 11 ++++------ src/write.c | 34 +++++++++--------------------- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index 7e83db171a..3aa5341f31 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -28,6 +28,8 @@ } \ arg = argv[++argIndex] +#define AVIF_MAX(a, b) (((a) > (b)) ? (a) : (b)) + typedef struct avifInputFile { const char * filename; @@ -270,8 +272,8 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat struct avifEncoderLayerConfig { - int extraLayerCount; // Image layers for color sub image; 0 to disable layer image (default). - int extraLayerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). + uint32_t extraLayerCount; // Image layers for color sub image; 0 to disable layer image (default). + uint32_t extraLayerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; }; @@ -322,7 +324,7 @@ enum avifProgressiveConfigValueType static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * config, const char * arg) { - int * currLayerCount = &config->extraLayerCount; + uint32_t * currLayerCount = &config->extraLayerCount; avifLayerConfig * currLayers = config->layers; uint8_t currLayer = 0; @@ -518,6 +520,7 @@ static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * confi } finish: + CHECK(config->extraLayerCount == config->extraLayerCountAlpha, "currently only support color and alpha has same count of layers"); return AVIF_TRUE; } @@ -1064,6 +1067,12 @@ int main(int argc, char * argv[]) returnCode = 1; goto cleanup; } + if (gridDimsCount == 0) { + gridDimsCount = 3; + gridDims[0] = 1; + gridDims[1] = 1; + gridDims[2] = 1; + } } else { // Positional argument input.files[input.filesCount].filename = arg; @@ -1305,14 +1314,30 @@ int main(int argc, char * argv[]) if (gridDimsCount > 0) { // Grid image! + uint32_t layerCount = AVIF_MAX(layerConfig.extraLayerCount, layerConfig.extraLayerCountAlpha) + 1; + if (gridDims[2] > layerCount) { + fprintf(stderr, "ERROR: Excess layer provided! (expecting %u, grid providing %u)\n", layerCount, gridDims[2]); + returnCode = 1; + goto cleanup; + } + gridCellCount = gridDims[0] * gridDims[1]; printf("Preparing to encode a %ux%u grid (%u cells)...\n", gridDims[0], gridDims[1], gridCellCount); - gridCellLayerCount = gridCellCount * gridDims[2]; + gridCellLayerCount = gridCellCount * layerCount; gridCells = calloc(gridCellLayerCount, sizeof(avifImage *)); gridCells[0] = image; // take ownership of image uint32_t gridCellLayerIndex = 0; + + // Duplicate image to fill remaining layers + if ((gridDims[2] < layerCount) && (gridCellLayerIndex % layerCount == gridDims[2] - 1)) { + for (uint32_t exIndex = gridDims[2]; exIndex < layerCount; ++exIndex) { + ++gridCellLayerIndex; + gridCells[gridCellLayerIndex] = image; + } + } + avifInputFile * nextFile; while ((nextFile = avifInputGetNextFile(&input)) != NULL) { if (!gridCellLayerIndex) { @@ -1337,6 +1362,14 @@ int main(int argc, char * argv[]) cellImage->alphaPremultiplied = image->alphaPremultiplied; gridCells[gridCellLayerIndex] = cellImage; + // Duplicate last image to fill remaining layers + if ((gridDims[2] < layerCount) && (gridCellLayerIndex % layerCount == gridDims[2] - 1)) { + for (uint32_t exIndex = gridDims[2]; exIndex < layerCount; ++exIndex) { + ++gridCellLayerIndex; + gridCells[gridCellLayerIndex] = cellImage; + } + } + avifAppFileFormat nextInputFormat = avifInputReadImage(&input, cellImage, NULL, NULL); if (nextInputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) { returnCode = 1; @@ -1425,7 +1458,6 @@ int main(int argc, char * argv[]) encoder->maxQuantizerAlpha = maxQuantizerAlpha; encoder->extraLayerCount = layerConfig.extraLayerCount; - encoder->extraLayerCountAlpha = layerConfig.extraLayerCountAlpha; memcpy(encoder->layers, layerConfig.layers, sizeof(encoder->layers)); memcpy(encoder->layersAlpha, layerConfig.layersAlpha, sizeof(encoder->layersAlpha)); @@ -1438,13 +1470,8 @@ int main(int argc, char * argv[]) if (gridDimsCount > 0) { avifResult addImageResult; - if (gridDims[2] > 1) { - addImageResult = - avifEncoderAddImageProgressiveGrid(encoder, gridDims[0], gridDims[1], gridDims[2], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); - } else { - addImageResult = - avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); - } + addImageResult = + avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); if (addImageResult != AVIF_RESULT_OK) { fprintf(stderr, "ERROR: Failed to encode image grid: %s\n", avifResultToString(addImageResult)); returnCode = 1; diff --git a/include/avif/avif.h b/include/avif/avif.h index a6a9a5ac3c..6055182551 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -1081,27 +1081,24 @@ typedef uint32_t avifAddImageFlags; // * avifEncoderAddImage() ... [repeatedly; at least once] // OR // * avifEncoderAddImageGrid() [exactly once, AVIF_ADD_IMAGE_FLAG_SINGLE is assumed] +// OR +// * avifEncoderAddImageProgressive() [exactly once, AVIF_ADD_IMAGE_FLAG_SINGLE is assumed] // * avifEncoderFinish() // * avifEncoderDestroy() // // durationInTimescales is ignored if AVIF_ADD_IMAGE_FLAG_SINGLE is set in addImageFlags. AVIF_API avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags); +// cellImages should have gridCols * gridRows * (max(encoder->extraLayerCount, encoder->extraLayerCountAlpha) + 1) elements. AVIF_API avifResult avifEncoderAddImageGrid(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, const avifImage * const * cellImages, avifAddImageFlags addImageFlags); +// cellImages should have max(encoder->extraLayerCount, encoder->extraLayerCountAlpha) + 1 elements. avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, - uint32_t layerCount, const avifImage * const * layerImages, avifAddImageFlags addImageFlags); -avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, - uint32_t gridCols, - uint32_t gridRows, - uint32_t layerCount, - const avifImage * const * layerImages, - avifAddImageFlags addImageFlags); AVIF_API avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output); // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. These diff --git a/src/write.c b/src/write.c index 9e09b1c3c7..87d68eb46d 100644 --- a/src/write.c +++ b/src/write.c @@ -311,7 +311,6 @@ avifEncoder * avifEncoderCreate(void) encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; encoder->extraLayerCount = 0; - encoder->extraLayerCountAlpha = 0; encoder->tileRowsLog2 = 0; encoder->tileColsLog2 = 0; encoder->speed = AVIF_SPEED_DEFAULT; @@ -610,7 +609,6 @@ static avifResult avifEncoderDataCreateXMPItem(avifEncoderData * data, const avi static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, uint32_t gridCols, uint32_t gridRows, - uint32_t layerCount, const avifImage * const * cellImages, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) @@ -627,13 +625,14 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, return AVIF_RESULT_NOT_IMPLEMENTED; } - // ----------------------------------------------------------------------- - // Validate images - - if ((layerCount == 0) || (layerCount > MAX_AV1_LAYER_COUNT)) { + const uint32_t layerCount = AVIF_MAX(encoder->extraLayerCount, encoder->extraLayerCountAlpha) + 1; + if (layerCount > MAX_AV1_LAYER_COUNT) { return AVIF_RESULT_INVALID_LAYERS; } + // ----------------------------------------------------------------------- + // Validate images + if ((gridCols == 0) || (gridCols > 256) || (gridRows == 0) || (gridRows > 256)) { return AVIF_RESULT_INVALID_IMAGE_GRID; } @@ -849,8 +848,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, if (item->codec) { item->extraLayerCount = item->alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; for (uint32_t layerIndex = 0; layerIndex < item->extraLayerCount + 1; ++layerIndex) { - const uint32_t index = AVIF_MIN(layerIndex, layerCount - 1); - const avifImage * layerImage = cellImages[item->cellIndex * layerCount + index]; + const avifImage * layerImage = cellImages[item->cellIndex * layerCount + layerIndex]; avifResult encodeResult = item->codec->encodeImage(item->codec, encoder, layerImage, item->alpha, layerIndex, addImageFlags, item->encodeOutput); if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) { @@ -871,7 +869,7 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, 1, 1, 1, &image, durationInTimescales, addImageFlags); + return avifEncoderAddImageInternal(encoder, 1, 1, &image, durationInTimescales, addImageFlags); } avifResult avifEncoderAddImageGrid(avifEncoder * encoder, @@ -881,27 +879,15 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, 1, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported } - avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, - uint32_t layerCount, const avifImage * const * layerImages, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, 1, 1, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single frame progressive images are supported -} - -avifResult avifEncoderAddImageProgressiveGrid(avifEncoder * encoder, - uint32_t gridCols, - uint32_t gridRows, - uint32_t layerCount, - const avifImage * const * layerImages, - avifAddImageFlags addImageFlags) { - avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, layerCount, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); + return avifEncoderAddImageInternal(encoder, 1, 1, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single frame progressive images are supported } static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) @@ -1531,7 +1517,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) avifEncoderItemReferenceArray layeredColorItems; avifArrayCreate(&layeredAlphaItems, sizeof(avifEncoderItemReference), 1); avifArrayCreate(&layeredColorItems, sizeof(avifEncoderItemReference), 1); - avifBool useInterleave = (encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0); + avifBool useInterleave = encoder->extraLayerCount > 0; for (uint32_t itemPasses = 0; itemPasses < 3; ++itemPasses) { // Use multiple passes to pack in the following order: From 5bdb27cf906a14473fcf1f87c642d15de58b7a06 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 8 Jul 2022 22:30:40 +0800 Subject: [PATCH 08/14] Add a basic test for progressive encoding. --- tests/CMakeLists.txt | 5 +++ tests/gtest/avifprogressivetest.cc | 56 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 tests/gtest/avifprogressivetest.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a46342aacd..238c8e37dd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -93,6 +93,11 @@ if(AVIF_ENABLE_GTEST) target_link_libraries(avify4mtest aviftest_helpers avif_apps ${GTEST_BOTH_LIBRARIES}) target_include_directories(avify4mtest PRIVATE ${GTEST_INCLUDE_DIRS}) add_test(NAME avify4mtest COMMAND avify4mtest) + + add_executable(avifprogressivetest gtest/avifprogressivetest.cc) + target_link_libraries(avifprogressivetest aviftest_helpers ${GTEST_BOTH_LIBRARIES}) + target_include_directories(avifprogressivetest PRIVATE ${GTEST_INCLUDE_DIRS}) + add_test(NAME avifprogressivetest COMMAND avifprogressivetest) else() message(STATUS "Most tests are disabled because AVIF_ENABLE_GTEST is OFF.") endif() diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc new file mode 100644 index 0000000000..b3f2378234 --- /dev/null +++ b/tests/gtest/avifprogressivetest.cc @@ -0,0 +1,56 @@ +// Copyright 2022 Google LLC. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +#include + +#include "avif/avif.h" +#include "aviftest_helpers.h" +#include "gtest/gtest.h" + +using ::testing::Combine; +using ::testing::Values; +using ::testing::ValuesIn; + +namespace libavif { +namespace { + +class ProgressiveTest + : public testing::Test {}; +} + +TEST(ProgressiveTest, EncodeDecode) { + if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) { + GTEST_SKIP_("ProgressiveTest requires AOM encoder."); + } + + testutil::AvifImagePtr image = testutil::CreateImage(512, 512, 8, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_YUV, AVIF_RANGE_FULL); + ASSERT_NE(image, nullptr); + testutil::FillImageGradient(image.get()); + + // Encode + testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy); + ASSERT_NE(encoder, nullptr); + encoder->codecChoice = AVIF_CODEC_CHOICE_AOM; + encoder->speed = AVIF_SPEED_FASTEST; + encoder->extraLayerCount = 1; + encoder->layers[0] = {50, 50, {1, 4}, {1, 4}}; + encoder->layers[1] = {25, 25, {1, 1}, {1, 1}}; + std::array layer_image_ptrs = {image.get(), image.get()}; + ASSERT_EQ(avifEncoderAddImageProgressive(encoder.get(), layer_image_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE), AVIF_RESULT_OK); + testutil::AvifRwData encodedAvif; + ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK); + + // Decode + ASSERT_NE(image, nullptr); + testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy); + ASSERT_NE(decoder, nullptr); + decoder->allowProgressive = true; + ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encodedAvif.data, encodedAvif.size), AVIF_RESULT_OK); + ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); + ASSERT_EQ(decoder->progressiveState, AVIF_PROGRESSIVE_STATE_ACTIVE); + ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); + // Check decoder->image + ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); + // Check decoder->image +} +} \ No newline at end of file From cf05850c44258a8ea1724dbb2377015f8c5791b3 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Fri, 8 Jul 2022 22:31:35 +0800 Subject: [PATCH 09/14] Revert changes to avifenc --- apps/avifenc.c | 413 ++++--------------------------------------------- 1 file changed, 26 insertions(+), 387 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index 3aa5341f31..6f3b3e8c00 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -9,7 +9,6 @@ #include "y4m.h" #include -#include #include #include #include @@ -28,8 +27,6 @@ } \ arg = argv[++argIndex] -#define AVIF_MAX(a, b) (((a) > (b)) ? (a) : (b)) - typedef struct avifInputFile { const char * filename; @@ -86,9 +83,9 @@ static void syntax(void) AVIF_QUANTIZER_LOSSLESS); printf(" --tilerowslog2 R : Set log2 of number of tile rows (0-6, default: 0)\n"); printf(" --tilecolslog2 C : Set log2 of number of tile columns (0-6, default: 0)\n"); - printf(" -g,--grid MxN(xL) : Encode a single-image grid AVIF with M cols & N rows & L layers. Either supply MxNxL identical W/H/D images,\n"); - printf(" or L identical W/H/D images that each can be evenly split into the MxN grid and follow AVIF grid image restrictions.\n"); - printf(" The grid will adopt the color profile of the first image supplied.\n"); + printf(" -g,--grid MxN : Encode a single-image grid AVIF with M cols & N rows. Either supply MxN identical W/H/D images, or a single\n"); + printf(" image that can be evenly split into the MxN grid and follow AVIF grid image restrictions. The grid will adopt\n"); + printf(" the color profile of the first image supplied.\n"); printf(" -s,--speed S : Encoder speed (%d-%d, slowest-fastest, 'default' or 'd' for codec internal defaults. default speed: 6)\n", AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST); @@ -107,35 +104,6 @@ static void syntax(void) printf(" --clap WN,WD,HN,HD,HON,HOD,VON,VOD: Add clap property (clean aperture). Width, Height, HOffset, VOffset (in num/denom pairs)\n"); printf(" --irot ANGLE : Add irot property (rotation). [0-3], makes (90 * ANGLE) degree rotation anti-clockwise\n"); printf(" --imir MODE : Add imir property (mirroring). 0=top-to-bottom, 1=left-to-right\n"); - printf(" --progressive LAYER_CONFIG : Encode progressive AVIF with given layer config\n"); - printf("\n"); - printf("progressive layer config format:\n"); - printf(" LAYER_CONFIG can be one of the two forms:\n"); - printf(" 1. SUB_CONFIG apply SUB_CONFIG to both color (YUV) planes and alpha plane\n"); - printf(" 2. SUB_CONFIG;SUB_CONFIG apply first SUB_CONFIG to color planes, second SUB_CONFIG to alpha plane\n"); - printf("\n"); - printf(" SUB_CONFIG is 0-4 LAYER_CONFIG joined by colon(:), and LAYER_CONFIG is in this form:\n"); - printf(" MinQ[,MaxQ][-ScaleH[,ScaleV]]\n"); - printf("\n"); - printf(" MinQ and MaxQ are min and max quantizers for this layer, and will overwrite values given by --min and --max.\n"); - printf(" Specially, when using aom with end-usage set to q or cq, min and max quantizers will use values given by --min and --max,\n"); - printf(" and cq-level of this layer will set to average of MinQ and MaxQ.\n"); - printf(" ScaleH and ScaleV are horizontal and vertical scale ratios [default=1, or any fraction].\n"); - printf(" If MaxQ is eliminated it uses the value of MinQ.\n"); - printf(" If ScaleH is eliminated it uses default value 1 (no scaling); if ScaleV is eliminated it uses the value of ScaleH.\n"); - printf("\n"); - printf(" Examples:\n"); - printf(" 40,62-1/2,1/4:30-1/2:10\n"); - printf(" Color and alpha planes both have 3 layers and share the same following config:\n"); - printf(" #0: min quantizer 40, max quantizer 62, 1/2 width, 1/4 height\n"); - printf(" #1: min quantizer 30, max quantizer 30, 1/2 width, 1/2 height\n"); - printf(" #2: min quantizer 10, max quantizer 10, full width, full height\n"); - printf("\n"); - printf(" 30,1/2:10;\n"); - printf(" Color planes have 2 layers, alpha plane is not layered.\n"); - printf("\n"); - printf(" ;30,1/2:10\n"); - printf(" Color planes is not layered, alpha plane have 2 layers.\n"); printf("\n"); if (avifCodecName(AVIF_CODEC_CHOICE_AOM, 0)) { printf("aom-specific advanced options:\n"); @@ -262,268 +230,6 @@ static avifBool convertCropToClap(uint32_t srcW, uint32_t srcH, avifPixelFormat return AVIF_TRUE; } -#define CHECK(condition, reason) \ - do { \ - if (!(condition)) { \ - fprintf(stderr, "ERROR: Failed reading progressive config: %s\n", reason); \ - return AVIF_FALSE; \ - } \ - } while (0) - -struct avifEncoderLayerConfig -{ - uint32_t extraLayerCount; // Image layers for color sub image; 0 to disable layer image (default). - uint32_t extraLayerCountAlpha; // Image layers for alpha sub image; 0 to disable layer image (default). - avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; - avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; -}; - -static const avifScalingMode avifScalingModeNormal = { 1, 1 }; - -static avifBool avifParseScalingMode(const char ** pArg, avifScalingMode * mode) -{ - char * end; - uint64_t value = strtoull(*pArg, &end, 10); - CHECK(errno != ERANGE, "overflowed while reading scale nominator"); - CHECK(end != *pArg, "can't parse scale nominator"); - if (*end != '/') { - if (value == 1) { - *mode = avifScalingModeNormal; - *pArg = end; - return AVIF_TRUE; - } - return AVIF_FALSE; - } - mode->numerator = value; - *pArg = end + 1; - value = strtoull(*pArg, &end, 10); - CHECK(errno != ERANGE, "overflowed while reading scale denominator"); - CHECK(end != *pArg, "can't parse scale denominator"); - mode->denominator = value; - *pArg = end; - return AVIF_TRUE; -} - -enum avifProgressiveConfigScannerState -{ - AVIF_PROGRESSIVE_SCANNER_STATE_VALUE, - AVIF_PROGRESSIVE_SCANNER_STATE_COMMA, - AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN, - AVIF_PROGRESSIVE_SCANNER_STATE_COLON, - AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON -}; - -enum avifProgressiveConfigValueType -{ - AVIF_PROGRESSIVE_VALUE_TYPE_NONE, - AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q, - AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q, - AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE, - AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE, -}; - -static avifBool avifParseProgressiveConfig(struct avifEncoderLayerConfig * config, const char * arg) -{ - uint32_t * currLayerCount = &config->extraLayerCount; - avifLayerConfig * currLayers = config->layers; - uint8_t currLayer = 0; - - enum avifProgressiveConfigScannerState currState = *arg == ';' ? AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON - : AVIF_PROGRESSIVE_SCANNER_STATE_VALUE; - enum avifProgressiveConfigScannerState targetState = AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON; - - enum avifProgressiveConfigValueType prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; - - for (;;) { - switch (currState) { - case AVIF_PROGRESSIVE_SCANNER_STATE_VALUE: { - int64_t value; - avifScalingMode mode; - char * end; - switch (prevReadValue) { - case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: - value = strtoll(arg, &end, 10); - CHECK(errno != ERANGE, "overflowed while reading min quantizer"); - CHECK(end != arg, "can't parse min quantizer"); - CHECK(value <= 63, "min quantizer too big"); - - arg = end; - currLayers[currLayer].minQuantizer = (int)value; - currState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: - value = strtoll(arg, &end, 10); - CHECK(errno != ERANGE, "overflowed while reading max quantizer"); - CHECK(end != arg, "can't parse max quantizer"); - CHECK(value <= 63, "max quantizer too big"); - - arg = end; - currLayers[currLayer].maxQuantizer = (int)value; - currState = AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN; - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: - CHECK(avifParseScalingMode(&arg, &mode), "unknown scaling mode"); - - currLayers[currLayer].horizontalMode = mode; - currState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: - CHECK(avifParseScalingMode(&arg, &mode), "unknown scaling mode"); - - currLayers[currLayer].verticalMode = mode; - currState = AVIF_PROGRESSIVE_SCANNER_STATE_COLON; - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: - CHECK(AVIF_FALSE, "too many values in layer config"); - } - break; - } - - case AVIF_PROGRESSIVE_SCANNER_STATE_COMMA: - case AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN: - case AVIF_PROGRESSIVE_SCANNER_STATE_COLON: - case AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON: - switch (*arg) { - case ',': - targetState = AVIF_PROGRESSIVE_SCANNER_STATE_COMMA; - break; - case '-': - targetState = AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN; - break; - case ':': - targetState = AVIF_PROGRESSIVE_SCANNER_STATE_COLON; - break; - case ';': - case '\0': - targetState = AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON; - break; - default: - CHECK(AVIF_FALSE, "unexpected separator"); - } - - CHECK(currState <= targetState, "too many config entries"); - - avifBool earlyEnd = currState < targetState; - switch (targetState) { - case AVIF_PROGRESSIVE_SCANNER_STATE_VALUE: - CHECK(AVIF_FALSE, "unknown state"); - break; - - case AVIF_PROGRESSIVE_SCANNER_STATE_COMMA: - CHECK(!earlyEnd, "unknown state"); - break; - - case AVIF_PROGRESSIVE_SCANNER_STATE_HYPHEN: - if (!earlyEnd) { - CHECK(prevReadValue == AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q, "unknown state"); - break; - } - - CHECK(prevReadValue == AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q, "unknown state"); - currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q; - break; - - case AVIF_PROGRESSIVE_SCANNER_STATE_COLON: - if (earlyEnd) { - switch (prevReadValue) { - case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: - currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; - currLayers[currLayer].horizontalMode = avifScalingModeNormal; - currLayers[currLayer].verticalMode = avifScalingModeNormal; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: - currLayers[currLayer].horizontalMode = avifScalingModeNormal; - currLayers[currLayer].verticalMode = avifScalingModeNormal; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: - currLayers[currLayer].verticalMode = currLayers[currLayer].horizontalMode; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: - case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: - CHECK(AVIF_FALSE, "unknown state"); - } - } - - ++currLayer; - CHECK(currLayer < MAX_AV1_LAYER_COUNT, "too many layers"); - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; - break; - - case AVIF_PROGRESSIVE_SCANNER_STATE_SEMICOLON: - if (earlyEnd) { - switch (prevReadValue) { - case AVIF_PROGRESSIVE_VALUE_TYPE_MIN_Q: - currLayers[currLayer].maxQuantizer = currLayers[currLayer].minQuantizer; - currLayers[currLayer].horizontalMode = avifScalingModeNormal; - currLayers[currLayer].verticalMode = avifScalingModeNormal; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_MAX_Q: - currLayers[currLayer].horizontalMode = avifScalingModeNormal; - currLayers[currLayer].verticalMode = avifScalingModeNormal; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_H_SCALE: - currLayers[currLayer].verticalMode = currLayers[currLayer].horizontalMode; - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_V_SCALE: - break; - - case AVIF_PROGRESSIVE_VALUE_TYPE_NONE: - CHECK(AVIF_FALSE, "unknown state"); - } - - ++currLayer; - } - - *currLayerCount = currLayer - 1; - if (*arg == ';') { - CHECK(currLayers == config->layers, "too many sub image configurations"); - currLayers = config->layersAlpha; - currLayerCount = &config->extraLayerCountAlpha; - - if (*(arg + 1) == '\0') { - goto finish; - } - - prevReadValue = AVIF_PROGRESSIVE_VALUE_TYPE_NONE; - currLayer = 0; - } else { - // reached \0 - if (currLayers == config->layers) { - memcpy(config->layersAlpha, config->layers, sizeof(config->layers)); - config->extraLayerCountAlpha = config->extraLayerCount; - } - - goto finish; - } - break; - } - - ++arg; - currState = AVIF_PROGRESSIVE_SCANNER_STATE_VALUE; - break; - } - } - -finish: - CHECK(config->extraLayerCount == config->extraLayerCountAlpha, "currently only support color and alpha has same count of layers"); - return AVIF_TRUE; -} - static avifInputFile * avifInputGetNextFile(avifInput * input) { if (input->useStdin) { @@ -631,7 +337,7 @@ static avifBool readEntireFile(const char * filename, avifRWData * raw) return AVIF_TRUE; } -static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, uint32_t layerCount, avifImage ** gridCells) +static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols, uint32_t gridRows, avifImage ** gridCells) { if ((gridSplitImage->width % gridCols) != 0) { fprintf(stderr, "ERROR: Can't split image width (%u) evenly into %u columns.\n", gridSplitImage->width, gridCols); @@ -657,7 +363,7 @@ static avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gr for (uint32_t gridX = 0; gridX < gridCols; ++gridX) { uint32_t gridIndex = gridX + (gridY * gridCols); avifImage * cellImage = avifImageCreateEmpty(); - gridCells[gridIndex * layerCount] = cellImage; + gridCells[gridIndex] = cellImage; avifImageCopy(cellImage, gridSplitImage, 0); cellImage->width = cellWidth; @@ -749,9 +455,8 @@ int main(int argc, char * argv[]) avifBool cicpExplicitlySet = AVIF_FALSE; avifBool premultiplyAlpha = AVIF_FALSE; int gridDimsCount = 0; - uint32_t gridDims[8]; // only the first two or three are used + uint32_t gridDims[8]; // only the first two are used uint32_t gridCellCount = 0; - uint32_t gridCellLayerCount = 0; avifImage ** gridCells = NULL; avifImage * gridSplitImage = NULL; // used for cleanup tracking memset(gridDims, 0, sizeof(gridDims)); @@ -770,9 +475,6 @@ int main(int argc, char * argv[]) avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; - struct avifEncoderLayerConfig layerConfig; - memset(&layerConfig, 0, sizeof(layerConfig)); - int argIndex = 1; while (argIndex < argc) { const char * arg = argv[argIndex]; @@ -881,9 +583,7 @@ int main(int argc, char * argv[]) } else if (!strcmp(arg, "-g") || !strcmp(arg, "--grid")) { NEXTARG(); gridDimsCount = parseU32List(gridDims, arg); - if (gridDimsCount == 2) { - gridDims[2] = 1; - } else if (gridDimsCount != 3) { + if (gridDimsCount != 2) { fprintf(stderr, "ERROR: Invalid grid dims: %s\n", arg); returnCode = 1; goto cleanup; @@ -893,11 +593,6 @@ int main(int argc, char * argv[]) returnCode = 1; goto cleanup; } - if ((gridDims[2] == 0 || gridDims[2] > MAX_AV1_LAYER_COUNT)) { - fprintf(stderr, "ERROR: Invalid layer count (valid layer range [1-4]): %s\n", arg); - returnCode = 1; - goto cleanup; - } } else if (!strcmp(arg, "--cicp") || !strcmp(arg, "--nclx")) { NEXTARG(); int cicp[3]; @@ -1054,25 +749,13 @@ int main(int argc, char * argv[]) minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; // lossless maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; // lossless codecChoice = AVIF_CODEC_CHOICE_AOM; // rav1e doesn't support lossless transform yet: - // https://github.com/xiph/rav1e/issues/151 - // SVT-AV1 doesn't support lossless encoding yet: - // https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/1636 + // https://github.com/xiph/rav1e/issues/151 + // SVT-AV1 doesn't support lossless encoding yet: + // https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/1636 requestedRange = AVIF_RANGE_FULL; // avoid limited range matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; // this is key for lossless } else if (!strcmp(arg, "-p") || !strcmp(arg, "--premultiply")) { premultiplyAlpha = AVIF_TRUE; - } else if (!strcmp(arg, "--progressive")) { - NEXTARG(); - if (!avifParseProgressiveConfig(&layerConfig, arg)) { - returnCode = 1; - goto cleanup; - } - if (gridDimsCount == 0) { - gridDimsCount = 3; - gridDims[0] = 1; - gridDims[1] = 1; - gridDims[2] = 1; - } } else { // Positional argument input.files[input.filesCount].filename = arg; @@ -1128,12 +811,6 @@ int main(int argc, char * argv[]) } } - if (gridDimsCount == 0 && input.filesCount > 1 && (layerConfig.extraLayerCount > 0 || layerConfig.extraLayerCountAlpha > 0)) { - fprintf(stderr, "Progressive animated AVIF currently not supported.\n"); - returnCode = 1; - goto cleanup; - } - avifInputFile * firstFile = avifInputGetNextFile(&input); uint32_t sourceDepth = 0; avifAppSourceTiming firstSourceTiming; @@ -1314,37 +991,20 @@ int main(int argc, char * argv[]) if (gridDimsCount > 0) { // Grid image! - uint32_t layerCount = AVIF_MAX(layerConfig.extraLayerCount, layerConfig.extraLayerCountAlpha) + 1; - if (gridDims[2] > layerCount) { - fprintf(stderr, "ERROR: Excess layer provided! (expecting %u, grid providing %u)\n", layerCount, gridDims[2]); - returnCode = 1; - goto cleanup; - } - gridCellCount = gridDims[0] * gridDims[1]; printf("Preparing to encode a %ux%u grid (%u cells)...\n", gridDims[0], gridDims[1], gridCellCount); - gridCellLayerCount = gridCellCount * layerCount; - gridCells = calloc(gridCellLayerCount, sizeof(avifImage *)); + gridCells = calloc(gridCellCount, sizeof(avifImage *)); gridCells[0] = image; // take ownership of image - uint32_t gridCellLayerIndex = 0; - - // Duplicate image to fill remaining layers - if ((gridDims[2] < layerCount) && (gridCellLayerIndex % layerCount == gridDims[2] - 1)) { - for (uint32_t exIndex = gridDims[2]; exIndex < layerCount; ++exIndex) { - ++gridCellLayerIndex; - gridCells[gridCellLayerIndex] = image; - } - } - + uint32_t gridCellIndex = 0; avifInputFile * nextFile; while ((nextFile = avifInputGetNextFile(&input)) != NULL) { - if (!gridCellLayerIndex) { + if (!gridCellIndex) { printf("Loading additional cells for grid image (%u cells)...\n", gridCellCount); } - ++gridCellLayerIndex; - if (gridCellLayerIndex >= gridCellLayerCount) { + ++gridCellIndex; + if (gridCellIndex >= gridCellCount) { // We have enough, warn and continue fprintf(stderr, "WARNING: [--grid] More than %u images were supplied for this %ux%u grid. The rest will be ignored.\n", @@ -1360,15 +1020,7 @@ int main(int argc, char * argv[]) cellImage->matrixCoefficients = image->matrixCoefficients; cellImage->yuvRange = image->yuvRange; cellImage->alphaPremultiplied = image->alphaPremultiplied; - gridCells[gridCellLayerIndex] = cellImage; - - // Duplicate last image to fill remaining layers - if ((gridDims[2] < layerCount) && (gridCellLayerIndex % layerCount == gridDims[2] - 1)) { - for (uint32_t exIndex = gridDims[2]; exIndex < layerCount; ++exIndex) { - ++gridCellLayerIndex; - gridCells[gridCellLayerIndex] = cellImage; - } - } + gridCells[gridCellIndex] = cellImage; avifAppFileFormat nextInputFormat = avifInputReadImage(&input, cellImage, NULL, NULL); if (nextInputFormat == AVIF_APP_FILE_FORMAT_UNKNOWN) { @@ -1404,22 +1056,19 @@ int main(int argc, char * argv[]) } } - if (gridCellLayerIndex == gridDims[2] - 1 && (gridDims[0] != 1 || gridDims[1] != 1)) { + if (gridCellIndex == 0) { printf("Single image input for a grid image. Attempting to split into %u cells...\n", gridCellCount); + gridSplitImage = image; + gridCells[0] = NULL; - for (uint32_t layerIndex = 0; layerIndex < gridDims[2]; ++layerIndex) { - gridSplitImage = gridCells[layerIndex]; - gridCells[layerIndex] = NULL; - if (!avifImageSplitGrid(gridSplitImage, gridDims[0], gridDims[1], gridDims[2], gridCells + layerIndex)) { - returnCode = 1; - goto cleanup; - } + if (!avifImageSplitGrid(gridSplitImage, gridDims[0], gridDims[1], gridCells)) { + returnCode = 1; + goto cleanup; } - - gridCellLayerIndex = gridCellLayerCount - 1; + gridCellIndex = gridCellCount - 1; } - if (gridCellLayerIndex != gridCellLayerCount - 1) { + if (gridCellIndex != gridCellCount - 1) { fprintf(stderr, "ERROR: Not enough input files for grid image! (expecting %u, or a single image to be split)\n", gridCellCount); returnCode = 1; goto cleanup; @@ -1431,11 +1080,7 @@ int main(int argc, char * argv[]) lossyHint = " (Lossless)"; } printf("AVIF to be written:%s\n", lossyHint); - avifBool progressive = layerConfig.extraLayerCount > 0 || layerConfig.extraLayerCountAlpha > 0; - avifImageDump(gridCells ? gridCells[0] : image, - gridDims[0], - gridDims[1], - progressive ? AVIF_PROGRESSIVE_STATE_AVAILABLE : AVIF_PROGRESSIVE_STATE_UNAVAILABLE); + avifImageDump(gridCells ? gridCells[0] : image, gridDims[0], gridDims[1], AVIF_PROGRESSIVE_STATE_UNAVAILABLE); printf("Encoding with AV1 codec '%s' speed [%d], color QP [%d (%s) <-> %d (%s)], alpha QP [%d (%s) <-> %d (%s)], tileRowsLog2 [%d], tileColsLog2 [%d], %d worker thread(s), please wait...\n", avifCodecName(codecChoice, AVIF_CODEC_FLAG_CAN_ENCODE), @@ -1456,11 +1101,6 @@ int main(int argc, char * argv[]) encoder->maxQuantizer = maxQuantizer; encoder->minQuantizerAlpha = minQuantizerAlpha; encoder->maxQuantizerAlpha = maxQuantizerAlpha; - - encoder->extraLayerCount = layerConfig.extraLayerCount; - memcpy(encoder->layers, layerConfig.layers, sizeof(encoder->layers)); - memcpy(encoder->layersAlpha, layerConfig.layersAlpha, sizeof(encoder->layersAlpha)); - encoder->tileRowsLog2 = tileRowsLog2; encoder->tileColsLog2 = tileColsLog2; encoder->codecChoice = codecChoice; @@ -1469,8 +1109,7 @@ int main(int argc, char * argv[]) encoder->keyframeInterval = keyframeInterval; if (gridDimsCount > 0) { - avifResult addImageResult; - addImageResult = + avifResult addImageResult = avifEncoderAddImageGrid(encoder, gridDims[0], gridDims[1], (const avifImage * const *)gridCells, AVIF_ADD_IMAGE_FLAG_SINGLE); if (addImageResult != AVIF_RESULT_OK) { fprintf(stderr, "ERROR: Failed to encode image grid: %s\n", avifResultToString(addImageResult)); From 953aee411aa67b4fe56bbf20f98f5ee97624e39c Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sat, 9 Jul 2022 09:47:59 +0800 Subject: [PATCH 10/14] apply clang-format --- apps/avifenc.c | 6 +++--- include/avif/avif.h | 12 ++++++------ src/write.c | 8 ++------ tests/gtest/avifprogressivetest.cc | 22 ++++++++++++++-------- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/avifenc.c b/apps/avifenc.c index 6f3b3e8c00..7529e896f9 100644 --- a/apps/avifenc.c +++ b/apps/avifenc.c @@ -749,9 +749,9 @@ int main(int argc, char * argv[]) minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; // lossless maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; // lossless codecChoice = AVIF_CODEC_CHOICE_AOM; // rav1e doesn't support lossless transform yet: - // https://github.com/xiph/rav1e/issues/151 - // SVT-AV1 doesn't support lossless encoding yet: - // https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/1636 + // https://github.com/xiph/rav1e/issues/151 + // SVT-AV1 doesn't support lossless encoding yet: + // https://gitlab.com/AOMediaCodec/SVT-AV1/-/issues/1636 requestedRange = AVIF_RANGE_FULL; // avoid limited range matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; // this is key for lossless } else if (!strcmp(arg, "-p") || !strcmp(arg, "--premultiply")) { diff --git a/include/avif/avif.h b/include/avif/avif.h index 6055182551..cc10a34f7a 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -998,12 +998,14 @@ AVIF_API avifResult avifDecoderNthImageMaxExtent(const avifDecoder * decoder, ui struct avifEncoderData; struct avifCodecSpecificOptions; -typedef struct avifScalingMode { +typedef struct avifScalingMode +{ uint64_t numerator; uint64_t denominator; } avifScalingMode; -typedef struct avifLayerConfig { +typedef struct avifLayerConfig +{ int minQuantizer; int maxQuantizer; avifScalingMode horizontalMode; @@ -1039,7 +1041,7 @@ typedef struct avifEncoder uint64_t timescale; // timescale of the media (Hz) // Layers (used by progressive rendering) - int extraLayerCount; // Extra color layers; 0 for regular single-layer color image (default). + int extraLayerCount; // Extra color layers; 0 for regular single-layer color image (default). int extraLayerCountAlpha; // Extra alpha layers; 0 for regular single-layer alpha image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; avifLayerConfig layersAlpha[MAX_AV1_LAYER_COUNT]; @@ -1096,9 +1098,7 @@ AVIF_API avifResult avifEncoderAddImageGrid(avifEncoder * encoder, const avifImage * const * cellImages, avifAddImageFlags addImageFlags); // cellImages should have max(encoder->extraLayerCount, encoder->extraLayerCountAlpha) + 1 elements. -avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, - const avifImage * const * layerImages, - avifAddImageFlags addImageFlags); +avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, const avifImage * const * layerImages, avifAddImageFlags addImageFlags); AVIF_API avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output); // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. These diff --git a/src/write.c b/src/write.c index 87d68eb46d..c4da243fd4 100644 --- a/src/write.c +++ b/src/write.c @@ -882,9 +882,7 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported } -avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, - const avifImage * const * layerImages, - avifAddImageFlags addImageFlags) +avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, const avifImage * const * layerImages, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); return avifEncoderAddImageInternal(encoder, 1, 1, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single frame progressive images are supported @@ -1555,9 +1553,7 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // Interleave - Pick out and record layered image items, interleave them later. // Layer image items have same number of samples and fixups. - if (useInterleave && item->encodeOutput->samples.count > 0 && - item->encodeOutput->samples.count == item->mdatFixups.count) { - + if (useInterleave && item->encodeOutput->samples.count > 0 && item->encodeOutput->samples.count == item->mdatFixups.count) { avifEncoderItemReference * ref; if (item->alpha) { ref = (avifEncoderItemReference *)avifArrayPushPtr(&layeredAlphaItems); diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc index b3f2378234..cb610a0688 100644 --- a/tests/gtest/avifprogressivetest.cc +++ b/tests/gtest/avifprogressivetest.cc @@ -14,16 +14,17 @@ using ::testing::ValuesIn; namespace libavif { namespace { -class ProgressiveTest - : public testing::Test {}; -} +class ProgressiveTest : public testing::Test {}; +} // namespace TEST(ProgressiveTest, EncodeDecode) { - if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) { + if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == + nullptr) { GTEST_SKIP_("ProgressiveTest requires AOM encoder."); } - testutil::AvifImagePtr image = testutil::CreateImage(512, 512, 8, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_YUV, AVIF_RANGE_FULL); + testutil::AvifImagePtr image = testutil::CreateImage( + 512, 512, 8, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_YUV, AVIF_RANGE_FULL); ASSERT_NE(image, nullptr); testutil::FillImageGradient(image.get()); @@ -36,7 +37,10 @@ TEST(ProgressiveTest, EncodeDecode) { encoder->layers[0] = {50, 50, {1, 4}, {1, 4}}; encoder->layers[1] = {25, 25, {1, 1}, {1, 1}}; std::array layer_image_ptrs = {image.get(), image.get()}; - ASSERT_EQ(avifEncoderAddImageProgressive(encoder.get(), layer_image_ptrs.data(), AVIF_ADD_IMAGE_FLAG_SINGLE), AVIF_RESULT_OK); + ASSERT_EQ( + avifEncoderAddImageProgressive(encoder.get(), layer_image_ptrs.data(), + AVIF_ADD_IMAGE_FLAG_SINGLE), + AVIF_RESULT_OK); testutil::AvifRwData encodedAvif; ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK); @@ -45,7 +49,9 @@ TEST(ProgressiveTest, EncodeDecode) { testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy); ASSERT_NE(decoder, nullptr); decoder->allowProgressive = true; - ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encodedAvif.data, encodedAvif.size), AVIF_RESULT_OK); + ASSERT_EQ( + avifDecoderSetIOMemory(decoder.get(), encodedAvif.data, encodedAvif.size), + AVIF_RESULT_OK); ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); ASSERT_EQ(decoder->progressiveState, AVIF_PROGRESSIVE_STATE_ACTIVE); ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); @@ -53,4 +59,4 @@ TEST(ProgressiveTest, EncodeDecode) { ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); // Check decoder->image } -} \ No newline at end of file +} // namespace libavif \ No newline at end of file From 1f41e173c138cf8eab1cce02511da4ac2bc1c5aa Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sat, 16 Jul 2022 17:14:43 +0800 Subject: [PATCH 11/14] address review changes --- include/avif/avif.h | 2 +- src/codec_aom.c | 96 +++++------------------------- src/write.c | 3 +- tests/gtest/avifprogressivetest.cc | 36 ++++++----- 4 files changed, 34 insertions(+), 103 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index cc10a34f7a..b89772afda 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -1097,7 +1097,7 @@ AVIF_API avifResult avifEncoderAddImageGrid(avifEncoder * encoder, uint32_t gridRows, const avifImage * const * cellImages, avifAddImageFlags addImageFlags); -// cellImages should have max(encoder->extraLayerCount, encoder->extraLayerCountAlpha) + 1 elements. +// layerImages should have max(encoder->extraLayerCount, encoder->extraLayerCountAlpha) + 1 elements. avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, const avifImage * const * layerImages, avifAddImageFlags addImageFlags); AVIF_API avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output); diff --git a/src/codec_aom.c b/src/codec_aom.c index e3e6e156b1..4351260ac7 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -48,15 +48,6 @@ #endif #endif -typedef enum avifAOMScaleConfigMethod -{ - // Set using AV1E_SET_SVC_PARAMS - AVIF_AOM_SCALE_SVC_PARAMS, - - // Set using AOME_SET_SCALEMODE - AVIF_AOM_SCALE_SCALEMODE -} avifAOMScaleConfigMethod; - struct avifCodecInternal { #if defined(AVIF_CODEC_AOM_DECODE) @@ -82,7 +73,6 @@ struct avifCodecInternal // Whether 'tuning' (of the specified distortion metric) was set with an // avifEncoderSetCodecSpecificOption(encoder, "tune", value) call. avifBool tuningSet; - avifAOMScaleConfigMethod scaleConfigMethod; #endif }; @@ -780,56 +770,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, return AVIF_RESULT_UNKNOWN_ERROR; } } - if (extraLayerCount > 0) { - int layerCount = extraLayerCount + 1; - -#if defined(AVIF_AOM_LAYER_CONFIG_PREFER_SVC_PARAMS) - avifBool useSvcParams = AVIF_TRUE; - for (int configIndex = 0; configIndex < layerCount; ++configIndex) { - const avifLayerConfig * layer = &layers[configIndex]; - if (layer->horizontalMode.numerator != layer->verticalMode.numerator || - layer->horizontalMode.denominator != layer->verticalMode.denominator) { - useSvcParams = AVIF_FALSE; - break; - } - } -#else - avifBool useSvcParams = AVIF_FALSE; - for (int configIndex = 0; configIndex < layerCount; ++configIndex) { - const avifLayerConfig * layer = &layers[configIndex]; - AOM_SCALING_MODE mode; - if (layer->horizontalMode.numerator == layer->verticalMode.numerator && - layer->horizontalMode.denominator == layer->verticalMode.denominator && - !avifFindAOMScalingMode(&layer->horizontalMode, &mode)) { - useSvcParams = AVIF_TRUE; - break; - } - } -#endif - - codec->internal->scaleConfigMethod = useSvcParams ? AVIF_AOM_SCALE_SVC_PARAMS : AVIF_AOM_SCALE_SCALEMODE; - if (useSvcParams) { - aom_svc_params_t svcParams; - memset(&svcParams, 0, sizeof(aom_svc_params_t)); - svcParams.number_temporal_layers = 1; - svcParams.number_spatial_layers = layerCount; - svcParams.framerate_factor[0] = 1; - for (int configIndex = 0; configIndex < layerCount; ++configIndex) { - const avifLayerConfig * layer = &layers[configIndex]; - svcParams.min_quantizers[configIndex] = layer->minQuantizer; - svcParams.max_quantizers[configIndex] = layer->maxQuantizer; - svcParams.scaling_factor_num[configIndex] = (int)layer->horizontalMode.numerator; - svcParams.scaling_factor_den[configIndex] = (int)layer->horizontalMode.denominator; - } - - if (aom_codec_control(&codec->internal->encoder, AV1E_SET_SVC_PARAMS, &svcParams) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } - } else { - if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, layerCount) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } + if (aom_codec_control(&codec->internal->encoder, AOME_SET_NUMBER_SPATIAL_LAYERS, extraLayerCount + 1) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; } } @@ -1005,32 +948,21 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } if (extraLayerCount > 0) { - switch (codec->internal->scaleConfigMethod) { - case AVIF_AOM_SCALE_SVC_PARAMS: { - aom_svc_layer_id_t layerId = { layerIndex, 0 }; - if (aom_codec_control(&codec->internal->encoder, AV1E_SET_SVC_LAYER_ID, &layerId) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } - } break; - - case AVIF_AOM_SCALE_SCALEMODE: { - aom_scaling_mode_t scaling_mode; - if (!avifFindAOMScalingMode(&layers[layerIndex].horizontalMode, &scaling_mode.h_scaling_mode)) { - return AVIF_RESULT_NOT_IMPLEMENTED; - } + aom_scaling_mode_t scaling_mode; + if (!avifFindAOMScalingMode(&layers[layerIndex].horizontalMode, &scaling_mode.h_scaling_mode)) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } - if (!avifFindAOMScalingMode(&layers[layerIndex].verticalMode, &scaling_mode.v_scaling_mode)) { - return AVIF_RESULT_NOT_IMPLEMENTED; - } + if (!avifFindAOMScalingMode(&layers[layerIndex].verticalMode, &scaling_mode.v_scaling_mode)) { + return AVIF_RESULT_NOT_IMPLEMENTED; + } - if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SCALEMODE, &scaling_mode) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; + } - if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { - return AVIF_RESULT_UNKNOWN_ERROR; - } - } break; + if (aom_codec_control(&codec->internal->encoder, AOME_SET_SPATIAL_LAYER_ID, layerIndex) != AOM_CODEC_OK) { + return AVIF_RESULT_UNKNOWN_ERROR; } } diff --git a/src/write.c b/src/write.c index c4da243fd4..7462747500 100644 --- a/src/write.c +++ b/src/write.c @@ -1553,7 +1553,8 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) // Interleave - Pick out and record layered image items, interleave them later. // Layer image items have same number of samples and fixups. - if (useInterleave && item->encodeOutput->samples.count > 0 && item->encodeOutput->samples.count == item->mdatFixups.count) { + if (useInterleave && (item->encodeOutput->samples.count > 0) && + (item->encodeOutput->samples.count == item->mdatFixups.count)) { avifEncoderItemReference * ref; if (item->alpha) { ref = (avifEncoderItemReference *)avifArrayPushPtr(&layeredAlphaItems); diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc index cb610a0688..2074c0a6f6 100644 --- a/tests/gtest/avifprogressivetest.cc +++ b/tests/gtest/avifprogressivetest.cc @@ -1,30 +1,24 @@ // Copyright 2022 Google LLC. All rights reserved. // SPDX-License-Identifier: BSD-2-Clause -#include - #include "avif/avif.h" #include "aviftest_helpers.h" #include "gtest/gtest.h" -using ::testing::Combine; -using ::testing::Values; -using ::testing::ValuesIn; - namespace libavif { namespace { - class ProgressiveTest : public testing::Test {}; -} // namespace TEST(ProgressiveTest, EncodeDecode) { if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) { - GTEST_SKIP_("ProgressiveTest requires AOM encoder."); + GTEST_SKIP() << "ProgressiveTest requires AOM encoder."; } - testutil::AvifImagePtr image = testutil::CreateImage( - 512, 512, 8, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_YUV, AVIF_RANGE_FULL); + const int image_size = 512; + testutil::AvifImagePtr image = + testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV444, + AVIF_PLANES_YUV, AVIF_RANGE_FULL); ASSERT_NE(image, nullptr); testutil::FillImageGradient(image.get()); @@ -36,11 +30,10 @@ TEST(ProgressiveTest, EncodeDecode) { encoder->extraLayerCount = 1; encoder->layers[0] = {50, 50, {1, 4}, {1, 4}}; encoder->layers[1] = {25, 25, {1, 1}, {1, 1}}; - std::array layer_image_ptrs = {image.get(), image.get()}; - ASSERT_EQ( - avifEncoderAddImageProgressive(encoder.get(), layer_image_ptrs.data(), - AVIF_ADD_IMAGE_FLAG_SINGLE), - AVIF_RESULT_OK); + avifImage* layer_image_ptrs[2] = {image.get(), image.get()}; + ASSERT_EQ(avifEncoderAddImageProgressive(encoder.get(), layer_image_ptrs, + AVIF_ADD_IMAGE_FLAG_SINGLE), + AVIF_RESULT_OK); testutil::AvifRwData encodedAvif; ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK); @@ -55,8 +48,13 @@ TEST(ProgressiveTest, EncodeDecode) { ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); ASSERT_EQ(decoder->progressiveState, AVIF_PROGRESSIVE_STATE_ACTIVE); ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); - // Check decoder->image + ASSERT_EQ(decoder->image->width, image_size); + ASSERT_EQ(decoder->image->height, image_size); + // TODO Check decoder->image and image are similar ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); - // Check decoder->image + ASSERT_EQ(decoder->image->width, image_size); + ASSERT_EQ(decoder->image->height, image_size); + // TODO Check decoder->image and image are more similar than previous layer } -} // namespace libavif \ No newline at end of file +} // namespace +} // namespace libavif From b8219ad6c9190b1ad06392d8d8fb1c4b96fe18b6 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sat, 16 Jul 2022 18:06:01 +0800 Subject: [PATCH 12/14] not setting AVIF_ADD_IMAGE_FLAG_SINGLE for layer image --- include/avif/avif.h | 8 +++++--- src/codec_aom.c | 17 ++++++----------- src/write.c | 10 +++++++--- tests/gtest/avifprogressivetest.cc | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/include/avif/avif.h b/include/avif/avif.h index b89772afda..6bd7da8bdc 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -1041,6 +1041,8 @@ typedef struct avifEncoder uint64_t timescale; // timescale of the media (Hz) // Layers (used by progressive rendering) + // * Note: libavif currently can only properly decode images without alpha, + // or images whose extraLayerCount == extraLayerCountAlpha, if progressive decode is enabled. int extraLayerCount; // Extra color layers; 0 for regular single-layer color image (default). int extraLayerCountAlpha; // Extra alpha layers; 0 for regular single-layer alpha image (default). avifLayerConfig layers[MAX_AV1_LAYER_COUNT]; @@ -1068,9 +1070,9 @@ typedef enum avifAddImageFlag // Force this frame to be a keyframe (sync frame). AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME = (1 << 0), - // Use this flag when encoding a single image. Signals "still_picture" to AV1 encoders, which - // tweaks various compression rules. This is enabled automatically when using the - // avifEncoderWrite() single-image encode path. + // Use this flag when encoding a single frame, single layer image. + // Signals "still_picture" to AV1 encoders, which tweaks various compression rules. + // This is enabled automatically when using the avifEncoderWrite() single-image encode path. AVIF_ADD_IMAGE_FLAG_SINGLE = (1 << 1) } avifAddImageFlag; typedef uint32_t avifAddImageFlags; diff --git a/src/codec_aom.c b/src/codec_aom.c index 4351260ac7..cfab69d32b 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -557,11 +557,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, { uint32_t extraLayerCount = alpha ? encoder->extraLayerCountAlpha : encoder->extraLayerCount; const avifLayerConfig * layers = alpha ? encoder->layersAlpha : encoder->layers; - // Disable single image flag to allow interlayer compression - // todo: we clearly don't want ALL_INTRA, but other tweaks should be reviewed as well. - if (extraLayerCount > 0) { - addImageFlags &= ~AVIF_ADD_IMAGE_FLAG_SINGLE; - } aom_codec_enc_cfg_t * cfg = &codec->internal->cfg; // Map encoder speed to AOM usage + CpuUsed: @@ -696,7 +691,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, cfg->g_input_bit_depth = image->depth; cfg->g_w = image->width; cfg->g_h = image->height; - if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) { + if (extraLayerCount > 0) { + cfg->g_lag_in_frames = 0; + } else 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. @@ -714,9 +711,6 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, // Tell libaom that all frames will be key frames. cfg->kf_max_dist = 0; } - if (extraLayerCount > 0) { - cfg->g_lag_in_frames = 0; - } if (encoder->maxThreads > 1) { cfg->g_threads = encoder->maxThreads; } @@ -1037,9 +1031,10 @@ static avifResult aomCodecEncodeImage(avifCodec * codec, } } - if ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) || ((extraLayerCount > 0) && (layerIndex == extraLayerCount))) { + if (((extraLayerCount > 0) && (layerIndex == extraLayerCount)) || + ((extraLayerCount == 0) && (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE))) { // Flush and clean up encoder resources early to save on overhead when encoding alpha or grid images. - // (For layered image, this is when the last layer is encoded.) + // (For layered image, this should be done after the last layer is encoded.) if (!aomCodecEncodeFinish(codec, output)) { return AVIF_RESULT_UNKNOWN_ERROR; diff --git a/src/write.c b/src/write.c index 7462747500..a92dc28c26 100644 --- a/src/write.c +++ b/src/write.c @@ -743,7 +743,8 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder, } encoder->data->alphaPresent = (firstCell->alphaPlane != NULL); - if (encoder->data->alphaPresent && (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE)) { + if (encoder->data->alphaPresent && + ((addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) || (encoder->extraLayerCount > 0) || (encoder->extraLayerCountAlpha > 0))) { // If encoding a single image in which the alpha plane exists but is entirely opaque, // simply skip writing an alpha AV1 payload entirely, as it'll be interpreted as opaque // and is less bytes. @@ -879,13 +880,16 @@ avifResult avifEncoderAddImageGrid(avifEncoder * encoder, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single image grids are supported + if (encoder->extraLayerCount == 0 && encoder->extraLayerCountAlpha == 0) { + addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; // only single image grids are supported + } + return avifEncoderAddImageInternal(encoder, gridCols, gridRows, cellImages, 1, addImageFlags); } avifResult avifEncoderAddImageProgressive(avifEncoder * encoder, const avifImage * const * layerImages, avifAddImageFlags addImageFlags) { avifDiagnosticsClearError(&encoder->diag); - return avifEncoderAddImageInternal(encoder, 1, 1, layerImages, 1, addImageFlags | AVIF_ADD_IMAGE_FLAG_SINGLE); // only single frame progressive images are supported + return avifEncoderAddImageInternal(encoder, 1, 1, layerImages, 1, addImageFlags); } static size_t avifEncoderFindExistingChunk(avifRWStream * s, size_t mdatStartOffset, const uint8_t * data, size_t size) diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc index 2074c0a6f6..d084b98069 100644 --- a/tests/gtest/avifprogressivetest.cc +++ b/tests/gtest/avifprogressivetest.cc @@ -32,7 +32,7 @@ TEST(ProgressiveTest, EncodeDecode) { encoder->layers[1] = {25, 25, {1, 1}, {1, 1}}; avifImage* layer_image_ptrs[2] = {image.get(), image.get()}; ASSERT_EQ(avifEncoderAddImageProgressive(encoder.get(), layer_image_ptrs, - AVIF_ADD_IMAGE_FLAG_SINGLE), + AVIF_ADD_IMAGE_FLAG_NONE), AVIF_RESULT_OK); testutil::AvifRwData encodedAvif; ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK); From ae6a5b526c0b131e9ca451916e8a205ac5c3be81 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Sat, 16 Jul 2022 18:38:04 +0800 Subject: [PATCH 13/14] fix compile error --- tests/gtest/avifprogressivetest.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gtest/avifprogressivetest.cc b/tests/gtest/avifprogressivetest.cc index d084b98069..33ccce4dcb 100644 --- a/tests/gtest/avifprogressivetest.cc +++ b/tests/gtest/avifprogressivetest.cc @@ -15,7 +15,7 @@ TEST(ProgressiveTest, EncodeDecode) { GTEST_SKIP() << "ProgressiveTest requires AOM encoder."; } - const int image_size = 512; + const uint32_t image_size = 512; testutil::AvifImagePtr image = testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_YUV, AVIF_RANGE_FULL); From bc713007e20d717f16bc87d39625a3a0a35caea8 Mon Sep 17 00:00:00 2001 From: Yuan Tong Date: Mon, 18 Jul 2022 13:30:28 +0800 Subject: [PATCH 14/14] fix MSVC compile error --- src/write.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/write.c b/src/write.c index a92dc28c26..2a26d47a89 100644 --- a/src/write.c +++ b/src/write.c @@ -1026,9 +1026,9 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) { avifEncoderItem * item = &encoder->data->items.item[itemIndex]; - avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; - avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; - avifRWStreamWriteU16(&s, item->extraLayerCount + 1); // unsigned int(16) extent_count; + avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID; + avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; + avifRWStreamWriteU16(&s, (uint16_t)(item->extraLayerCount + 1)); // unsigned int(16) extent_count; for (uint32_t i = 0; i < item->extraLayerCount + 1; ++i) { avifEncoderItemAddMdatFixup(item, &s); @@ -1222,8 +1222,8 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output) } } - avifRWStreamWriteU8(&dedup->s, largeSize); // unsigned int(7) reserved = 0; - // unsigned int(1) large_size; + avifRWStreamWriteU8(&dedup->s, (uint8_t)largeSize); // unsigned int(7) reserved = 0; + // unsigned int(1) large_size; // FieldLength = (large_size + 1) * 16; // unsigned int(FieldLength) layer_size[3];