blob: e05c186ad306155a8adf272edd3d344c7afeb7be [file] [log] [blame]
// 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));
}