Skip to content

Commit 7bbff15

Browse files
committed
Move default tune setting earlier in codec_aom.c
1 parent 62cac9d commit 7bbff15

2 files changed

Lines changed: 142 additions & 6 deletions

File tree

src/codec_aom.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,10 @@ static avifBool avifImageUsesTuneIq(const avifCodec * codec, avifBool alpha)
420420
}
421421
}
422422

423+
// In practice this function should also return true if avifEncoderSetCodecSpecificOption("tune", "iq")
424+
// was called for a previous frame and not called (or called with NULL) for this frame, because the tune
425+
// option persists across frames in libaom. However AOM_TUNE_IQ is only supported with still images in
426+
// libavif and libaom as of today, so there is no need to remember this option across frames.
423427
return ret;
424428
}
425429

@@ -648,10 +652,21 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
648652
avifAddImageFlags addImageFlags,
649653
avifCodecEncodeOutput * output)
650654
{
655+
avifBool useLibavifDefaultTuneMetric = AVIF_FALSE; // If true, override libaom's default tune option.
656+
aom_tune_metric libavifDefaultTuneMetric = AOM_TUNE_PSNR; // Meaningless unless useLibavifDefaultTuneMetric.
657+
if (quality != AVIF_QUALITY_LOSSLESS && !avifAOMOptionsContainExplicitTuning(codec, alpha)) {
658+
useLibavifDefaultTuneMetric = AVIF_TRUE;
659+
libavifDefaultTuneMetric = AOM_TUNE_SSIM;
660+
}
661+
651662
struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;
652663
avifBool quantizerUpdated = AVIF_FALSE;
653-
const avifBool tuneIq = avifImageUsesTuneIq(codec, alpha);
654-
const int quantizer = aomQualityToQuantizer(quality, tuneIq);
664+
// True if libavif knows that tune=iq is used, either by default by libavif, or explicitly set by the user.
665+
// False otherwise (including if libaom uses tune=iq by default, which is not the case as of v1.13.1 and earlier versions).
666+
// This is only accurate for the first frame but tune=iq is only supported for still images in libavif and
667+
// for all-intra coding in libaom (at least up to v1.13.1) anyway.
668+
const avifBool useTuneIq = useLibavifDefaultTuneMetric ? libavifDefaultTuneMetric == AOM_TUNE_IQ : avifImageUsesTuneIq(codec, alpha);
669+
const int quantizer = aomQualityToQuantizer(quality, useTuneIq);
655670

656671
// For encoder->scalingMode.horizontal and encoder->scalingMode.vertical to take effect in AOM
657672
// encoder, config should be applied for each frame, so we don't care about changes on these
@@ -990,10 +1005,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
9901005
}
9911006
#endif
9921007

993-
if (!avifAOMOptionsContainExplicitTuning(codec, alpha)) {
994-
if (aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, AOM_TUNE_SSIM) != AOM_CODEC_OK) {
995-
return AVIF_RESULT_UNKNOWN_ERROR;
996-
}
1008+
if (useLibavifDefaultTuneMetric &&
1009+
aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, libavifDefaultTuneMetric) != AOM_CODEC_OK) {
1010+
return AVIF_RESULT_UNKNOWN_ERROR;
9971011
}
9981012
if (!avifProcessAOMOptionsPostInit(codec, alpha)) {
9991013
return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
@@ -1074,6 +1088,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
10741088
aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
10751089
}
10761090
if (encoderChanges & AVIF_ENCODER_CHANGE_CODEC_SPECIFIC) {
1091+
// Do not apply libavifDefaultTuneMetric even if useLibavifDefaultTuneMetric is true:
1092+
// codec-specific settings persist on the libaom side, so keep the same behavior for tune.
10771093
if (!avifProcessAOMOptionsPostInit(codec, alpha)) {
10781094
return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
10791095
}

tests/gtest/aviftunetest.cc

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,126 @@ TEST(AomSharpnessTest, GenerateDifferentBitstreams) {
112112
}
113113
}
114114

