Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/include/ultrahdr/avifultrahdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions lib/src/avifencoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#ifndef ULTRAHDR_AVIFENCODER_H
#define ULTRAHDR_AVIFENCODER_H

#include <cstring>

#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
9 changes: 6 additions & 3 deletions lib/src/avifultrahdr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <map>

#include "ultrahdr/avifultrahdr.h"
#include "avifencoder.h"
#include "ultrahdr/gainmapmath.h"
#include "ultrahdr/gainmapmetadata.h"

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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));
Expand Down
111 changes: 111 additions & 0 deletions tests/ultrahdr_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "ultrahdr/avifultrahdr.h"
#if defined(UHDR_ENABLE_HEIF)
#include "libheif/heif.h"
#include "../lib/src/avifencoder.h"
#endif

namespace ultrahdr {
Expand Down Expand Up @@ -1210,6 +1211,116 @@ TEST_F(UltraHdrApiTest, HeicCompressedIntentsUnsupported) {
// AVIF Tests (API-0, API-1, and Unsupported APIs)
// ============================================================================

TEST(AvifEncoderPolicyTest, RealtimeSetsSpeedOnlyForTheSelectedAomEncoder) {
std::unique_ptr<heif_context, decltype(&heif_context_free)> 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<heif_encoder, decltype(&heif_encoder_release)> 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);
}

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);
std::unique_ptr<heif_encoder, decltype(&heif_encoder_release)> 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<heif_context, decltype(&heif_context_free)> 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<uint8_t>& bytes) {
SCOPED_TRACE(preset == UHDR_USAGE_REALTIME ? "realtime" : "best quality");
std::unique_ptr<uhdr_codec_private_t, decltype(&uhdr_release_encoder)> encoder(
uhdr_create_encoder(), uhdr_release_encoder);
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());
ASSERT_NE(output, nullptr);
ASSERT_GT(output->data_sz, 0u);
const auto* output_data = static_cast<const uint8_t*>(output->data);
bytes.assign(output_data, output_data + output->data_sz);
};

std::vector<uint8_t> best_stream;
std::vector<uint8_t> 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());
EXPECT_FALSE(best_stream == realtime_stream)
<< "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);
Expand Down
3 changes: 3 additions & 0 deletions ultrahdr_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading