Skip to content

Commit 6064c35

Browse files
juliobbvwantehchang
authored andcommitted
Remember tune=iq option across frames (AOMediaCodec#2997)
1 parent 2340467 commit 6064c35

2 files changed

Lines changed: 73 additions & 18 deletions

File tree

src/codec_aom.c

Lines changed: 42 additions & 18 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 previousFrameUsedTuneIq;
7273
#endif
7374
};
7475

@@ -403,16 +404,21 @@ 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 useLibavifDefaultTuneMetric, aom_tune_metric libavifDefaultTuneMetric)
407408
{
408-
avifBool ret = AVIF_FALSE;
409-
410409
#if !defined(AOM_HAVE_TUNE_IQ)
411410
// Define the tune IQ value here if libaom doesn't define it. The enum value is guaranteed to never change
412411
// in libaom, so this definition won't ever get out of sync.
413412
#define AOM_TUNE_IQ 10
414413
#endif
415414

415+
if (useLibavifDefaultTuneMetric) {
416+
return libavifDefaultTuneMetric == AOM_TUNE_IQ;
417+
}
418+
419+
avifBool useTuneIq = AVIF_FALSE;
420+
avifBool isAnyTuneDefined = AVIF_FALSE;
421+
416422
// Tune IQ string -> enum mapping
417423
static const struct aomOptionEnumList tuneIqEnum[] = { { "iq", AOM_TUNE_IQ }, // Image Quality (IQ) mode
418424
{ NULL, 0 } };
@@ -423,16 +429,24 @@ static avifBool avifImageUsesTuneIq(const avifCodec * codec, avifBool alpha)
423429
// If there are multiple "tune" options specified, honor the last one.
424430
// For consistent behavior, handle both cases where tune IQ was either specified as a string (tune=iq),
425431
// 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);
432+
if (avifKeyEqualsName(entry->key, "tune", alpha)) {
433+
isAnyTuneDefined = AVIF_TRUE;
434+
435+
if (aomOptionParseEnum(entry->value, tuneIqEnum, &val)) {
436+
useTuneIq = (val == AOM_TUNE_IQ);
437+
}
428438
}
429439
}
430440

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;
441+
if (!isAnyTuneDefined && codec->internal->previousFrameUsedTuneIq) {
442+
// Handle the case where the encoder was called with avifEncoderSetCodecSpecificOption("tune", "iq")
443+
// for a previous frame and not called (or called with NULL) for this frame, because the tune
444+
// option persists across frames in libaom.
445+
// In this case, we know libaom will also use tune=iq for this frame.
446+
return AVIF_TRUE;
447+
}
448+
449+
return useTuneIq;
436450
}
437451

438452
#if !defined(HAVE_AOM_CODEC_SET_OPTION)
@@ -725,23 +739,33 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
725739

726740
avifBool useLibavifDefaultTuneMetric = AVIF_FALSE; // If true, override libaom's default tune option.
727741
aom_tune_metric libavifDefaultTuneMetric = AOM_TUNE_PSNR; // Meaningless unless useLibavifDefaultTuneMetric.
728-
if (quality != AVIF_QUALITY_LOSSLESS && !avifAOMOptionsContainExplicitTuning(codec, alpha)) {
729-
useLibavifDefaultTuneMetric = AVIF_TRUE;
730-
if (alpha) {
731-
// Minimize ringing for alpha.
732-
libavifDefaultTuneMetric = AOM_TUNE_PSNR;
733-
} else {
734-
libavifDefaultTuneMetric = AOM_TUNE_SSIM;
742+
743+
// libavif only needs to set the default tune metric for the first frame,
744+
// because libaom will persist that setting until explicitly changed.
745+
if (!codec->internal->encoderInitialized) {
746+
if (quality != AVIF_QUALITY_LOSSLESS && !avifAOMOptionsContainExplicitTuning(codec, alpha)) {
747+
useLibavifDefaultTuneMetric = AVIF_TRUE;
748+
if (alpha) {
749+
// Minimize ringing for alpha.
750+
libavifDefaultTuneMetric = AOM_TUNE_PSNR;
751+
} else {
752+
libavifDefaultTuneMetric = AOM_TUNE_SSIM;
753+
}
735754
}
755+
AVIF_ASSERT_OR_RETURN(!codec->internal->previousFrameUsedTuneIq);
736756
}
737757

738758
struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;
739759
avifBool quantizerUpdated = AVIF_FALSE;
740760
// True if libavif knows that tune=iq is used, either by default by libavif, or explicitly set by the user.
741761
// 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);
762+
const avifBool useTuneIq = avifImageUsesTuneIq(codec, alpha, useLibavifDefaultTuneMetric, libavifDefaultTuneMetric);
743763
const int quantizer = aomQualityToQuantizer(quality, useTuneIq);
744764

765+
// libavif needs to know whether the current frame uses tune=iq for the next frame, as libaom persists
766+
// tuning modes across frames
767+
codec->internal->previousFrameUsedTuneIq = useTuneIq;
768+
745769
// For encoder->scalingMode.horizontal and encoder->scalingMode.vertical to take effect in AOM
746770
// encoder, config should be applied for each frame, so we don't care about changes on these
747771
// 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)