| // Copyright 2019 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include "avif/internal.h" |
| |
| #include "aom/aom_decoder.h" |
| #include "aom/aomdx.h" |
| #include "aom/aom_encoder.h" |
| #include "aom/aomcx.h" |
| |
| #include <string.h> |
| |
| struct avifCodecInternal |
| { |
| avifBool decoderInitialized[AVIF_CODEC_PLANES_COUNT]; |
| aom_codec_ctx_t decoders[AVIF_CODEC_PLANES_COUNT]; |
| |
| aom_image_t * images[AVIF_CODEC_PLANES_COUNT]; |
| avifRawData encodedOBUs[AVIF_CODEC_PLANES_COUNT]; |
| avifCodecConfigurationBox configs[AVIF_CODEC_PLANES_COUNT]; |
| }; |
| |
| avifCodec * avifCodecCreate() |
| { |
| avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec)); |
| codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal)); |
| memset(codec->internal, 0, sizeof(struct avifCodecInternal)); |
| return codec; |
| } |
| |
| void avifCodecDestroy(avifCodec * codec) |
| { |
| for (int plane = 0; plane < AVIF_CODEC_PLANES_COUNT; ++plane) { |
| if (codec->internal->decoderInitialized[plane]) { |
| aom_codec_destroy(&codec->internal->decoders[plane]); |
| } |
| avifRawDataFree(&codec->internal->encodedOBUs[plane]); |
| } |
| avifFree(codec->internal); |
| avifFree(codec); |
| } |
| |
| avifBool avifCodecDecode(avifCodec * codec, avifCodecPlanes planes, avifRawData * obu) |
| { |
| aom_codec_stream_info_t si; |
| aom_codec_iface_t * decoder_interface = aom_codec_av1_dx(); |
| if (aom_codec_dec_init(&codec->internal->decoders[planes], decoder_interface, NULL, 0)) { |
| return AVIF_FALSE; |
| } |
| codec->internal->decoderInitialized[planes] = AVIF_TRUE; |
| |
| if (aom_codec_control(&codec->internal->decoders[planes], AV1D_SET_OUTPUT_ALL_LAYERS, 1)) { |
| return AVIF_FALSE; |
| } |
| |
| si.is_annexb = 0; |
| if (aom_codec_peek_stream_info(decoder_interface, obu->data, obu->size, &si)) { |
| return AVIF_FALSE; |
| } |
| |
| if (aom_codec_decode(&codec->internal->decoders[planes], obu->data, obu->size, NULL)) { |
| return AVIF_FALSE; |
| } |
| |
| aom_codec_iter_t iter = NULL; |
| codec->internal->images[planes] = aom_codec_get_frame(&codec->internal->decoders[planes], &iter); // It doesn't appear that I own this / need to free this |
| return (codec->internal->images[planes]) ? AVIF_TRUE : AVIF_FALSE; |
| } |
| |
| avifCodecImageSize avifCodecGetImageSize(avifCodec * codec, avifCodecPlanes planes) |
| { |
| avifCodecImageSize size; |
| if (codec->internal->images[planes]) { |
| size.width = codec->internal->images[planes]->d_w; |
| size.height = codec->internal->images[planes]->d_h; |
| } else { |
| size.width = 0; |
| size.height = 0; |
| } |
| return size; |
| } |
| |
| avifBool avifCodecAlphaLimitedRange(avifCodec * codec) |
| { |
| aom_image_t * aomAlphaImage = codec->internal->images[AVIF_CODEC_PLANES_ALPHA]; |
| if (aomAlphaImage && (aomAlphaImage->range == AOM_CR_STUDIO_RANGE)) { |
| return AVIF_TRUE; |
| } |
| return AVIF_FALSE; |
| } |
| |
| avifResult avifCodecGetDecodedImage(avifCodec * codec, avifImage * image) |
| { |
| aom_image_t * aomColorImage = codec->internal->images[AVIF_CODEC_PLANES_COLOR]; |
| aom_image_t * aomAlphaImage = codec->internal->images[AVIF_CODEC_PLANES_ALPHA]; |
| avifBool hasAlpha = aomAlphaImage ? AVIF_TRUE : AVIF_FALSE; |
| |
| avifPixelFormat yuvFormat = AVIF_PIXEL_FORMAT_NONE; |
| switch (aomColorImage->fmt) { |
| case AOM_IMG_FMT_I420: |
| case AOM_IMG_FMT_AOMI420: |
| case AOM_IMG_FMT_I42016: |
| yuvFormat = AVIF_PIXEL_FORMAT_YUV420; |
| break; |
| case AOM_IMG_FMT_I422: |
| case AOM_IMG_FMT_I42216: |
| yuvFormat = AVIF_PIXEL_FORMAT_YUV422; |
| break; |
| case AOM_IMG_FMT_I444: |
| case AOM_IMG_FMT_I44416: |
| yuvFormat = AVIF_PIXEL_FORMAT_YUV444; |
| break; |
| case AOM_IMG_FMT_YV12: |
| case AOM_IMG_FMT_AOMYV12: |
| case AOM_IMG_FMT_YV1216: |
| yuvFormat = AVIF_PIXEL_FORMAT_YV12; |
| break; |
| case AOM_IMG_FMT_NONE: |
| default: |
| break; |
| } |
| |
| image->width = aomColorImage->d_w; |
| image->height = aomColorImage->d_h; |
| image->depth = aomColorImage->bit_depth; |
| image->yuvFormat = yuvFormat; |
| image->yuvRange = (aomColorImage->range == AOM_CR_STUDIO_RANGE) ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL; |
| |
| avifPixelFormatInfo formatInfo; |
| avifGetPixelFormatInfo(yuvFormat, &formatInfo); |
| |
| int uvHeight = image->height >> formatInfo.chromaShiftY; |
| avifImageAllocatePlanes(image, AVIF_PLANES_YUV); |
| for (int yuvPlane = 0; yuvPlane < 3; ++yuvPlane) { |
| int aomPlaneIndex = yuvPlane; |
| int planeHeight = image->height; |
| if (yuvPlane == AVIF_CHAN_U) { |
| aomPlaneIndex = formatInfo.aomIndexU; |
| planeHeight = uvHeight; |
| } else if (yuvPlane == AVIF_CHAN_V) { |
| aomPlaneIndex = formatInfo.aomIndexV; |
| planeHeight = uvHeight; |
| } |
| |
| for (int j = 0; j < planeHeight; ++j) { |
| uint8_t * srcRow = &aomColorImage->planes[aomPlaneIndex][j * aomColorImage->stride[aomPlaneIndex]]; |
| uint8_t * dstRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]]; |
| memcpy(dstRow, srcRow, image->yuvRowBytes[yuvPlane]); |
| } |
| } |
| |
| if (hasAlpha) { |
| avifImageAllocatePlanes(image, AVIF_PLANES_A); |
| for (int j = 0; j < image->height; ++j) { |
| uint8_t * srcAlphaRow = &aomAlphaImage->planes[0][j * aomAlphaImage->stride[0]]; |
| uint8_t * dstAlphaRow = &image->alphaPlane[j * image->alphaRowBytes]; |
| memcpy(dstAlphaRow, srcAlphaRow, image->alphaRowBytes); |
| } |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alphaOnly, int * yShift) |
| { |
| *yShift = 0; |
| |
| aom_img_fmt_t fmt; |
| if (alphaOnly) { |
| // We're going monochrome, who cares about chroma quality |
| fmt = AOM_IMG_FMT_I420; |
| *yShift = 1; |
| } else { |
| switch (image->yuvFormat) { |
| case AVIF_PIXEL_FORMAT_YUV444: |
| fmt = AOM_IMG_FMT_I444; |
| break; |
| case AVIF_PIXEL_FORMAT_YUV422: |
| fmt = AOM_IMG_FMT_I422; |
| break; |
| case AVIF_PIXEL_FORMAT_YUV420: |
| fmt = AOM_IMG_FMT_I420; |
| *yShift = 1; |
| break; |
| case AVIF_PIXEL_FORMAT_YV12: |
| fmt = AOM_IMG_FMT_YV12; |
| *yShift = 1; |
| break; |
| default: |
| return AOM_IMG_FMT_NONE; |
| } |
| } |
| |
| if (image->depth > 8) { |
| fmt |= AOM_IMG_FMT_HIGHBITDEPTH; |
| } |
| |
| return fmt; |
| } |
| |
| static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, int numThreads, int quality, avifRawData * outputOBU, avifCodecConfigurationBox * outputConfig) |
| { |
| avifBool success = AVIF_FALSE; |
| aom_codec_iface_t * encoder_interface = aom_codec_av1_cx(); |
| aom_codec_ctx_t encoder; |
| |
| memset(outputConfig, 0, sizeof(avifCodecConfigurationBox)); |
| |
| int yShift = 0; |
| aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alphaOnly, &yShift); |
| if (aomFormat == AOM_IMG_FMT_NONE) { |
| return AVIF_FALSE; |
| } |
| |
| avifPixelFormatInfo formatInfo; |
| avifGetPixelFormatInfo(image->yuvFormat, &formatInfo); |
| |
| 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_bit_depth = image->depth; |
| cfg.g_input_bit_depth = image->depth; |
| cfg.g_w = image->width; |
| cfg.g_h = image->height; |
| if (numThreads > 1) { |
| cfg.g_threads = numThreads; |
| } |
| |
| // 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 = 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 = formatInfo.chromaShiftX; |
| outputConfig->chromaSubsamplingY = 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; |
| |
| avifBool lossless = (quality == AVIF_BEST_QUALITY) ? AVIF_TRUE : AVIF_FALSE; |
| cfg.rc_min_quantizer = 0; |
| if (lossless) { |
| cfg.rc_max_quantizer = 0; |
| } else { |
| cfg.rc_max_quantizer = quality; |
| } |
| |
| uint32_t encoderFlags = 0; |
| if (image->depth > 8) { |
| encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH; |
| } |
| aom_codec_enc_init(&encoder, encoder_interface, &cfg, encoderFlags); |
| |
| if (lossless) { |
| aom_codec_control(&encoder, AV1E_SET_LOSSLESS, 1); |
| } |
| if (numThreads > 1) { |
| aom_codec_control(&encoder, AV1E_SET_ROW_MT, 1); |
| } |
| |
| int uvHeight = image->height >> yShift; |
| aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16); |
| |
| if (alphaOnly) { |
| aomImage->range = AOM_CR_FULL_RANGE; // Alpha is always full range |
| aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, aomImage->range); |
| aomImage->monochrome = 1; |
| for (int j = 0; j < image->height; ++j) { |
| uint8_t * srcAlphaRow = &image->alphaPlane[j * image->alphaRowBytes]; |
| uint8_t * dstAlphaRow = &aomImage->planes[0][j * aomImage->stride[0]]; |
| memcpy(dstAlphaRow, srcAlphaRow, image->alphaRowBytes); |
| } |
| |
| for (int j = 0; j < uvHeight; ++j) { |
| // Zero out U and V |
| memset(&aomImage->planes[1][j * aomImage->stride[1]], 0, aomImage->stride[1]); |
| memset(&aomImage->planes[2][j * aomImage->stride[2]], 0, aomImage->stride[2]); |
| } |
| } else { |
| aomImage->range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE; |
| aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, aomImage->range); |
| for (int yuvPlane = 0; yuvPlane < 3; ++yuvPlane) { |
| int aomPlaneIndex = yuvPlane; |
| int planeHeight = image->height; |
| if (yuvPlane == AVIF_CHAN_U) { |
| aomPlaneIndex = formatInfo.aomIndexU; |
| planeHeight = uvHeight; |
| } else if (yuvPlane == AVIF_CHAN_V) { |
| aomPlaneIndex = formatInfo.aomIndexV; |
| planeHeight = uvHeight; |
| } |
| |
| for (int j = 0; j < planeHeight; ++j) { |
| uint8_t * srcRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]]; |
| uint8_t * dstRow = &aomImage->planes[aomPlaneIndex][j * aomImage->stride[aomPlaneIndex]]; |
| memcpy(dstRow, srcRow, image->yuvRowBytes[yuvPlane]); |
| } |
| } |
| } |
| |
| aom_codec_encode(&encoder, aomImage, 0, 1, 0); |
| aom_codec_encode(&encoder, NULL, 0, 1, 0); // flush |
| |
| aom_codec_iter_t iter = NULL; |
| for (;;) { |
| const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&encoder, &iter); |
| if (pkt == NULL) |
| break; |
| if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { |
| avifRawDataSet(outputOBU, pkt->data.frame.buf, pkt->data.frame.sz); |
| success = AVIF_TRUE; |
| break; |
| } |
| } |
| |
| aom_img_free(aomImage); |
| aom_codec_destroy(&encoder); |
| return success; |
| } |
| |
| avifResult avifCodecEncodeImage(avifCodec * codec, avifImage * image, int numThreads, int colorQuality, avifRawData * colorOBU, avifRawData * alphaOBU) |
| { |
| if (colorOBU) { |
| if (!encodeOBU(image, AVIF_FALSE, numThreads, colorQuality, colorOBU, &codec->internal->configs[AVIF_CODEC_PLANES_COLOR])) { |
| return AVIF_RESULT_ENCODE_COLOR_FAILED; |
| } |
| } |
| if (alphaOBU) { |
| if (!encodeOBU(image, AVIF_TRUE, numThreads, AVIF_BEST_QUALITY, alphaOBU, &codec->internal->configs[AVIF_CODEC_PLANES_ALPHA])) { |
| return AVIF_RESULT_ENCODE_COLOR_FAILED; |
| } |
| } |
| return AVIF_RESULT_OK; |
| } |
| |
| void avifCodecGetConfigurationBox(avifCodec * codec, avifCodecPlanes planes, avifCodecConfigurationBox * outConfig) |
| { |
| memcpy(outConfig, &codec->internal->configs[planes], sizeof(avifCodecConfigurationBox)); |
| } |