blob: c9b5db935530370f9af5690061db79b962f26648 [file] [log] [blame]
// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause
#include "avif/internal.h"
#include "rav1e.h"
#include <string.h>
struct avifCodecInternal
{
avifCodecConfigurationBox config;
};
static void rav1eCodecDestroyInternal(avifCodec * codec)
{
avifFree(codec->internal);
}
static avifBool rav1eCodecOpen(struct avifCodec * codec, uint32_t firstSampleIndex)
{
(void)firstSampleIndex; // Codec is encode-only, this isn't used
(void)codec;
return AVIF_TRUE;
}
static avifBool rav1eCodecEncodeImage(avifCodec * codec, avifImage * image, avifEncoder * encoder, avifRWData * obu, avifBool alpha)
{
avifBool success = AVIF_FALSE;
RaConfig * rav1eConfig = NULL;
RaContext * rav1eContext = NULL;
RaFrame * rav1eFrame = NULL;
int yShift = 0;
RaChromaSampling chromaSampling;
RaPixelRange rav1eRange;
if (alpha) {
rav1eRange = RA_PIXEL_RANGE_FULL;
chromaSampling = RA_CHROMA_SAMPLING_CS400;
} else {
rav1eRange = (image->yuvRange == AVIF_RANGE_FULL) ? RA_PIXEL_RANGE_FULL : RA_PIXEL_RANGE_LIMITED;
switch (image->yuvFormat) {
case AVIF_PIXEL_FORMAT_YUV444:
chromaSampling = RA_CHROMA_SAMPLING_CS444;
break;
case AVIF_PIXEL_FORMAT_YUV422:
chromaSampling = RA_CHROMA_SAMPLING_CS422;
break;
case AVIF_PIXEL_FORMAT_YUV420:
chromaSampling = RA_CHROMA_SAMPLING_CS420;
yShift = 1;
break;
case AVIF_PIXEL_FORMAT_YV12:
return AVIF_FALSE;
default:
return AVIF_FALSE;
}
}
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) {
goto cleanup;
}
if (rav1e_config_parse_int(rav1eConfig, "width", image->width) == -1) {
goto cleanup;
}
if (rav1e_config_parse_int(rav1eConfig, "height", image->height) == -1) {
goto cleanup;
}
if (rav1e_config_parse_int(rav1eConfig, "threads", encoder->maxThreads) == -1) {
goto cleanup;
}
int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
if (alpha) {
minQuantizer = AVIF_QUANTIZER_LOSSLESS;
maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
}
if (rav1e_config_parse_int(rav1eConfig, "min_quantizer", minQuantizer) == -1) {
goto cleanup;
}
if (rav1e_config_parse_int(rav1eConfig, "quantizer", maxQuantizer) == -1) {
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) {
goto cleanup;
}
}
if (encoder->tileColsLog2 != 0) {
int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
if (rav1e_config_parse_int(rav1eConfig, "tile_cols", 1 << tileColsLog2) == -1) {
goto cleanup;
}
}
if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
rav1e_config_set_color_description(rav1eConfig,
(RaMatrixCoefficients)image->nclx.matrixCoefficients,
(RaColorPrimaries)image->nclx.colourPrimaries,
(RaTransferCharacteristics)image->nclx.transferCharacteristics);
}
rav1eContext = rav1e_context_new(rav1eConfig);
if (!rav1eContext) {
goto cleanup;
}
rav1eFrame = rav1e_frame_new(rav1eContext);
int byteWidth = (image->depth > 8) ? 2 : 1;
if (alpha) {
rav1e_frame_fill_plane(rav1eFrame, 0, image->alphaPlane, image->alphaRowBytes * image->height, image->alphaRowBytes, byteWidth);
} else {
uint32_t uvHeight = image->height >> yShift;
if (uvHeight < 1) {
uvHeight = 1;
}
rav1e_frame_fill_plane(rav1eFrame, 0, image->yuvPlanes[0], image->yuvRowBytes[0] * image->height, image->yuvRowBytes[0], byteWidth);
rav1e_frame_fill_plane(rav1eFrame, 1, image->yuvPlanes[1], image->yuvRowBytes[1] * uvHeight, image->yuvRowBytes[1], byteWidth);
rav1e_frame_fill_plane(rav1eFrame, 2, image->yuvPlanes[2], image->yuvRowBytes[2] * uvHeight, image->yuvRowBytes[2], byteWidth);
}
RaEncoderStatus encoderStatus = rav1e_send_frame(rav1eContext, rav1eFrame);
if (encoderStatus != 0) {
goto cleanup;
}
encoderStatus = rav1e_send_frame(rav1eContext, NULL); // flush
if (encoderStatus != 0) {
goto cleanup;
}
RaPacket * pkt = NULL;
encoderStatus = rav1e_receive_packet(rav1eContext, &pkt);
if (encoderStatus != 0) {
goto cleanup;
}
if (pkt && pkt->data && (pkt->len > 0)) {
avifRWDataSet(obu, pkt->data, pkt->len);
success = AVIF_TRUE;
}
cleanup:
if (rav1eFrame) {
rav1e_frame_unref(rav1eFrame);
rav1eFrame = NULL;
}
if (rav1eContext) {
rav1e_context_unref(rav1eContext);
rav1eContext = NULL;
}
if (rav1eConfig) {
rav1e_config_unref(rav1eConfig);
rav1eConfig = NULL;
}
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
}
avifCodec * avifCodecCreateRav1e(void)
{
avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec));
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));
memset(codec->internal, 0, sizeof(struct avifCodecInternal));
return codec;
}