Correctly update encoder settings, clear csOptions after avifEncoderAddImage()
Only allow quantizer and tileRows/Cols to change.
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 9b34159..08a1d50 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -1058,8 +1058,8 @@
// image in less bytes. AVIF_SPEED_DEFAULT means "Leave the AV1 codec to its default speed settings"./
// If avifEncoder uses rav1e, the speed value is directly passed through (0-10). If libaom is used,
// a combination of settings are tweaked to simulate this speed range.
-// * AV1 encoder settings and codec specific options set by avifEncoderSetCodecSpecificOption()
-// will be applied / updated to AV1 encoder before each call to avifEncoderAddImage().
+// * Some encoder settings can be changed after encoding starts. Changes will take effect in the next
+// call to avifEncoderAddImage().
typedef struct avifEncoder
{
// Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c)
@@ -1068,15 +1068,15 @@
// settings (see Notes above)
int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default).
uint64_t timescale; // timescale of the media (Hz)
- // AV1 encoder settings.
int maxThreads;
+ int speed;
+ // changeable encoder settings.
int minQuantizer;
int maxQuantizer;
int minQuantizerAlpha;
int maxQuantizerAlpha;
int tileRowsLog2;
int tileColsLog2;
- int speed;
// stats from the most recent write
avifIOStats ioStats;
@@ -1128,8 +1128,10 @@
avifAddImageFlags addImageFlags);
AVIF_API avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output);
-// Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs.
-// key must be non-NULL, but passing a NULL value will delete that key, if it exists.
+// Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs,
+// to be consumed by the codec at the next avifEncoderAddImage() call.
+// See the codec documentation to know if a setting is persistent or applied only to the next frame.
+// key must be non-NULL, but passing a NULL value will delete the pending key, if it exists.
// Setting an incorrect or unknown option for the current codec will cause errors of type
// AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION from avifEncoderWrite() or avifEncoderAddImage().
AVIF_API void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value);
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 8b1ef22..da650ae 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -250,6 +250,7 @@
} avifCodecSpecificOption;
AVIF_ARRAY_DECLARE(avifCodecSpecificOptions, avifCodecSpecificOption, entries);
avifCodecSpecificOptions * avifCodecSpecificOptionsCreate(void);
+void avifCodecSpecificOptionsClear(avifCodecSpecificOptions * csOptions);
void avifCodecSpecificOptionsDestroy(avifCodecSpecificOptions * csOptions);
void avifCodecSpecificOptionsSet(avifCodecSpecificOptions * csOptions, const char * key, const char * value); // if(value==NULL), key is deleted
@@ -259,6 +260,19 @@
struct avifCodec;
struct avifCodecInternal;
+typedef enum avifEncoderChange
+{
+ AVIF_ENCODER_CHANGE_MIN_QUANTIZER = (1 << 0),
+ AVIF_ENCODER_CHANGE_MAX_QUANTIZER = (1 << 1),
+ AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA = (1 << 2),
+ AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA = (1 << 3),
+ AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2 = (1 << 4),
+ AVIF_ENCODER_CHANGE_TILE_COLS_LOG2 = (1 << 5),
+
+ AVIF_ENCODER_CHANGE_CODEC_SPECIFIC = (1 << 31)
+} avifEncoderChange;
+typedef uint32_t avifEncoderChanges;
+
typedef avifBool (*avifCodecGetNextImageFunc)(struct avifCodec * codec,
struct avifDecoder * decoder,
const avifDecodeSample * sample,
@@ -273,7 +287,7 @@
avifEncoder * encoder,
const avifImage * image,
avifBool alpha,
- avifBool updateConfig,
+ avifEncoderChanges encoderChanges,
avifAddImageFlags addImageFlags,
avifCodecEncodeOutput * output);
typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output);
diff --git a/src/avif.c b/src/avif.c
index 81f2889..fd4a9ed 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -878,17 +878,24 @@
return NULL;
}
+void avifCodecSpecificOptionsClear(avifCodecSpecificOptions * csOptions)
+{
+ for (uint32_t i = 0; i < csOptions->count; ++i) {
+ avifCodecSpecificOption * entry = &csOptions->entries[i];
+ avifFree(entry->key);
+ avifFree(entry->value);
+ }
+
+ csOptions->count = 0;
+}
+
void avifCodecSpecificOptionsDestroy(avifCodecSpecificOptions * csOptions)
{
if (!csOptions) {
return;
}
- for (uint32_t i = 0; i < csOptions->count; ++i) {
- avifCodecSpecificOption * entry = &csOptions->entries[i];
- avifFree(entry->key);
- avifFree(entry->value);
- }
+ avifCodecSpecificOptionsClear(csOptions);
avifArrayDestroy(csOptions);
avifFree(csOptions);
}
@@ -916,10 +923,12 @@
}
}
- // Add a new key
- avifCodecSpecificOption * entry = (avifCodecSpecificOption *)avifArrayPushPtr(csOptions);
- entry->key = avifStrdup(key);
- entry->value = avifStrdup(value);
+ if (value) {
+ // Add a new key
+ avifCodecSpecificOption * entry = (avifCodecSpecificOption *)avifArrayPushPtr(csOptions);
+ entry->key = avifStrdup(key);
+ entry->value = avifStrdup(value);
+ }
}
// ---------------------------------------------------------------------------
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 6f64e59..4d9c801 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -527,11 +527,17 @@
avifEncoder * encoder,
const avifImage * image,
avifBool alpha,
- avifBool updateConfig,
+ avifEncoderChanges encoderChanges,
avifAddImageFlags addImageFlags,
avifCodecEncodeOutput * output)
{
- if (!codec->internal->encoderInitialized || updateConfig) {
+ struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;
+ aom_codec_iface_t * encoderInterface = NULL;
+ unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
+ int aomCpuUsed = -1;
+ avifBool lossless = AVIF_FALSE;
+
+ if (!codec->internal->encoderInitialized) {
// Map encoder speed to AOM usage + CpuUsed:
// Speed 0: GoodQuality CpuUsed 0
// Speed 1: GoodQuality CpuUsed 1
@@ -544,7 +550,6 @@
// Speed 8: RealTime CpuUsed 8
// Speed 9: RealTime CpuUsed 9
// Speed 10: RealTime CpuUsed 9
- unsigned int aomUsage = AOM_USAGE_GOOD_QUALITY;
// Use the new AOM_USAGE_ALL_INTRA (added in https://crbug.com/aomedia/2959) for still
// image encoding if it is available.
#if defined(AOM_USAGE_ALL_INTRA)
@@ -552,7 +557,6 @@
aomUsage = AOM_USAGE_ALL_INTRA;
}
#endif
- int aomCpuUsed = -1;
if (encoder->speed != AVIF_SPEED_DEFAULT) {
aomCpuUsed = AVIF_CLAMP(encoder->speed, 0, 9);
if (aomCpuUsed >= 7) {
@@ -589,23 +593,18 @@
}
}
- struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;
+ codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
+ if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
+ return AVIF_RESULT_UNKNOWN_ERROR;
+ }
- aom_codec_iface_t * encoderInterface = NULL;
- if (!codec->internal->encoderInitialized) {
- codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
- if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
- return AVIF_RESULT_UNKNOWN_ERROR;
- }
+ avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
- avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
-
- encoderInterface = aom_codec_av1_cx();
- aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, cfg, aomUsage);
- if (err != AOM_CODEC_OK) {
- avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
- return AVIF_RESULT_UNKNOWN_ERROR;
- }
+ encoderInterface = aom_codec_av1_cx();
+ aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, cfg, aomUsage);
+ if (err != AOM_CODEC_OK) {
+ avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
+ return AVIF_RESULT_UNKNOWN_ERROR;
}
// Set our own default cfg->rc_end_usage value, which may differ from libaom's default.
@@ -666,8 +665,7 @@
cfg->g_profile = seqProfile;
cfg->g_bit_depth = image->depth;
cfg->g_input_bit_depth = image->depth;
- cfg->g_w = image->width;
- cfg->g_h = image->height;
+
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
// Set the maximum number of frames to encode to 1. This instructs
// libaom to set still_picture and reduced_still_picture_header to
@@ -690,16 +688,6 @@
cfg->g_threads = encoder->maxThreads;
}
- int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
- int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
- if (alpha) {
- minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
- maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
- }
- avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
- cfg->rc_min_quantizer = minQuantizer;
- cfg->rc_max_quantizer = maxQuantizer;
-
codec->internal->monochromeEnabled = AVIF_FALSE;
if (aomVersion > aomVersion_2_0_0) {
// There exists a bug in libaom's chroma_check() function where it will attempt to
@@ -715,76 +703,126 @@
cfg->monochrome = 1;
}
}
+ }
+
+ avifBool dimensionsChanged = AVIF_FALSE;
+ if (!codec->internal->encoderInitialized) {
+ cfg->g_w = image->width;
+ cfg->g_h = image->height;
+ } else if ((cfg->g_w != image->width) || (cfg->g_h != image->height)) {
+ // We are not ready for dimension change for now.
+ return AVIF_RESULT_NOT_IMPLEMENTED;
+ }
+
+ if (!codec->internal->encoderInitialized || encoderChanges) {
+ int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
+ int maxQuantizer = AVIF_CLAMP(encoder->maxQuantizer, 0, 63);
+ if (alpha) {
+ minQuantizer = AVIF_CLAMP(encoder->minQuantizerAlpha, 0, 63);
+ maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
+ }
+ lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
+ cfg->rc_min_quantizer = minQuantizer;
+ cfg->rc_max_quantizer = maxQuantizer;
+ }
+
+ if (!codec->internal->encoderInitialized) {
+ aom_codec_flags_t encoderFlags = 0;
+ if (image->depth > 8) {
+ encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
+ }
if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) {
return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
}
- if (!codec->internal->encoderInitialized) {
- aom_codec_flags_t encoderFlags = 0;
- if (image->depth > 8) {
- encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
- }
-
- if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, cfg, encoderFlags) != AOM_CODEC_OK) {
- avifDiagnosticsPrintf(codec->diag,
- "aom_codec_enc_init() failed: %s: %s",
- aom_codec_error(&codec->internal->encoder),
- aom_codec_error_detail(&codec->internal->encoder));
- return AVIF_RESULT_UNKNOWN_ERROR;
- }
- codec->internal->encoderInitialized = AVIF_TRUE;
- } else {
- if (aom_codec_enc_config_set(&codec->internal->encoder, cfg) != AOM_CODEC_OK) {
- avifDiagnosticsPrintf(codec->diag,
- "aom_codec_enc_config_set() failed: %s: %s",
- aom_codec_error(&codec->internal->encoder),
- aom_codec_error_detail(&codec->internal->encoder));
- return AVIF_RESULT_UNKNOWN_ERROR;
- }
+ if (aom_codec_enc_init(&codec->internal->encoder, encoderInterface, cfg, encoderFlags) != AOM_CODEC_OK) {
+ avifDiagnosticsPrintf(codec->diag,
+ "aom_codec_enc_init() failed: %s: %s",
+ aom_codec_error(&codec->internal->encoder),
+ aom_codec_error_detail(&codec->internal->encoder));
+ return AVIF_RESULT_UNKNOWN_ERROR;
}
- if (lossless) {
- aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1);
- }
if (encoder->maxThreads > 1) {
aom_codec_control(&codec->internal->encoder, AV1E_SET_ROW_MT, 1);
}
- if (encoder->tileRowsLog2 != 0) {
- int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
- aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
- }
- if (encoder->tileColsLog2 != 0) {
- int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
- aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
- }
if (aomCpuUsed != -1) {
if (aom_codec_control(&codec->internal->encoder, AOME_SET_CPUUSED, aomCpuUsed) != AOM_CODEC_OK) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
}
+ } else if ((encoderChanges & ~AVIF_ENCODER_CHANGE_CODEC_SPECIFIC) || dimensionsChanged) {
+ // Codec specific options does not change cfg, so no need to update it.
+ aom_codec_err_t err = aom_codec_enc_config_set(&codec->internal->encoder, cfg);
+ if (err != AOM_CODEC_OK) {
+ avifDiagnosticsPrintf(codec->diag,
+ "aom_codec_enc_config_set() failed: %s: %s",
+ aom_codec_error(&codec->internal->encoder),
+ aom_codec_error_detail(&codec->internal->encoder));
+ return AVIF_RESULT_UNKNOWN_ERROR;
+ }
+ }
+
+ if (!codec->internal->encoderInitialized || (encoderChanges & AVIF_ENCODER_CHANGE_CODEC_SPECIFIC)) {
if (!avifProcessAOMOptionsPostInit(codec, alpha)) {
return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
}
-#if defined(AOM_USAGE_ALL_INTRA)
- if (aomUsage == AOM_USAGE_ALL_INTRA && !codec->internal->endUsageSet && !codec->internal->cqLevelSet) {
- // The default rc_end_usage in all intra mode is AOM_Q, which requires cq-level to
- // function. A libavif user may not know this internal detail and therefore may only
- // set the min and max quantizers in the avifEncoder struct. If this is the case, set
- // cq-level to a reasonable value for the user, otherwise the default cq-level
- // (currently 10) will be unknowingly used.
- assert(cfg->rc_end_usage == AOM_Q);
- unsigned int cqLevel = (cfg->rc_min_quantizer + cfg->rc_max_quantizer) / 2;
- aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel);
+ }
+
+ avifBool quantizerUpdated = AVIF_FALSE;
+ if (!codec->internal->encoderInitialized) {
+ quantizerUpdated = AVIF_TRUE;
+ if (lossless) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
}
-#endif
+ int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6);
+ if (tileRowsLog2 > 0) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2);
+ }
+ int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6);
+ if (tileColsLog2 > 0) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2);
+ }
if (!codec->internal->tuningSet) {
if (aom_codec_control(&codec->internal->encoder, AOME_SET_TUNING, AOM_TUNE_SSIM) != AOM_CODEC_OK) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
}
+ codec->internal->encoderInitialized = AVIF_TRUE;
+ } else if (encoderChanges) {
+ if (alpha) {
+ if (encoderChanges & (AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA | AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA)) {
+ quantizerUpdated = AVIF_TRUE;
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
+ }
+ } else {
+ if (encoderChanges & (AVIF_ENCODER_CHANGE_MIN_QUANTIZER | AVIF_ENCODER_CHANGE_MAX_QUANTIZER)) {
+ quantizerUpdated = AVIF_TRUE;
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
+ }
+ }
+ if (encoderChanges & AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, AVIF_CLAMP(encoder->tileRowsLog2, 0, 6));
+ }
+ if (encoderChanges & AVIF_ENCODER_CHANGE_TILE_COLS_LOG2) {
+ aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, AVIF_CLAMP(encoder->tileColsLog2, 0, 6));
+ }
}
+#if defined(AOM_USAGE_ALL_INTRA)
+ if (aomUsage == AOM_USAGE_ALL_INTRA && !codec->internal->endUsageSet && !codec->internal->cqLevelSet && quantizerUpdated) {
+ // The default rc_end_usage in all intra mode is AOM_Q, which requires cq-level to
+ // function. A libavif user may not know this internal detail and therefore may only
+ // set the min and max quantizers in the avifEncoder struct. If this is the case, set
+ // cq-level to a reasonable value for the user, otherwise the default cq-level
+ // (currently 10) will be unknowingly used.
+ assert(cfg->rc_end_usage == AOM_Q);
+ unsigned int cqLevel = (cfg->rc_min_quantizer + cfg->rc_max_quantizer) / 2;
+ aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel);
+ }
+#endif
+
aom_image_t aomImage;
// We prefer to simply set the aomImage.planes[] pointers to the plane buffers in 'image'. When
// doing this, we set aomImage.w equal to aomImage.d_w and aomImage.h equal to aomImage.d_h and
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index f8b301a..ee68268 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -12,6 +12,8 @@
RaContext * rav1eContext;
RaChromaSampling chromaSampling;
int yShift;
+ uint32_t encodeWidth;
+ uint32_t encodeHeight;
};
static void rav1eCodecDestroyInternal(avifCodec * codec)
@@ -51,11 +53,20 @@
avifEncoder * encoder,
const avifImage * image,
avifBool alpha,
- avifBool updateConfig,
+ avifEncoderChanges updatedConfig,
uint32_t addImageFlags,
avifCodecEncodeOutput * output)
{
- if (updateConfig) {
+ // rav1e does not support changing config.
+ if (updatedConfig) {
+ return AVIF_RESULT_NOT_IMPLEMENTED;
+ }
+
+ // rav1e does not support changing encoding dimension.
+ if (!codec->internal->rav1eContext) {
+ codec->internal->encodeWidth = image->width;
+ codec->internal->encodeHeight = image->height;
+ } else if ((codec->internal->encodeWidth != image->width) || (codec->internal->encodeHeight != image->height)) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
diff --git a/src/codec_svt.c b/src/codec_svt.c
index f45ccf1..f6a4c18 100644
--- a/src/codec_svt.c
+++ b/src/codec_svt.c
@@ -46,14 +46,22 @@
avifEncoder * encoder,
const avifImage * image,
avifBool alpha,
- avifBool updateConfig,
+ avifEncoderChanges updatedConfig,
uint32_t addImageFlags,
avifCodecEncodeOutput * output)
{
- if (updateConfig) {
+ // svt does not support changing config.
+ if (updatedConfig) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
+ // svt does not support changing encoding dimension.
+ if (codec->internal->svt_encoder != NULL) {
+ if ((codec->internal->svt_config.source_width != image->width) || (codec->internal->svt_config.source_height != image->height)) {
+ return AVIF_RESULT_NOT_IMPLEMENTED;
+ }
+ }
+
avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
EbColorFormat color_format = EB_YUV420;
EbBufferHeaderType * input_buffer = NULL;
diff --git a/src/write.c b/src/write.c
index 0083dcf..5cfcff9 100644
--- a/src/write.c
+++ b/src/write.c
@@ -128,7 +128,6 @@
uint16_t primaryItemID;
avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage()
avifBool alphaPresent;
- avifBool csOptionsUpdated;
} avifEncoderData;
static void avifEncoderDataDestroy(avifEncoderData * data);
@@ -319,7 +318,6 @@
void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
{
avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
- encoder->data->csOptionsUpdated = AVIF_TRUE; // False positive is possible but not important.
}
static void avifBackupSettings(avifEncoder * encoder)
@@ -339,13 +337,12 @@
lastEncoder->tileRowsLog2 = encoder->tileRowsLog2;
lastEncoder->tileColsLog2 = encoder->tileColsLog2;
lastEncoder->speed = encoder->speed;
- encoder->data->csOptionsUpdated = AVIF_FALSE;
}
// This function detect changes made on avifEncoder.
// It reports if the change is valid, i.e. if any setting that can't change was changed.
-// It also sets needUpdate to true if valid changes are detected.
-static avifBool avifEncoderSettingsChanged(const avifEncoder * encoder, avifBool * needUpdate)
+// It also reports detected changes in updatedConfig.
+static avifBool avifEncoderSettingsChanged(const avifEncoder * encoder, avifEncoderChanges * encoderChanges)
{
const avifEncoder * lastEncoder = &encoder->data->lastEncoder;
@@ -354,16 +351,32 @@
}
if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
- (lastEncoder->timescale != encoder->timescale)) {
+ (lastEncoder->timescale != encoder->timescale) || (lastEncoder->maxThreads != encoder->maxThreads) ||
+ (lastEncoder->speed != encoder->speed)) {
return AVIF_FALSE;
}
- if ((lastEncoder->maxThreads != encoder->maxThreads) || (lastEncoder->minQuantizer != encoder->minQuantizer) ||
- (lastEncoder->maxQuantizer != encoder->maxQuantizer) || (lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha) ||
- (lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha) || (lastEncoder->tileRowsLog2 != encoder->tileRowsLog2) ||
- (lastEncoder->tileColsLog2 != encoder->tileColsLog2) || (lastEncoder->speed != encoder->speed) ||
- (encoder->data->csOptionsUpdated)) {
- *needUpdate = AVIF_TRUE;
+ *encoderChanges = 0;
+ if ((lastEncoder->minQuantizer != encoder->minQuantizer)) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER;
+ }
+ if ((lastEncoder->maxQuantizer != encoder->maxQuantizer)) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER;
+ }
+ if ((lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha)) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA;
+ }
+ if ((lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha)) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA;
+ }
+ if ((lastEncoder->tileRowsLog2 != encoder->tileRowsLog2)) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2;
+ }
+ if ((lastEncoder->tileColsLog2 != encoder->tileColsLog2)) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_TILE_COLS_LOG2;
+ }
+ if (encoder->csOptions->count > 0) {
+ *encoderChanges |= AVIF_ENCODER_CHANGE_CODEC_SPECIFIC;
}
return AVIF_TRUE;
@@ -656,8 +669,8 @@
return AVIF_RESULT_NO_CODEC_AVAILABLE;
}
- avifBool updateConfig = AVIF_FALSE;
- if (!avifEncoderSettingsChanged(encoder, &updateConfig)) {
+ avifEncoderChanges encoderChanges = 0;
+ if (!avifEncoderSettingsChanged(encoder, &encoderChanges)) {
return AVIF_RESULT_CANNOT_CHANGE_SETTING;
}
avifBackupSettings(encoder);
@@ -863,19 +876,11 @@
// Another frame in an image sequence
const avifImage * imageMetadata = encoder->data->imageMetadata;
- // HEIF (ISO 23008-12:2017), Section 6.6.2.3.1:
- // All input images shall have exactly the same width and height; call those tile_width and tile_height.
- // MIAF (ISO 23000-22:2019), Section 7.3.11.4.1:
- // All input images of a grid image item shall use the same coding format, chroma sampling format, and the
- // same decoder configuration (see 7.3.6.2).
- // If the first image in the sequence had an alpha plane (even if fully opaque), all
- // subsequence images must have alpha as well.
- if ((imageMetadata->width != firstCell->width) || (imageMetadata->height != firstCell->height) ||
- (imageMetadata->depth != firstCell->depth) || (imageMetadata->yuvFormat != firstCell->yuvFormat) ||
+ // If the first image had an alpha plane (even if fully opaque), all subsequent images must have alpha as well.
+ if ((imageMetadata->depth != firstCell->depth) || (imageMetadata->yuvFormat != firstCell->yuvFormat) ||
(imageMetadata->yuvRange != firstCell->yuvRange) || (imageMetadata->colorPrimaries != firstCell->colorPrimaries) ||
(imageMetadata->transferCharacteristics != firstCell->transferCharacteristics) ||
(imageMetadata->matrixCoefficients != firstCell->matrixCoefficients) ||
- (!!imageMetadata->alphaPlane != !!firstCell->alphaPlane) ||
(imageMetadata->alphaPremultiplied != firstCell->alphaPremultiplied) ||
(encoder->data->alphaPresent && !firstCell->alphaPlane)) {
return AVIF_RESULT_INCOMPATIBLE_IMAGE;
@@ -894,7 +899,7 @@
if (item->codec) {
const avifImage * cellImage = cellImages[item->cellIndex];
avifResult encodeResult =
- item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, updateConfig, addImageFlags, item->encodeOutput);
+ item->codec->encodeImage(item->codec, encoder, cellImage, item->alpha, encoderChanges, addImageFlags, item->encodeOutput);
if (encodeResult == AVIF_RESULT_UNKNOWN_ERROR) {
encodeResult = item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
@@ -904,6 +909,7 @@
}
}
+ avifCodecSpecificOptionsClear(encoder->csOptions);
avifEncoderFrame * frame = (avifEncoderFrame *)avifArrayPushPtr(&encoder->data->frames);
frame->durationInTimescales = durationInTimescales;
return AVIF_RESULT_OK;