From 4bf0187b317ac27483d2623bb4b6f43277c3a304 Mon Sep 17 00:00:00 2001 From: Joe Drago Date: Fri, 15 Nov 2019 18:09:19 -0800 Subject: [PATCH] avifEncoder now has a speed setting, codec_aom only flushes encoder when necessary (avoids lost frame packets), minor cleanup --- CHANGELOG.md | 5 +++++ include/avif/avif.h | 17 +++++++++++++---- src/codec_aom.c | 41 +++++++++++++++++++++++++++++++++++++---- src/codec_rav1e.c | 7 ++++++- src/write.c | 8 +++----- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6b603ebb3..8ec642854d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- avifEncoder now has a speed setting +- codec_aom only flushes encoder when necessary (avoids lost frame packets) + ### Changed - Updated libaom to more recent SHA in aom.cmd - Tweaked AVIF_LOCAL_AOM settings to play nice with libaom's usage of CMake's option() +- Minor cleanup ## [0.4.7] - 2019-11-11 ### Changed diff --git a/include/avif/avif.h b/include/avif/avif.h index 2b9ebb6e91..df47abc72d 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -30,6 +30,10 @@ typedef int avifBool; #define AVIF_PLANE_COUNT_RGB 3 #define AVIF_PLANE_COUNT_YUV 3 +#define AVIF_SPEED_DEFAULT -1 +#define AVIF_SPEED_SLOWEST 0 +#define AVIF_SPEED_FASTEST 10 + enum avifPlanesFlags { AVIF_PLANES_RGB = (1 << 0), @@ -480,22 +484,27 @@ uint32_t avifDecoderNearestKeyframe(avifDecoder * decoder, uint32_t frameIndex); // avifEncoder // Notes: -// * if avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree() -// * if (maxThreads < 2), multithreading is disabled -// * quality range: [AVIF_BEST_QUALITY - AVIF_WORST_QUALITY] +// * If avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree() +// * If (maxThreads < 2), multithreading is disabled +// * Quality range: [AVIF_BEST_QUALITY - AVIF_WORST_QUALITY] // * To enable tiling, set tileRowsLog2 > 0 and/or tileColsLog2 > 0. // Tiling values range [0-6], where the value indicates a request for 2^n tiles in that dimension. +// * Speed range: [AVIF_SPEED_SLOWEST - AVIF_SPEED_FASTEST]. Slower should make for a better quality +// image in less bytes. AVIF_SPEED_DEFAULT means "Leave the AV1 codec to its default speed settings"./ +// If avifEncoder uses rav1e, the speed value is directly passed through (0-10). If libaom is used, +// a combination of settings are tweaked to simulate this speed range. typedef struct avifEncoder { // Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c) avifCodecChoice codecChoice; - // settings + // settings (see Notes above) int maxThreads; int minQuantizer; int maxQuantizer; int tileRowsLog2; int tileColsLog2; + int speed; // stats from the most recent write avifIOStats ioStats; diff --git a/src/codec_aom.c b/src/codec_aom.c index fee504f87c..790f57f2fe 100644 --- a/src/codec_aom.c +++ b/src/codec_aom.c @@ -234,6 +234,30 @@ static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEn aom_codec_iface_t * encoder_interface = aom_codec_av1_cx(); aom_codec_ctx_t aomEncoder; + // Map encoder speed to AOM usage + CpuUsed: + // Speed 0: GoodQuality CpuUsed 0 + // Speed 1: GoodQuality CpuUsed 1 + // Speed 2: GoodQuality CpuUsed 2 + // Speed 3: GoodQuality CpuUsed 3 + // Speed 4: GoodQuality CpuUsed 4 + // Speed 5: GoodQuality CpuUsed 5 + // Speed 6: RealTime CpuUsed 4 + // Speed 7: RealTime CpuUsed 5 + // Speed 8: RealTime CpuUsed 6 + // Speed 9: RealTime CpuUsed 7 + // Speed 10: RealTime CpuUsed 8 + unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY; + int aomCpuUsed = -1; + if (encoder->speed != AVIF_SPEED_DEFAULT) { + if (encoder->speed < 6) { + aomUsage = AOM_USAGE_GOOD_QUALITY; + aomCpuUsed = AVIF_CLAMP(encoder->speed, 0, 5); + } else { + aomUsage = AOM_USAGE_REALTIME; + aomCpuUsed = AVIF_CLAMP(encoder->speed - 2, 4, 8); + } + } + int yShift = 0; aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alpha, &yShift); if (aomFormat == AOM_IMG_FMT_NONE) { @@ -244,7 +268,7 @@ static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEn avifGetPixelFormatInfo(image->yuvFormat, &formatInfo); struct aom_codec_enc_cfg cfg; - aom_codec_enc_config_default(encoder_interface, &cfg, 0); + aom_codec_enc_config_default(encoder_interface, &cfg, aomUsage); cfg.g_profile = codec->configBox.seqProfile; cfg.g_bit_depth = image->depth; @@ -287,6 +311,9 @@ static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEn int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6); aom_codec_control(&aomEncoder, AV1E_SET_TILE_COLUMNS, tileColsLog2); } + if (aomCpuUsed != -1) { + aom_codec_control(&aomEncoder, AOME_SET_CPUUSED, aomCpuUsed); + } uint32_t uvHeight = image->height >> yShift; aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16); @@ -338,13 +365,19 @@ static avifBool aomCodecEncodeImage(avifCodec * codec, avifImage * image, avifEn } aom_codec_encode(&aomEncoder, aomImage, 0, 1, 0); - aom_codec_encode(&aomEncoder, NULL, 0, 1, 0); // flush + avifBool flushed = AVIF_FALSE; aom_codec_iter_t iter = NULL; for (;;) { const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&aomEncoder, &iter); - if (pkt == NULL) - break; + if (pkt == NULL) { + if (flushed) + break; + + aom_codec_encode(&aomEncoder, NULL, 0, 1, 0); // flush + flushed = AVIF_TRUE; + continue; + } if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { avifRWDataSet(obu, pkt->data.frame.buf, pkt->data.frame.sz); success = AVIF_TRUE; diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c index 920fbc2592..adaa2e5473 100644 --- a/src/codec_rav1e.c +++ b/src/codec_rav1e.c @@ -91,7 +91,6 @@ static avifBool rav1eCodecEncodeImage(avifCodec * codec, avifImage * image, avif 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) { @@ -104,6 +103,12 @@ static avifBool rav1eCodecEncodeImage(avifCodec * codec, avifImage * image, avif 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, diff --git a/src/write.c b/src/write.c index b9c64478ec..35a11cefe7 100644 --- a/src/write.c +++ b/src/write.c @@ -31,6 +31,9 @@ avifEncoder * avifEncoderCreate(void) encoder->maxThreads = 1; encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS; encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; + encoder->tileRowsLog2 = 0; + encoder->tileColsLog2 = 0; + encoder->speed = AVIF_SPEED_DEFAULT; return encoder; } @@ -106,11 +109,6 @@ avifResult avifEncoderWrite(avifEncoder * encoder, avifImage * image, avifRWData // ----------------------------------------------------------------------- // Encode AV1 OBUs - // avifRWData * alphaOBUPtr = &alphaOBU; - // if (avifImageIsOpaque(image)) { - // alphaOBUPtr = NULL; - // } - if (!codec[AVIF_CODEC_PLANES_COLOR]->encodeImage(codec[AVIF_CODEC_PLANES_COLOR], image, encoder, &colorOBU, AVIF_FALSE)) { result = AVIF_RESULT_ENCODE_COLOR_FAILED; goto writeCleanup;