blob: 1b50eeefcefdf1fe81a4d003c5eeb0ab5d49fba1 [file] [log] [blame]
// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/internal.h"
#include <string.h>
#include "aom/aom_encoder.h"
#include "aom/aomcx.h"
static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifRawData * outputOBU, int quality);
static avifBool avifImageIsOpaque(avifImage * image);
avifResult avifImageWrite(avifImage * image, avifRawData * output, int quality)
{
avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
avifRawData colorOBU = AVIF_RAW_DATA_EMPTY;
avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY;
avifStream s;
avifStreamStart(&s, output);
// -----------------------------------------------------------------------
// Reformat pixels, if need be
avifPixelFormat dstPixelFormat = image->pixelFormat;
if (image->pixelFormat == AVIF_PIXEL_FORMAT_RGBA) {
// AV1 doesn't support RGB, reformat
dstPixelFormat = AVIF_PIXEL_FORMAT_YUV444;
}
#if 0 // TODO: implement choice in depth
int dstDepth = AVIF_CLAMP(image->depth, 8, 12);
if ((dstDepth == 9) || (dstDepth == 11)) {
++dstDepth;
}
#else
int dstDepth = 12;
#endif
avifImage * pixelImage = image;
avifImage * reformattedImage = NULL;
if ((image->pixelFormat != dstPixelFormat) || (image->depth != dstDepth)) {
reformattedImage = avifImageCreate();
avifResult reformatResult = avifImageReformatPixels(image, reformattedImage, dstPixelFormat, dstDepth);
if (reformatResult != AVIF_RESULT_OK) {
result = reformatResult;
goto writeCleanup;
}
pixelImage = reformattedImage;
}
// -----------------------------------------------------------------------
// Encode AV1 OBUs
if (!encodeOBU(pixelImage, AVIF_FALSE, &colorOBU, quality)) {
result = AVIF_RESULT_ENCODE_COLOR_FAILED;
goto writeCleanup;
}
// Skip alpha creation on opaque images
avifBool hasAlpha = AVIF_FALSE;
if (!avifImageIsOpaque(pixelImage)) {
if (!encodeOBU(pixelImage, AVIF_TRUE, &alphaOBU, quality)) {
result = AVIF_RESULT_ENCODE_ALPHA_FAILED;
goto writeCleanup;
}
hasAlpha = AVIF_TRUE;
}
// -----------------------------------------------------------------------
// Write ftyp
avifBoxMarker ftyp = avifStreamWriteBox(&s, "ftyp", -1, 0);
avifStreamWrite(&s, "mif1", 4); // unsigned int(32) major_brand;
avifStreamWriteU32(&s, 0); // unsigned int(32) minor_version;
avifStreamWrite(&s, "mif1", 4); // unsigned int(32) compatible_brands[];
avifStreamWrite(&s, "avif", 4); // ... compatible_brands[]
avifStreamWrite(&s, "miaf", 4); // ... compatible_brands[]
avifStreamFinishBox(&s, ftyp);
// -----------------------------------------------------------------------
// Write mdat
avifBoxMarker mdat = avifStreamWriteBox(&s, "mdat", -1, 0);
uint32_t colorOBUOffset = (uint32_t)s.offset;
avifStreamWrite(&s, colorOBU.data, colorOBU.size);
uint32_t alphaOBUOffset = (uint32_t)s.offset;
avifStreamWrite(&s, alphaOBU.data, alphaOBU.size);
avifStreamFinishBox(&s, mdat);
// -----------------------------------------------------------------------
// Start meta
avifBoxMarker meta = avifStreamWriteBox(&s, "meta", 0, 0);
// -----------------------------------------------------------------------
// Write hdlr
avifBoxMarker hdlr = avifStreamWriteBox(&s, "hdlr", 0, 0);
avifStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0;
avifStreamWrite(&s, "pict", 4); // unsigned int(32) handler_type;
avifStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0;
avifStreamWrite(&s, "libavif", 8); // string name; (writing null terminator)
avifStreamFinishBox(&s, hdlr);
// -----------------------------------------------------------------------
// Write pitm
avifStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t));
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
// -----------------------------------------------------------------------
// Write iloc
avifBoxMarker iloc = avifStreamWriteBox(&s, "iloc", 0, 0);
// iloc header
uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; unsigned int(4) length_size;
avifStreamWrite(&s, &offsetSizeAndLengthSize, 1); //
avifStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size; unsigned int(4) reserved;
avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) item_count;
// Item ID #1 (Color OBU)
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
avifStreamWriteU32(&s, colorOBUOffset); // unsigned int(offset_size*8) extent_offset;
avifStreamWriteU32(&s, (uint32_t)colorOBU.size); // unsigned int(length_size*8) extent_length;
if (hasAlpha) {
avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
avifStreamWriteU32(&s, alphaOBUOffset); // unsigned int(offset_size*8) extent_offset;
avifStreamWriteU32(&s, (uint32_t)alphaOBU.size); // unsigned int(length_size*8) extent_length;
}
avifStreamFinishBox(&s, iloc);
// -----------------------------------------------------------------------
// Write iinf
avifBoxMarker iinf = avifStreamWriteBox(&s, "iinf", 0, 0);
avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) entry_count;
avifBoxMarker infe0 = avifStreamWriteBox(&s, "infe", 2, 0);
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
avifStreamWrite(&s, "av01", 4); // unsigned int(32) item_type;
avifStreamWrite(&s, "Color", 6); // string item_name; (writing null terminator)
avifStreamFinishBox(&s, infe0);
if (hasAlpha) {
avifBoxMarker infe1 = avifStreamWriteBox(&s, "infe", 2, 0);
avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID;
avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index;
avifStreamWrite(&s, "av01", 4); // unsigned int(32) item_type;
avifStreamWrite(&s, "Alpha", 6); // string item_name; (writing null terminator)
avifStreamFinishBox(&s, infe1);
}
avifStreamFinishBox(&s, iinf);
// -----------------------------------------------------------------------
// Write iprp->ipco->ispe
avifBoxMarker iprp = avifStreamWriteBox(&s, "iprp", -1, 0);
avifBoxMarker ipco = avifStreamWriteBox(&s, "ipco", -1, 0);
avifBoxMarker ispe = avifStreamWriteBox(&s, "ispe", 0, 0);
avifStreamWriteU32(&s, image->width); // unsigned int(32) image_width;
avifStreamWriteU32(&s, image->height); // unsigned int(32) image_height;
avifStreamFinishBox(&s, ispe);
avifStreamFinishBox(&s, ipco);
avifBoxMarker ipma = avifStreamWriteBox(&s, "ipma", 0, 0);
avifStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID;
avifStreamWriteU8(&s, 1); // unsigned int(8) association_count;
avifStreamWriteU8(&s, 1); // bit(1) essential; unsigned int(7) property_index;
avifStreamFinishBox(&s, ipma);
avifStreamFinishBox(&s, iprp);
// -----------------------------------------------------------------------
// Finish up stream
avifStreamFinishBox(&s, meta);
avifStreamFinishWrite(&s);
result = AVIF_RESULT_OK;
// -----------------------------------------------------------------------
// Cleanup
writeCleanup:
if (reformattedImage) {
avifImageDestroy(reformattedImage);
}
avifRawDataFree(&colorOBU);
avifRawDataFree(&alphaOBU);
return result;
}
static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, avifRawData * outputOBU, int quality)
{
avifBool success = AVIF_FALSE;
aom_codec_iface_t * encoder_interface = aom_codec_av1_cx();
aom_codec_ctx_t encoder;
struct aom_codec_enc_cfg cfg;
aom_codec_enc_config_default(encoder_interface, &cfg, 0);
// Profile 2. 8-bit and 10-bit 4:2:2
// 12-bit 4:0:0, 4:2:2 and 4:4:4
cfg.g_profile = 2;
cfg.g_bit_depth = AOM_BITS_12;
cfg.g_input_bit_depth = 12;
cfg.g_w = image->width;
cfg.g_h = image->height;
// cfg.g_threads = ...;
avifBool lossless = (quality == 0) || (quality == 100) || alphaOnly; // alpha is always lossless
cfg.rc_min_quantizer = 0;
if (lossless) {
cfg.rc_max_quantizer = 0;
} else {
cfg.rc_max_quantizer = quality;
}
aom_codec_enc_init(&encoder, encoder_interface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH);
aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, AOM_CR_FULL_RANGE);
if (lossless) {
aom_codec_control(&encoder, AV1E_SET_LOSSLESS, 1);
}
aom_image_t * aomImage = aom_img_alloc(NULL, AOM_IMG_FMT_I44416, image->width, image->height, 16);
aomImage->range = AOM_CR_FULL_RANGE; // always use full range
if (alphaOnly) {
}
if (alphaOnly) {
aomImage->monochrome = 1;
for (int j = 0; j < image->height; ++j) {
for (int i = 0; i < image->width; ++i) {
for (int plane = 0; plane < 3; ++plane) {
uint16_t * planeChannel = (uint16_t *)&aomImage->planes[plane][(j * aomImage->stride[plane]) + (2 * i)];
if (plane == 0) {
*planeChannel = image->planes[3][i + (j * image->strides[plane])];
} else {
*planeChannel = 0;
}
}
}
}
} else {
for (int j = 0; j < image->height; ++j) {
for (int i = 0; i < image->width; ++i) {
for (int plane = 0; plane < 3; ++plane) {
uint16_t * planeChannel = (uint16_t *)&aomImage->planes[plane][(j * aomImage->stride[plane]) + (2 * i)];
*planeChannel = image->planes[plane][i + (j * image->strides[plane])];
}
}
}
}
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;
}
static avifBool avifImageIsOpaque(avifImage * image)
{
int maxChannel = (1 << image->depth) - 1;
for (int j = 0; j < image->height; ++j) {
for (int i = 0; i < image->width; ++i) {
if (image->planes[3][i + (j * image->strides[3])] != maxChannel) {
return AVIF_FALSE;
}
}
}
return AVIF_TRUE;
}