115+
constexpr uint64_t kDuration = 1;
116+
117+
void EncodeAnimation(const char* key, const char* value_before_first_frame,
118+
const char* value_after_first_frame,
119+
const char* value_before_second_frame,
120+
std::vector<uint8_t>& encoded_bitstream) {
121+
// Generate an animation with two different frames.
122+
const ImagePtr first_frame =
123+
testutil::ReadImage(data_path, "paris_exif_xmp_icc.jpg");
124+
ASSERT_NE(first_frame, nullptr);
125+
// Speed up the test.
126+
first_frame->width = 64;
127+
first_frame->height = 64;
128+
const ImagePtr second_frame(avifImageCreateEmpty());
129+
ASSERT_NE(second_frame, nullptr);
130+
ASSERT_EQ(
131+
avifImageCopy(second_frame.get(), first_frame.get(), AVIF_PLANES_ALL),
132+
AVIF_RESULT_OK);
133+
testutil::FillImageGradient(first_frame.get());
134+
135+
EncoderPtr encoder(avifEncoderCreate());
136+
ASSERT_NE(encoder, nullptr);
137+
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
138+
encoder->creationTime = encoder->modificationTime = 1; // Deterministic.
139+
const avifAddImageFlag flag = AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
140+
141+
// First frame.
142+
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), key,
143+
value_before_first_frame),
144+
AVIF_RESULT_OK);
145+
ASSERT_EQ(
146+
avifEncoderAddImage(encoder.get(), first_frame.get(), kDuration, flag),
147+
AVIF_RESULT_OK);
148+
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), key,
149+
value_after_first_frame),
150+
AVIF_RESULT_OK);
151+
152+
// Second frame.
153+
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), key,
154+
value_before_second_frame),
155+
AVIF_RESULT_OK);
156+
ASSERT_EQ(
157+
avifEncoderAddImage(encoder.get(), second_frame.get(), kDuration, flag),
158+
AVIF_RESULT_OK);
159+
160+
testutil::AvifRwData encoded;
161+
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encoded), AVIF_RESULT_OK);
162+
163+
// Make sure it decodes fine, even if unrelated to the current test.
164+
DecoderPtr decoder(avifDecoderCreate());
165+
ASSERT_NE(decoder, nullptr);
166+
ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size),
167+
AVIF_RESULT_OK);
168+
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
169+
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
170+
ASSERT_GT(testutil::GetPsnr(*first_frame, *decoder->image), 32.0);
171+
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
172+
ASSERT_GT(testutil::GetPsnr(*second_frame, *decoder->image), 32.0);
173+
174+
encoded_bitstream = std::vector(encoded.data, encoded.data + encoded.size);
175+
}
176+
177+
TEST(AomTuneMetricTest, TuneOptionHasSameBehaviorAsOtherCodecSpecificOptions) {
178+
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
179+
nullptr) {
180+
GTEST_SKIP() << "Codec unavailable, skip test.";
181+
}
182+
std::vector<uint8_t> a, b;
183+
184+
EncodeAnimation("tune", nullptr, nullptr, nullptr, a);
185+
EncodeAnimation("tune", nullptr, nullptr, nullptr, b);
186+
// Make sure the comparison works as intended for identical input.
187+
EXPECT_EQ(a, b);
188+
189+
EncodeAnimation("tune", "psnr", nullptr, nullptr, a);
190+
EncodeAnimation("tune", nullptr, nullptr, nullptr, b);
191+
// AOM_TUNE_PSNR is not the default.
192+
EXPECT_NE(a, b);
193+
194+
EncodeAnimation("tune", nullptr, nullptr, nullptr, a);
195+
EncodeAnimation("tune", nullptr, nullptr, "psnr", b);
196+
// The second frame differs.
197+
EXPECT_NE(a, b);
198+
199+
EncodeAnimation("tune", nullptr, "ssim", "psnr", a);
200+
EncodeAnimation("tune", nullptr, nullptr, "psnr", b);
201+
// The option is overwritten successfully.
202+
EXPECT_EQ(a, b);
203+
204+
EncodeAnimation("tune", nullptr, nullptr, nullptr, a);
205+
EncodeAnimation("tune", nullptr, "psnr", nullptr, b);
206+
// The pending key is successfully deleted.
207+
EXPECT_EQ(a, b);
208+
209+
EncodeAnimation("tune", "psnr", "psnr", "psnr", a);
210+
EncodeAnimation("tune", "psnr", nullptr, nullptr, b);
211+
// avifEncoderSetCodecSpecificOption(NULL) only deletes the *pending* key.
212+
EXPECT_EQ(a, b);
213+
}
214+
215+
TEST(AomTuneMetricTest, TuneIqOnlySupportsAllIntra) {
216+
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
217+
nullptr) {
218+
GTEST_SKIP() << "Codec unavailable, skip test.";
219+
}
220+
221+
const ImagePtr image =
222+
testutil::ReadImage(data_path, "paris_exif_xmp_icc.jpg");
223+
ASSERT_NE(image, nullptr);
224+
225+
EncoderPtr encoder(avifEncoderCreate());
226+
ASSERT_NE(encoder, nullptr);
227+
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
228+
ASSERT_EQ(avifEncoderSetCodecSpecificOption(encoder.get(), "tune", "iq"),
229+
AVIF_RESULT_OK);
230+
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), kDuration,
231+
AVIF_ADD_IMAGE_FLAG_NONE),
232+
AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION);
233+
}
234+
115235
//------------------------------------------------------------------------------
116236

117237
} // namespace

0 commit comments

Comments
 (0)