| // Copyright 2019 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include "avif/internal.h" |
| |
| #include "rav1e/rav1e.h" |
| |
| #include <string.h> |
| |
| struct avifCodecInternal |
| { |
| uint32_t unused; // rav1e codec has no state |
| }; |
| |
| 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) |
| { |
| (void)codec; // unused |
| |
| avifBool success = AVIF_FALSE; |
| |
| RaConfig * rav1eConfig = NULL; |
| RaContext * rav1eContext = NULL; |
| RaFrame * rav1eFrame = NULL; |
| RaPacket * pkt = NULL; |
| |
| int yShift = 0; |
| RaChromaSampling chromaSampling; |
| RaPixelRange rav1eRange; |
| if (alpha) { |
| rav1eRange = RA_PIXEL_RANGE_FULL; |
| chromaSampling = RA_CHROMA_SAMPLING_CS422; // I can't seem to get RA_CHROMA_SAMPLING_CS400 to work right now, unfortunately |
| } 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; |
| } |
| } |
| |
| 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; |
| } |
| minQuantizer = (minQuantizer * 255) / 63; // Rescale quantizer values as rav1e's QP range is [0,255] |
| maxQuantizer = (maxQuantizer * 255) / 63; |
| if (rav1e_config_parse_int(rav1eConfig, "min_quantizer", minQuantizer) == -1) { |
| goto cleanup; |
| } |
| if (rav1e_config_parse_int(rav1eConfig, "quantizer", maxQuantizer) == -1) { |
| goto cleanup; |
| } |
| 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 (encoder->speed != AVIF_SPEED_DEFAULT) { |
| int speed = AVIF_CLAMP(encoder->speed, 0, 10); |
| if (rav1e_config_parse_int(rav1eConfig, "speed", speed) == -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; |
| } |
| |
| 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 (pkt) { |
| rav1e_packet_unref(pkt); |
| pkt = NULL; |
| } |
| 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; |
| } |
| |
| const char * avifCodecVersionRav1e(void) |
| { |
| return rav1e_version_full(); |
| } |
| |
| avifCodec * avifCodecCreateRav1e(void) |
| { |
| avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec)); |
| memset(codec, 0, sizeof(struct avifCodec)); |
| codec->open = rav1eCodecOpen; |
| codec->encodeImage = rav1eCodecEncodeImage; |
| codec->destroyInternal = rav1eCodecDestroyInternal; |
| |
| codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal)); |
| memset(codec->internal, 0, sizeof(struct avifCodecInternal)); |
| return codec; |
| } |