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