@@ -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.
0 commit comments