From 9e8ced540c57951f6a14a79e558c02ba10282bd5 Mon Sep 17 00:00:00 2001 From: Greg Benz Date: Thu, 24 Sep 2026 11:30:38 -0500 Subject: [PATCH 1/2] Use faster AOM encoding for the AVIF realtime preset --- lib/include/ultrahdr/avifultrahdr.h | 2 + lib/src/avifencoder.h | 45 ++++++++++ lib/src/avifultrahdr.cpp | 9 +- tests/ultrahdr_api_test.cpp | 135 ++++++++++++++++++++++++++++ ultrahdr_api.h | 3 + 5 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 lib/src/avifencoder.h diff --git a/lib/include/ultrahdr/avifultrahdr.h b/lib/include/ultrahdr/avifultrahdr.h index f5b6f1bd..c8bfebc9 100644 --- a/lib/include/ultrahdr/avifultrahdr.h +++ b/lib/include/ultrahdr/avifultrahdr.h @@ -171,6 +171,8 @@ class AvifUltraHdr : public UltraHdr { DataStruct* alternateIcc); uhdr_codec_t mCodec; + // Preserve the caller's preset; API-0 changes mEncPreset for gain-map generation. + bool mUseRealtimeAomSpeed; }; } // namespace ultrahdr diff --git a/lib/src/avifencoder.h b/lib/src/avifencoder.h new file mode 100644 index 00000000..3c3228be --- /dev/null +++ b/lib/src/avifencoder.h @@ -0,0 +1,45 @@ +#ifndef ULTRAHDR_AVIFENCODER_H +#define ULTRAHDR_AVIFENCODER_H + +#include + +#include "libheif/heif.h" + +namespace ultrahdr { +namespace internal { + +static inline heif_error get_encoder_for_format(heif_context* ctx, + heif_compression_format format, + bool use_realtime_aom_speed, + heif_encoder** encoder) { + if (!use_realtime_aom_speed || format != heif_compression_AV1) { + return heif_context_get_encoder_for_format(ctx, format, encoder); + } + + // The first descriptor is libheif's normal priority-selected encoder for this format. + const heif_encoder_descriptor* descriptor = nullptr; + if (heif_get_encoder_descriptors(format, nullptr, &descriptor, 1) <= 0 || + descriptor == nullptr) { + return heif_context_get_encoder_for_format(ctx, format, encoder); + } + + heif_error error = heif_context_get_encoder(ctx, descriptor, encoder); + if (error.code != heif_error_Ok) return error; + + const char* encoder_id = heif_encoder_descriptor_get_id_name(descriptor); + if (encoder_id == nullptr || std::strcmp(encoder_id, "aom") != 0) return error; + + error = heif_encoder_set_parameter_integer(*encoder, "speed", 8); + if (error.code != heif_error_Ok && + !(error.code == heif_error_Usage_error && + error.subcode == heif_suberror_Unsupported_parameter)) { + return error; + } + + return {heif_error_Ok, heif_suberror_Unspecified, nullptr}; +} + +} // namespace internal +} // namespace ultrahdr + +#endif // ULTRAHDR_AVIFENCODER_H diff --git a/lib/src/avifultrahdr.cpp b/lib/src/avifultrahdr.cpp index 779e95c1..e2df9635 100644 --- a/lib/src/avifultrahdr.cpp +++ b/lib/src/avifultrahdr.cpp @@ -23,6 +23,7 @@ #include #include "ultrahdr/avifultrahdr.h" +#include "avifencoder.h" #include "ultrahdr/gainmapmath.h" #include "ultrahdr/gainmapmetadata.h" @@ -338,7 +339,8 @@ AvifUltraHdr::AvifUltraHdr(void* uhdrGLESCtxt, int mapDimensionScaleFactor, int uhdr_codec_t codec) : UltraHdr(uhdrGLESCtxt, mapDimensionScaleFactor, mapCompressQuality, useMultiChannelGainMap, gamma, preset, minContentBoost, maxContentBoost, targetDispPeakBrightness), - mCodec(codec) {} + mCodec(codec), + mUseRealtimeAomSpeed(preset == UHDR_USAGE_REALTIME) {} /* Encode API-0 */ uhdr_error_info_t AvifUltraHdr::encodeAvifUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest, @@ -464,8 +466,9 @@ uhdr_error_info_t AvifUltraHdr::encodeAvifUltraHdr(uhdr_raw_image_t* sdr_intent, return status; } - HEIF_ERR_CHECK(heif_context_get_encoder_for_format( - ctx, mCodec == UHDR_CODEC_AVIF ? heif_compression_AV1 : heif_compression_HEVC, &encoder)); + HEIF_ERR_CHECK(internal::get_encoder_for_format( + ctx, mCodec == UHDR_CODEC_AVIF ? heif_compression_AV1 : heif_compression_HEVC, + mCodec == UHDR_CODEC_AVIF && mUseRealtimeAomSpeed, &encoder)); // set the encoder parameters HEIF_ERR_CHECK(heif_encoder_set_lossy_quality(encoder, quality)); diff --git a/tests/ultrahdr_api_test.cpp b/tests/ultrahdr_api_test.cpp index f4b2edc9..351351ef 100644 --- a/tests/ultrahdr_api_test.cpp +++ b/tests/ultrahdr_api_test.cpp @@ -21,6 +21,9 @@ #include "ultrahdr/gainmapmath.h" #include "ultrahdr/heifultrahdr.h" #include "ultrahdr/avifultrahdr.h" +#if defined(UHDR_ENABLE_HEIF) +#include "../lib/src/avifencoder.h" +#endif namespace ultrahdr { @@ -658,6 +661,138 @@ TEST_F(UltraHdrApiTest, HeicCompressedIntentsUnsupported) { // AVIF Tests (API-0, API-1, and Unsupported APIs) // ============================================================================ +TEST(AvifEncoderPolicyTest, RealtimeSetsSpeedOnlyForTheSelectedAomEncoder) { + std::unique_ptr context(heif_context_alloc(), + heif_context_free); + ASSERT_NE(context, nullptr); + + const heif_encoder_descriptor* default_descriptor = nullptr; + if (heif_get_encoder_descriptors(heif_compression_AV1, nullptr, &default_descriptor, 1) <= 0 || + default_descriptor == nullptr) { + GTEST_SKIP() << "AV1 encoder plugin not available"; + } + + heif_encoder* default_encoder_raw = nullptr; + ASSERT_EQ(heif_context_get_encoder_for_format(context.get(), heif_compression_AV1, + &default_encoder_raw) + .code, + heif_error_Ok); + std::unique_ptr default_encoder( + default_encoder_raw, heif_encoder_release); + ASSERT_NE(default_encoder, nullptr); + EXPECT_STREQ(heif_encoder_get_name(default_encoder.get()), + heif_encoder_descriptor_get_name(default_descriptor)); + + const char* encoder_id = heif_encoder_descriptor_get_id_name(default_descriptor); + const bool default_is_aom = encoder_id != nullptr && std::strcmp(encoder_id, "aom") == 0; + int default_speed = -1; + if (default_is_aom) { + ASSERT_EQ(heif_encoder_get_parameter_integer(default_encoder.get(), "speed", &default_speed) + .code, + heif_error_Ok); + } + + heif_encoder* realtime_encoder_raw = nullptr; + ASSERT_EQ(internal::get_encoder_for_format(context.get(), heif_compression_AV1, true, + &realtime_encoder_raw) + .code, + heif_error_Ok); + std::unique_ptr realtime_encoder( + realtime_encoder_raw, heif_encoder_release); + ASSERT_NE(realtime_encoder, nullptr); + EXPECT_STREQ(heif_encoder_get_name(realtime_encoder.get()), + heif_encoder_descriptor_get_name(default_descriptor)); + if (default_is_aom) { + int realtime_speed = -1; + ASSERT_EQ(heif_encoder_get_parameter_integer(realtime_encoder.get(), "speed", &realtime_speed) + .code, + heif_error_Ok); + EXPECT_EQ(realtime_speed, 8); + } + + heif_encoder* best_encoder_raw = nullptr; + ASSERT_EQ(internal::get_encoder_for_format(context.get(), heif_compression_AV1, false, + &best_encoder_raw) + .code, + heif_error_Ok); + std::unique_ptr best_encoder( + best_encoder_raw, heif_encoder_release); + ASSERT_NE(best_encoder, nullptr); + EXPECT_STREQ(heif_encoder_get_name(best_encoder.get()), + heif_encoder_descriptor_get_name(default_descriptor)); + if (default_is_aom) { + int best_speed = -1; + ASSERT_EQ(heif_encoder_get_parameter_integer(best_encoder.get(), "speed", &best_speed).code, + heif_error_Ok); + EXPECT_EQ(best_speed, default_speed); + } +} + +TEST_F(UltraHdrApiTest, AvifApi0CallerPresetControlsRealtimeAomSpeed) { + std::unique_ptr context(heif_context_alloc(), + heif_context_free); + ASSERT_NE(context, nullptr); + + const heif_encoder_descriptor* default_descriptor = nullptr; + if (heif_get_encoder_descriptors(heif_compression_AV1, nullptr, &default_descriptor, 1) <= 0 || + default_descriptor == nullptr) { + GTEST_SKIP() << "AV1 encoder plugin not available"; + } + const char* encoder_id = heif_encoder_descriptor_get_id_name(default_descriptor); + if (encoder_id == nullptr || std::strcmp(encoder_id, "aom") != 0) { + GTEST_SKIP() << "libheif's selected AV1 encoder is not AOM"; + } + + auto encode = [this](uhdr_enc_preset_t preset) { + std::vector bytes; + std::unique_ptr encoder( + uhdr_create_encoder(), uhdr_release_encoder); + if (!encoder) { + ADD_FAILURE() << "failed to create encoder"; + return bytes; + } + + auto configure = [](uhdr_error_info_t status) { + if (status.error_code != UHDR_CODEC_OK) { + ADD_FAILURE() << (status.has_detail ? status.detail : "failed to configure encoder"); + return false; + } + return true; + }; + if (!configure(uhdr_enc_set_raw_image(encoder.get(), &mHdrRaw, UHDR_HDR_IMG)) || + !configure(uhdr_enc_set_output_format(encoder.get(), UHDR_CODEC_AVIF)) || + !configure(uhdr_enc_set_preset(encoder.get(), preset)) || + !configure(uhdr_enc_set_quality(encoder.get(), 85, UHDR_BASE_IMG)) || + !configure(uhdr_enc_set_quality(encoder.get(), 85, UHDR_GAIN_MAP_IMG))) { + return bytes; + } + + const uhdr_error_info_t status = uhdr_encode(encoder.get()); + if (status.error_code != UHDR_CODEC_OK) { + ADD_FAILURE() << (status.has_detail ? status.detail : "failed to encode AVIF"); + return bytes; + } + uhdr_compressed_image_t* output = uhdr_get_encoded_stream(encoder.get()); + if (output == nullptr || output->data_sz == 0) { + ADD_FAILURE() << "AVIF encode returned an empty stream"; + return bytes; + } + const auto* output_data = static_cast(output->data); + bytes.assign(output_data, output_data + output->data_sz); + return bytes; + }; + + const std::vector best_stream = encode(UHDR_USAGE_BEST_QUALITY); + const std::vector realtime_stream = encode(UHDR_USAGE_REALTIME); + ASSERT_FALSE(best_stream.empty()); + ASSERT_FALSE(realtime_stream.empty()); + const bool streams_equal = best_stream.size() == realtime_stream.size() && + std::equal(best_stream.begin(), best_stream.end(), + realtime_stream.begin()); + EXPECT_FALSE(streams_equal) + << "API-0's internal realtime gain-map preset must not hide the caller's AVIF preset"; +} + TEST_F(UltraHdrApiTest, AvifEncodeApi0AndDecode) { uhdr_codec_private_t* enc = uhdr_create_encoder(); ASSERT_NE(enc, nullptr); diff --git a/ultrahdr_api.h b/ultrahdr_api.h index 39e529f5..e115e1fe 100644 --- a/ultrahdr_api.h +++ b/ultrahdr_api.h @@ -481,6 +481,9 @@ UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_target_display_peak_brightness(uhdr_c * \param[in] enc encoder instance. * \param[in] preset encoding preset. #UHDR_USAGE_REALTIME - Tune settings for best performance * #UHDR_USAGE_BEST_QUALITY - Tune settings for best quality + * \note For AVIF, the realtime preset sets AOM speed to 8 when libheif selects its AOM encoder. + * Other selected encoders keep their default speed. If AOM does not support this parameter, + * its default speed is used. * * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, * #UHDR_CODEC_INVALID_PARAM otherwise. From d39c977971236d73c45ec4564971b621e13c456d Mon Sep 17 00:00:00 2001 From: Greg Benz Date: Thu, 24 Sep 2026 22:28:14 -0500 Subject: [PATCH 2/2] Simplify AVIF realtime preset regression tests --- tests/ultrahdr_api_test.cpp | 116 +++++++++++++++--------------------- 1 file changed, 47 insertions(+), 69 deletions(-) diff --git a/tests/ultrahdr_api_test.cpp b/tests/ultrahdr_api_test.cpp index 351351ef..ff0945d2 100644 --- a/tests/ultrahdr_api_test.cpp +++ b/tests/ultrahdr_api_test.cpp @@ -692,43 +692,29 @@ TEST(AvifEncoderPolicyTest, RealtimeSetsSpeedOnlyForTheSelectedAomEncoder) { heif_error_Ok); } - heif_encoder* realtime_encoder_raw = nullptr; - ASSERT_EQ(internal::get_encoder_for_format(context.get(), heif_compression_AV1, true, - &realtime_encoder_raw) - .code, - heif_error_Ok); - std::unique_ptr realtime_encoder( - realtime_encoder_raw, heif_encoder_release); - ASSERT_NE(realtime_encoder, nullptr); - EXPECT_STREQ(heif_encoder_get_name(realtime_encoder.get()), - heif_encoder_descriptor_get_name(default_descriptor)); - if (default_is_aom) { - int realtime_speed = -1; - ASSERT_EQ(heif_encoder_get_parameter_integer(realtime_encoder.get(), "speed", &realtime_speed) + for (const bool realtime : {true, false}) { + SCOPED_TRACE(realtime ? "realtime" : "best quality"); + heif_encoder* mode_encoder_raw = nullptr; + ASSERT_EQ(internal::get_encoder_for_format(context.get(), heif_compression_AV1, realtime, + &mode_encoder_raw) .code, heif_error_Ok); - EXPECT_EQ(realtime_speed, 8); - } - - heif_encoder* best_encoder_raw = nullptr; - ASSERT_EQ(internal::get_encoder_for_format(context.get(), heif_compression_AV1, false, - &best_encoder_raw) - .code, - heif_error_Ok); - std::unique_ptr best_encoder( - best_encoder_raw, heif_encoder_release); - ASSERT_NE(best_encoder, nullptr); - EXPECT_STREQ(heif_encoder_get_name(best_encoder.get()), - heif_encoder_descriptor_get_name(default_descriptor)); - if (default_is_aom) { - int best_speed = -1; - ASSERT_EQ(heif_encoder_get_parameter_integer(best_encoder.get(), "speed", &best_speed).code, - heif_error_Ok); - EXPECT_EQ(best_speed, default_speed); + std::unique_ptr mode_encoder( + mode_encoder_raw, heif_encoder_release); + ASSERT_NE(mode_encoder, nullptr); + EXPECT_STREQ(heif_encoder_get_name(mode_encoder.get()), + heif_encoder_descriptor_get_name(default_descriptor)); + if (default_is_aom) { + int mode_speed = -1; + ASSERT_EQ(heif_encoder_get_parameter_integer(mode_encoder.get(), "speed", &mode_speed).code, + heif_error_Ok); + EXPECT_EQ(mode_speed, realtime ? 8 : default_speed); + } } } TEST_F(UltraHdrApiTest, AvifApi0CallerPresetControlsRealtimeAomSpeed) { + // Context allocation initializes libheif plugins before the descriptor query below. std::unique_ptr context(heif_context_alloc(), heif_context_free); ASSERT_NE(context, nullptr); @@ -743,53 +729,45 @@ TEST_F(UltraHdrApiTest, AvifApi0CallerPresetControlsRealtimeAomSpeed) { GTEST_SKIP() << "libheif's selected AV1 encoder is not AOM"; } - auto encode = [this](uhdr_enc_preset_t preset) { - std::vector bytes; + auto encode = [this](uhdr_enc_preset_t preset, std::vector& bytes) { + SCOPED_TRACE(preset == UHDR_USAGE_REALTIME ? "realtime" : "best quality"); std::unique_ptr encoder( uhdr_create_encoder(), uhdr_release_encoder); - if (!encoder) { - ADD_FAILURE() << "failed to create encoder"; - return bytes; - } - - auto configure = [](uhdr_error_info_t status) { - if (status.error_code != UHDR_CODEC_OK) { - ADD_FAILURE() << (status.has_detail ? status.detail : "failed to configure encoder"); - return false; - } - return true; - }; - if (!configure(uhdr_enc_set_raw_image(encoder.get(), &mHdrRaw, UHDR_HDR_IMG)) || - !configure(uhdr_enc_set_output_format(encoder.get(), UHDR_CODEC_AVIF)) || - !configure(uhdr_enc_set_preset(encoder.get(), preset)) || - !configure(uhdr_enc_set_quality(encoder.get(), 85, UHDR_BASE_IMG)) || - !configure(uhdr_enc_set_quality(encoder.get(), 85, UHDR_GAIN_MAP_IMG))) { - return bytes; - } - - const uhdr_error_info_t status = uhdr_encode(encoder.get()); - if (status.error_code != UHDR_CODEC_OK) { - ADD_FAILURE() << (status.has_detail ? status.detail : "failed to encode AVIF"); - return bytes; - } + ASSERT_NE(encoder, nullptr); + + uhdr_error_info_t status = uhdr_enc_set_raw_image(encoder.get(), &mHdrRaw, UHDR_HDR_IMG); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) + << (status.has_detail ? status.detail : "failed to set HDR image"); + status = uhdr_enc_set_output_format(encoder.get(), UHDR_CODEC_AVIF); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) + << (status.has_detail ? status.detail : "failed to set AVIF output format"); + status = uhdr_enc_set_preset(encoder.get(), preset); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) + << (status.has_detail ? status.detail : "failed to set encoder preset"); + status = uhdr_enc_set_quality(encoder.get(), 85, UHDR_BASE_IMG); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) + << (status.has_detail ? status.detail : "failed to set base image quality"); + status = uhdr_enc_set_quality(encoder.get(), 85, UHDR_GAIN_MAP_IMG); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) + << (status.has_detail ? status.detail : "failed to set gain map quality"); + + status = uhdr_encode(encoder.get()); + ASSERT_EQ(status.error_code, UHDR_CODEC_OK) + << (status.has_detail ? status.detail : "failed to encode AVIF"); uhdr_compressed_image_t* output = uhdr_get_encoded_stream(encoder.get()); - if (output == nullptr || output->data_sz == 0) { - ADD_FAILURE() << "AVIF encode returned an empty stream"; - return bytes; - } + ASSERT_NE(output, nullptr); + ASSERT_GT(output->data_sz, 0u); const auto* output_data = static_cast(output->data); bytes.assign(output_data, output_data + output->data_sz); - return bytes; }; - const std::vector best_stream = encode(UHDR_USAGE_BEST_QUALITY); - const std::vector realtime_stream = encode(UHDR_USAGE_REALTIME); + std::vector best_stream; + std::vector realtime_stream; + ASSERT_NO_FATAL_FAILURE(encode(UHDR_USAGE_BEST_QUALITY, best_stream)); + ASSERT_NO_FATAL_FAILURE(encode(UHDR_USAGE_REALTIME, realtime_stream)); ASSERT_FALSE(best_stream.empty()); ASSERT_FALSE(realtime_stream.empty()); - const bool streams_equal = best_stream.size() == realtime_stream.size() && - std::equal(best_stream.begin(), best_stream.end(), - realtime_stream.begin()); - EXPECT_FALSE(streams_equal) + EXPECT_FALSE(best_stream == realtime_stream) << "API-0's internal realtime gain-map preset must not hide the caller's AVIF preset"; }