Skip to content

Commit d07e496

Browse files
committed
Remember tune=iq option across frames (#2997)
1 parent c848cde commit d07e496

2 files changed

Lines changed: 61 additions & 10 deletions

File tree

src/codec_aom.c

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ struct avifCodecInternal
6969
aom_img_fmt_t aomFormat;
7070
uint32_t currentLayer;
7171
int qualityFirstLayer;
72+
avifBool previousLayerUsedTuneIq;
7273
#endif
7374
};
7475

@@ -403,9 +404,10 @@ static avifBool avifProcessAOMOptionsPreInit(avifCodec * codec, avifBool alpha,
403404
return AVIF_TRUE;
404405
}
405406

406-
static avifBool avifImageUsesTuneIq(const avifCodec * codec, avifBool alpha)
407+
static avifBool avifImageUsesTuneIq(const avifCodec * codec, avifBool alpha, avifBool isFirstLayer)
407408
{
408-
avifBool ret = AVIF_FALSE;
409+
avifBool useTuneIq = AVIF_FALSE;
410+
avifBool isAnyTuneDefined = AVIF_FALSE;
409411

410412
#if !defined(AOM_HAVE_TUNE_IQ)
411413
// Define the tune IQ value here if libaom doesn't define it. The enum value is guaranteed to never change
@@ -423,16 +425,27 @@ static avifBool avifImageUsesTuneIq(const avifCodec * codec, avifBool alpha)
423425
// If there are multiple "tune" options specified, honor the last one.
424426
// For consistent behavior, handle both cases where tune IQ was either specified as a string (tune=iq),
425427
// or as an enum value (tune=10).
426-
if (avifKeyEqualsName(entry->key, "tune", alpha) && aomOptionParseEnum(entry->value, tuneIqEnum, &val)) {
427-
ret = (val == AOM_TUNE_IQ);
428+
if (avifKeyEqualsName(entry->key, "tune", alpha)) {
429+
// Check whether any tuning mode has been defined, excluding the { "tune", NULL } case
430+
isAnyTuneDefined = entry->value != NULL;
431+
432+
if (aomOptionParseEnum(entry->value, tuneIqEnum, &val)) {
433+
useTuneIq = (val == AOM_TUNE_IQ);
434+
} else {
435+
useTuneIq = AVIF_FALSE;
436+
}
428437
}
429438
}
430439

431-
// In practice this function should also return true if avifEncoderSetCodecSpecificOption("tune", "iq")
432-
// was called for a previous frame and not called (or called with NULL) for this frame, because the tune
433-
// option persists across frames in libaom. However AOM_TUNE_IQ is only supported with still images in
434-
// libavif and libaom as of today, so there is no need to remember this option across frames.
435-
return ret;
440+
if (!isAnyTuneDefined && !isFirstLayer && codec->internal->previousLayerUsedTuneIq) {
441+
// Handle the case where the encoder was called with avifEncoderSetCodecSpecificOption("tune", "iq")
442+
// for a previous layer (frame) and not called (or called with NULL) for this frame, because the tune
443+
// option persists across frames in libaom.
444+
// In this case, we know libaom will also use tune=iq for this frame.
445+
useTuneIq = AVIF_TRUE;
446+
}
447+
448+
return useTuneIq;
436449
}
437450

438451
#if !defined(HAVE_AOM_CODEC_SET_OPTION)
@@ -739,9 +752,16 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
739752
avifBool quantizerUpdated = AVIF_FALSE;
740753
// True if libavif knows that tune=iq is used, either by default by libavif, or explicitly set by the user.
741754
// False otherwise (including if libaom uses tune=iq by default, which is not the case as of v3.13.1 and earlier versions).
742-
const avifBool useTuneIq = useLibavifDefaultTuneMetric ? libavifDefaultTuneMetric == AOM_TUNE_IQ : avifImageUsesTuneIq(codec, alpha);
755+
const avifBool useTuneIq = useLibavifDefaultTuneMetric ? libavifDefaultTuneMetric == AOM_TUNE_IQ
756+
: avifImageUsesTuneIq(codec, alpha, codec->internal->currentLayer == 0);
743757
const int quantizer = aomQualityToQuantizer(quality, useTuneIq);
744758

759+
if (!alpha && encoder->extraLayerCount > 0) {
760+
// We need to know whether the current layer uses tune=iq for the next layer, as libaom persists
761+
// tuning modes across frames
762+
codec->internal->previousLayerUsedTuneIq = useTuneIq;
763+
}
764+
745765
// For encoder->scalingMode.horizontal and encoder->scalingMode.vertical to take effect in AOM
746766
// encoder, config should be applied for each frame, so we don't care about changes on these
747767
// two fields.

tests/gtest/avifprogressivetest.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,37 @@ TEST_F(ProgressiveTest, QualityChange) {
8787
TestDecode(kImageSize, kImageSize);
8888
}
8989

90+
// NOTE: This test requires libaom v3.12.0 or later, as this was the first
91+
// version where tune IQ was available
92+
TEST_F(ProgressiveTest, TuneIq) {
93+
encoder_->extraLayerCount = 1;
94+
// Tune IQ requires all-intra mode, which libavif determines when the first
95+
// layer is encoded at a very low quality (e.g. quality 10)
96+
encoder_->quality = 10;
97+
encoder_->codecChoice = AVIF_CODEC_CHOICE_AOM;
98+
99+
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder_.get(), "tune", "iq"),
100+
AVIF_RESULT_OK);
101+
avifResult result = avifEncoderAddImage(encoder_.get(), image_.get(), 1,
102+
AVIF_ADD_IMAGE_FLAG_NONE);
103+
104+
if (result == AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION) {
105+
// The aom version that libavif was built with likely does not support
106+
// AOM_TUNE_IQ.
107+
return;
108+
}
109+
110+
ASSERT_EQ(result, AVIF_RESULT_OK);
111+
encoder_->quality = 50;
112+
ASSERT_EQ(avifEncoderAddImage(encoder_.get(), image_.get(), 1,
113+
AVIF_ADD_IMAGE_FLAG_NONE),
114+
AVIF_RESULT_OK);
115+
116+
ASSERT_EQ(avifEncoderFinish(encoder_.get(), &encoded_avif_), AVIF_RESULT_OK);
117+
118+
TestDecode(kImageSize, kImageSize);
119+
}
120+
90121
// NOTE: This test requires libaom v3.6.0 or later, otherwise the following
91122
// assertion in libaom fails:
92123
// av1/encoder/mcomp.c:1717: av1_full_pixel_search: Assertion

0 commit comments

Comments
 (0)