Distribute out and share code populating av01 config box across codecs
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e29c8b..5488552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ ## [Unreleased] ### Changed - Fix QP range for rav1e encodes (rav1e uses [0-255], not [0-63]) +- Distribute out and share code populating av01 config box across codecs ## [0.4.3] - 2019-10-28 ### Added
diff --git a/include/avif/internal.h b/include/avif/internal.h index a8de61c..c0bee2d 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h
@@ -129,19 +129,18 @@ typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec, avifImage * image); // avifCodecEncodeImageFunc: if either OBU* is null, skip its encode. alpha should always be lossless typedef avifBool (*avifCodecEncodeImageFunc)(struct avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha); -typedef void (*avifCodecGetConfigurationBoxFunc)(struct avifCodec * codec, avifCodecConfigurationBox * outConfig); typedef void (*avifCodecDestroyInternalFunc)(struct avifCodec * codec); typedef struct avifCodec { avifCodecDecodeInput * decodeInput; + avifCodecConfigurationBox configBox; // Pre-populated by avifEncoderWrite(), available and overridable by codec impls struct avifCodecInternal * internal; // up to each codec to use how it wants avifCodecOpenFunc open; avifCodecAlphaLimitedRangeFunc alphaLimitedRange; avifCodecGetNextImageFunc getNextImage; avifCodecEncodeImageFunc encodeImage; - avifCodecGetConfigurationBoxFunc getConfigurationBox; avifCodecDestroyInternalFunc destroyInternal; } avifCodec;
diff --git a/src/codec_aom.c b/src/codec_aom.c index ff9ff52..fee504f 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c
@@ -34,8 +34,6 @@ aom_codec_iter_t iter; uint32_t inputSampleIndex; aom_image_t * image; - - avifCodecConfigurationBox config; }; static void aomCodecDestroyInternal(avifCodec * codec) @@ -193,12 +191,12 @@ return AVIF_TRUE; } -static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alphaOnly, int * yShift) +static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alpha, int * yShift) { *yShift = 0; aom_img_fmt_t fmt; - if (alphaOnly) { + if (alpha) { // We're going monochrome, who cares about chroma quality fmt = AOM_IMG_FMT_I420; *yShift = 1; @@ -230,16 +228,14 @@ return fmt; } -static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifEncoder * encoder, avifRWData * outputOBU, avifCodecConfigurationBox * outputConfig) +static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha) { avifBool success = AVIF_FALSE; aom_codec_iface_t * encoder_interface = aom_codec_av1_cx(); aom_codec_ctx_t aomEncoder; - memset(outputConfig, 0, sizeof(avifCodecConfigurationBox)); - int yShift = 0; - aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alphaOnly, &yShift); + aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alpha, &yShift); if (aomFormat == AOM_IMG_FMT_NONE) { return AVIF_FALSE; } @@ -250,40 +246,7 @@ struct aom_codec_enc_cfg cfg; aom_codec_enc_config_default(encoder_interface, &cfg, 0); - // Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only. - // Profile 1. 8-bit and 10-bit 4:4:4 - // Profile 2. 8-bit and 10-bit 4:2:2 - // 12-bit 4:0:0, 4:2:2 and 4:4:4 - if (image->depth == 12) { - // Only profile 2 can handle 12 bit - cfg.g_profile = 2; - } else { - // 8-bit or 10-bit - - if (alphaOnly) { - // Assuming aomImage->monochrome makes it 4:0:0 - cfg.g_profile = 0; - } else { - switch (image->yuvFormat) { - case AVIF_PIXEL_FORMAT_YUV444: - cfg.g_profile = 1; - break; - case AVIF_PIXEL_FORMAT_YUV422: - cfg.g_profile = 2; - break; - case AVIF_PIXEL_FORMAT_YUV420: - cfg.g_profile = 0; - break; - case AVIF_PIXEL_FORMAT_YV12: - cfg.g_profile = 0; - break; - case AVIF_PIXEL_FORMAT_NONE: - default: - break; - } - } - } - + cfg.g_profile = codec->configBox.seqProfile; cfg.g_bit_depth = image->depth; cfg.g_input_bit_depth = image->depth; cfg.g_w = image->width; @@ -292,32 +255,9 @@ cfg.g_threads = encoder->maxThreads; } - // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf - uint8_t seqLevelIdx0 = 31; - if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) { - // Image is 5.1 compatible - seqLevelIdx0 = 13; // 5.1 - } - - outputConfig->seqProfile = (uint8_t)cfg.g_profile; - outputConfig->seqLevelIdx0 = seqLevelIdx0; - outputConfig->seqTier0 = 0; - outputConfig->highBitdepth = (image->depth > 8) ? 1 : 0; - outputConfig->twelveBit = (image->depth == 12) ? 1 : 0; - outputConfig->monochrome = alphaOnly ? 1 : 0; - outputConfig->chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX; - outputConfig->chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY; - - // TODO: choose the correct one from below: - // * 0 - CSP_UNKNOWN Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream) - // * 1 - CSP_VERTICAL Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples - // * 2 - CSP_COLOCATED co-located with (0, 0) luma sample - // * 3 - CSP_RESERVED - outputConfig->chromaSamplePosition = 0; - int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63); int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63); - if (alphaOnly) { + if (alpha) { minQuantizer = AVIF_QUANTIZER_LOSSLESS; maxQuantizer = AVIF_QUANTIZER_LOSSLESS; } @@ -351,7 +291,7 @@ uint32_t uvHeight = image->height >> yShift; aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16); - if (alphaOnly) { + if (alpha) { aomImage->range = AOM_CR_FULL_RANGE; // Alpha is always full range aom_codec_control(&aomEncoder, AV1E_SET_COLOR_RANGE, aomImage->range); aomImage->monochrome = 1; @@ -406,7 +346,7 @@ if (pkt == NULL) break; if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { - avifRWDataSet(outputOBU, pkt->data.frame.buf, pkt->data.frame.sz); + avifRWDataSet(obu, pkt->data.frame.buf, pkt->data.frame.sz); success = AVIF_TRUE; break; } @@ -417,19 +357,6 @@ return success; } -static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha) -{ - if (!encodeOBU(image, alpha, encoder, obu, &codec->internal->config)) { - return AVIF_FALSE; - } - return AVIF_TRUE; -} - -static void aomCodecGetConfigurationBox(avifCodec * codec, avifCodecConfigurationBox * outConfig) -{ - memcpy(outConfig, &codec->internal->config, sizeof(avifCodecConfigurationBox)); -} - const char * avifCodecVersionAOM(void) { return aom_codec_version_str(); @@ -443,7 +370,6 @@ codec->alphaLimitedRange = aomCodecAlphaLimitedRange; codec->getNextImage = aomCodecGetNextImage; codec->encodeImage = aomCodecEncodeImage; - codec->getConfigurationBox = aomCodecGetConfigurationBox; codec->destroyInternal = aomCodecDestroyInternal; codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 0d1c522..a27380d 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c
@@ -9,7 +9,7 @@ struct avifCodecInternal { - avifCodecConfigurationBox config; + uint32_t unused; // rav1e codec has no state }; static void rav1eCodecDestroyInternal(avifCodec * codec) @@ -26,6 +26,8 @@ static avifBool rav1eCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha) { + (void)codec; // unused + avifBool success = AVIF_FALSE; RaConfig * rav1eConfig = NULL; @@ -58,9 +60,6 @@ } } - avifPixelFormatInfo formatInfo; - avifGetPixelFormatInfo(image->yuvFormat, &formatInfo); - rav1eConfig = rav1e_config_default(); if (rav1e_config_set_pixel_format( rav1eConfig, (uint8_t)image->depth, chromaSampling, RA_CHROMA_SAMPLE_POSITION_UNKNOWN, rav1eRange) < 0) { @@ -92,57 +91,6 @@ goto cleanup; } - // Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only. - // Profile 1. 8-bit and 10-bit 4:4:4 - // Profile 2. 8-bit and 10-bit 4:2:2 - // 12-bit 4:0:0, 4:2:2 and 4:4:4 - uint8_t seqProfile = 0; - if (image->depth == 12) { - // Only seqProfile 2 can handle 12 bit - seqProfile = 2; - } else { - // 8-bit or 10-bit - - if (alpha) { - seqProfile = 0; - } else { - switch (image->yuvFormat) { - case AVIF_PIXEL_FORMAT_YUV444: - seqProfile = 1; - break; - case AVIF_PIXEL_FORMAT_YUV422: - seqProfile = 2; - break; - case AVIF_PIXEL_FORMAT_YUV420: - seqProfile = 0; - break; - case AVIF_PIXEL_FORMAT_YV12: - seqProfile = 0; - break; - case AVIF_PIXEL_FORMAT_NONE: - default: - break; - } - } - } - - // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf - uint8_t seqLevelIdx0 = 31; - if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) { - // Image is 5.1 compatible - seqLevelIdx0 = 13; // 5.1 - } - - memset(&codec->internal->config, 0, sizeof(avifCodecConfigurationBox)); - codec->internal->config.seqProfile = seqProfile; - codec->internal->config.seqLevelIdx0 = seqLevelIdx0; - codec->internal->config.seqTier0 = 0; - codec->internal->config.highBitdepth = (image->depth > 8) ? 1 : 0; - codec->internal->config.twelveBit = (image->depth == 12) ? 1 : 0; - codec->internal->config.monochrome = alpha ? 1 : 0; - codec->internal->config.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX; - codec->internal->config.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY; - if (encoder->tileRowsLog2 != 0) { int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6); if (rav1e_config_parse_int(rav1eConfig, "tile_rows", 1 << tileRowsLog2) == -1) { @@ -217,11 +165,6 @@ return success; } -static void rav1eCodecGetConfigurationBox(avifCodec * codec, avifCodecConfigurationBox * outConfig) -{ - memcpy(outConfig, &codec->internal->config, sizeof(avifCodecConfigurationBox)); -} - const char * avifCodecVersionRav1e(void) { return "0"; // https://github.com/xiph/rav1e/issues/1801 @@ -233,7 +176,6 @@ memset(codec, 0, sizeof(struct avifCodec)); codec->open = rav1eCodecOpen; codec->encodeImage = rav1eCodecEncodeImage; - codec->getConfigurationBox = rav1eCodecGetConfigurationBox; codec->destroyInternal = rav1eCodecDestroyInternal; codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal));
diff --git a/src/write.c b/src/write.c index 23774a1..b9c6447 100644 --- a/src/write.c +++ b/src/write.c
@@ -21,6 +21,7 @@ static const size_t alphaURNSize = sizeof(alphaURN); static avifBool avifImageIsOpaque(avifImage * image); +static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha); static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg); avifEncoder * avifEncoderCreate(void) @@ -65,6 +66,17 @@ } } + // ----------------------------------------------------------------------- + // Pre-fill config boxes based on image (codec can query/update later) + + fillConfigBox(codec[AVIF_CODEC_PLANES_COLOR], image, AVIF_FALSE); + if (codec[AVIF_CODEC_PLANES_ALPHA]) { + fillConfigBox(codec[AVIF_CODEC_PLANES_ALPHA], image, AVIF_TRUE); + } + + // ----------------------------------------------------------------------- + // Begin write stream + avifRWStream s; avifRWStreamStart(&s, output); @@ -273,9 +285,7 @@ ++ipcoIndex; ipmaPush(&ipmaColor, ipcoIndex); - avifCodecConfigurationBox colorConfig; - codec[AVIF_CODEC_PLANES_COLOR]->getConfigurationBox(codec[AVIF_CODEC_PLANES_COLOR], &colorConfig); - writeConfigBox(&s, &colorConfig); + writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_COLOR]->configBox); ++ipcoIndex; ipmaPush(&ipmaColor, ipcoIndex); @@ -287,9 +297,7 @@ ++ipcoIndex; ipmaPush(&ipmaAlpha, ipcoIndex); - avifCodecConfigurationBox alphaConfig; - codec[AVIF_CODEC_PLANES_ALPHA]->getConfigurationBox(codec[AVIF_CODEC_PLANES_ALPHA], &alphaConfig); - writeConfigBox(&s, &alphaConfig); + writeConfigBox(&s, &codec[AVIF_CODEC_PLANES_ALPHA]->configBox); ++ipcoIndex; ipmaPush(&ipmaAlpha, ipcoIndex); @@ -409,6 +417,70 @@ return AVIF_TRUE; } +static void fillConfigBox(avifCodec * codec, avifImage * image, avifBool alpha) +{ + avifPixelFormatInfo formatInfo; + avifGetPixelFormatInfo(image->yuvFormat, &formatInfo); + + // Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only. + // Profile 1. 8-bit and 10-bit 4:4:4 + // Profile 2. 8-bit and 10-bit 4:2:2 + // 12-bit 4:0:0, 4:2:2 and 4:4:4 + uint8_t seqProfile = 0; + if (image->depth == 12) { + // Only seqProfile 2 can handle 12 bit + seqProfile = 2; + } else { + // 8-bit or 10-bit + + if (alpha) { + seqProfile = 0; + } else { + switch (image->yuvFormat) { + case AVIF_PIXEL_FORMAT_YUV444: + seqProfile = 1; + break; + case AVIF_PIXEL_FORMAT_YUV422: + seqProfile = 2; + break; + case AVIF_PIXEL_FORMAT_YUV420: + seqProfile = 0; + break; + case AVIF_PIXEL_FORMAT_YV12: + seqProfile = 0; + break; + case AVIF_PIXEL_FORMAT_NONE: + default: + break; + } + } + } + + // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf + uint8_t seqLevelIdx0 = 31; + if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) { + // Image is 5.1 compatible + seqLevelIdx0 = 13; // 5.1 + } + + memset(&codec->configBox, 0, sizeof(avifCodecConfigurationBox)); + codec->configBox.seqProfile = seqProfile; + codec->configBox.seqLevelIdx0 = seqLevelIdx0; + codec->configBox.seqTier0 = 0; + codec->configBox.highBitdepth = (image->depth > 8) ? 1 : 0; + codec->configBox.twelveBit = (image->depth == 12) ? 1 : 0; + codec->configBox.monochrome = alpha ? 1 : 0; + codec->configBox.chromaSubsamplingX = (uint8_t)formatInfo.chromaShiftX; + codec->configBox.chromaSubsamplingY = (uint8_t)formatInfo.chromaShiftY; + + // TODO: choose the correct one from below: + // * 0 - CSP_UNKNOWN Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream) + // * 1 - CSP_VERTICAL Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples + // * 2 - CSP_COLOCATED co-located with (0, 0) luma sample + // * 3 - CSP_RESERVED + codec->configBox.chromaSamplePosition = 0; +} + static void writeConfigBox(avifRWStream * s, avifCodecConfigurationBox * cfg) { avifBoxMarker av1C = avifRWStreamWriteBox(s, "av1C", -1, 0);