Roll "singleImage" and keyframe forcing into a flags value, add keyframe interval feature with a sensible default in avifenc
diff --git a/apps/avifenc.c b/apps/avifenc.c
index 1a2d9c1..3c61135 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -12,6 +12,8 @@
#include <stdlib.h>
#include <string.h>
+#define KEYFRAME_INTERVAL_DEFAULT 8 // A reasonable (but arbitrary) default
+
#define NEXTARG() \
if (((argIndex + 1) == argc) || (argv[argIndex + 1][0] == '-')) { \
fprintf(stderr, "%s requires an argument.", arg); \
@@ -56,6 +58,8 @@
AVIF_SPEED_FASTEST);
printf(" -c,--codec C : AV1 codec to use (choose from versions list below)\n");
printf(" --timescale,--fps V : Set the timescale to V. If all frames are 1 timescale in length, this is equivalent to frames per second\n");
+ printf(" -k,--keyframe INTERVAL : Set the keyframe interval (how many frames between keyframes). Set to 0 to disable. Default: %d\n",
+ KEYFRAME_INTERVAL_DEFAULT);
printf(" --ignore-icc : If the input file contains an embedded ICC profile, ignore it (no-op if absent)\n");
printf(" --pasp H,V : Add pasp property (aspect ratio). H=horizontal spacing, V=vertical spacing\n");
printf(" --clap WN,WD,HN,HD,HON,HOD,VON,VOD: Add clap property (clean aperture). Width, Height, HOffset, VOffset (in num/denom pairs)\n");
@@ -165,6 +169,7 @@
avifImage * nextImage = NULL;
avifRWData raw = AVIF_DATA_EMPTY;
int timescale = 1; // 1 fps by default
+ int keyframeInterval = KEYFRAME_INTERVAL_DEFAULT;
// By default, the color profile itself is unspecified, so CP/TC are set (to 2) accordingly.
// However, if the end-user doesn't specify any CICP, we will convert to YUV using BT709
@@ -212,6 +217,9 @@
returnCode = 1;
goto cleanup;
}
+ } else if (!strcmp(arg, "-k") || !strcmp(arg, "--keyframe")) {
+ NEXTARG();
+ keyframeInterval = atoi(arg);
} else if (!strcmp(arg, "--min")) {
NEXTARG();
minQuantizer = atoi(arg);
@@ -538,13 +546,18 @@
encoder->codecChoice = codecChoice;
encoder->speed = speed;
encoder->timescale = (uint64_t)timescale;
- encoder->singleImage = (inputFilenamesCount == 1);
+ encoder->keyframeInterval = keyframeInterval;
+
+ uint32_t addImageFlags = AVIF_ADD_IMAGE_FLAG_NONE;
+ if (inputFilenamesCount == 1) {
+ addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE;
+ }
uint32_t firstDurationInTimescales = 1; // TODO: allow arbitrary per-frame durations
if (inputFilenamesCount > 1) {
printf(" * Encoding frame 1 [%u/%d ts]: %s\n", firstDurationInTimescales, timescale, inputFilenames[0]);
}
- avifResult addImageResult = avifEncoderAddImage(encoder, image, 1);
+ avifResult addImageResult = avifEncoderAddImage(encoder, image, 1, addImageFlags);
if (addImageResult != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to encode image: %s\n", avifResultToString(addImageResult));
goto cleanup;
@@ -630,7 +643,7 @@
goto cleanup;
}
- avifResult nextImageResult = avifEncoderAddImage(encoder, nextImage, nextDurationInTimescales);
+ avifResult nextImageResult = avifEncoderAddImage(encoder, nextImage, nextDurationInTimescales, AVIF_ADD_IMAGE_FLAG_NONE);
if (nextImageResult != AVIF_RESULT_OK) {
fprintf(stderr, "ERROR: Failed to encode image: %s\n", avifResultToString(nextImageResult));
goto cleanup;
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 3f83efc..89b889c 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -651,10 +651,8 @@
int tileRowsLog2;
int tileColsLog2;
int speed;
+ int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default).
uint64_t timescale; // timescale of the media (Hz)
- avifBool singleImage; // Set to true when encoding a single image. Signals "still_picture" to AV1 encoders, which
- // tweaks various compression rules. This is enabled automatically when using the
- // avifEncoderWrite() single-image encode path.
// stats from the most recent write
avifIOStats ioStats;
@@ -667,6 +665,19 @@
avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output);
void avifEncoderDestroy(avifEncoder * encoder);
+enum avifAddImageFlags
+{
+ AVIF_ADD_IMAGE_FLAG_NONE = 0,
+
+ // Force this frame to be a keyframe (sync frame).
+ AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME = (1 << 0),
+
+ // Use this flag when encoding a single image. Signals "still_picture" to AV1 encoders, which
+ // tweaks various compression rules. This is enabled automatically when using the
+ // avifEncoderWrite() single-image encode path.
+ AVIF_ADD_IMAGE_FLAG_SINGLE = (1 << 1)
+};
+
// Multi-function alternative to avifEncoderWrite() for image sequences.
//
// Usage / function call order is:
@@ -676,7 +687,7 @@
// * avifEncoderFinish()
// * avifEncoderDestroy()
//
-avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales);
+avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, uint32_t addImageFlags);
avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output);
// Helpers
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 325de86..5ff0fd4 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -136,7 +136,7 @@
const avifImage * image,
avifEncoder * encoder,
avifBool alpha,
- avifBool forceKeyframe,
+ uint32_t addImageFlags,
avifCodecEncodeOutput * output);
typedef avifBool (*avifCodecEncodeFinishFunc)(struct avifCodec * codec, avifCodecEncodeOutput * output);
typedef void (*avifCodecDestroyInternalFunc)(struct avifCodec * codec);
diff --git a/src/codec_aom.c b/src/codec_aom.c
index 7802d6d..a23e0cc 100644
--- a/src/codec_aom.c
+++ b/src/codec_aom.c
@@ -239,7 +239,7 @@
const avifImage * image,
avifEncoder * encoder,
avifBool alpha,
- avifBool forceKeyframe,
+ uint32_t addImageFlags,
avifCodecEncodeOutput * output)
{
if (!codec->internal->encoderInitialized) {
@@ -398,7 +398,7 @@
}
aom_enc_frame_flags_t encodeFlags = 0;
- if (forceKeyframe) {
+ if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) {
encodeFlags |= AOM_EFLAG_FORCE_KF;
}
aom_codec_encode(&codec->internal->encoder, aomImage, 0, 1, encodeFlags);
diff --git a/src/codec_rav1e.c b/src/codec_rav1e.c
index 9cdf133..b765159 100644
--- a/src/codec_rav1e.c
+++ b/src/codec_rav1e.c
@@ -35,7 +35,7 @@
const avifImage * image,
avifEncoder * encoder,
avifBool alpha,
- avifBool forceKeyframe,
+ uint32_t addImageFlags,
avifCodecEncodeOutput * output)
{
avifBool success = AVIF_FALSE;
@@ -79,7 +79,7 @@
goto cleanup;
}
- if (encoder->singleImage) {
+ if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
if (rav1e_config_parse(rav1eConfig, "still_picture", "true") == -1) {
goto cleanup;
}
@@ -153,7 +153,7 @@
}
RaFrameTypeOverride frameType = RA_FRAME_TYPE_OVERRIDE_NO;
- if (forceKeyframe) {
+ if (addImageFlags & AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME) {
frameType = RA_FRAME_TYPE_OVERRIDE_KEY;
}
rav1e_frame_set_type(rav1eFrame, frameType);
diff --git a/src/write.c b/src/write.c
index b666d65..1c94e65 100644
--- a/src/write.c
+++ b/src/write.c
@@ -177,7 +177,7 @@
encoder->speed = AVIF_SPEED_DEFAULT;
encoder->data = avifEncoderDataCreate();
encoder->timescale = 1;
- encoder->singleImage = AVIF_FALSE;
+ encoder->keyframeInterval = 0;
return encoder;
}
@@ -332,7 +332,7 @@
avifRWStreamFinishBox(s, meta);
}
-avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales)
+avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales, uint32_t addImageFlags)
{
// -----------------------------------------------------------------------
// Validate image
@@ -446,15 +446,14 @@
// -----------------------------------------------------------------------
// Encode AV1 OBUs
- avifBool forceKeyframe = AVIF_FALSE;
- if ((encoder->data->frames.count % 8) == 0) { // TODO: make this configurable
- forceKeyframe = AVIF_TRUE;
+ if (encoder->keyframeInterval && ((encoder->data->frames.count % encoder->keyframeInterval) == 0)) {
+ addImageFlags |= AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME;
}
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
if (item->codec) {
- if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha, forceKeyframe, item->encodeOutput)) {
+ if (!item->codec->encodeImage(item->codec, image, encoder, item->alpha, addImageFlags, item->encodeOutput)) {
return item->alpha ? AVIF_RESULT_ENCODE_ALPHA_FAILED : AVIF_RESULT_ENCODE_COLOR_FAILED;
}
}
@@ -956,8 +955,7 @@
avifResult avifEncoderWrite(avifEncoder * encoder, const avifImage * image, avifRWData * output)
{
- encoder->singleImage = AVIF_TRUE;
- avifResult addImageResult = avifEncoderAddImage(encoder, image, 1);
+ avifResult addImageResult = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
if (addImageResult != AVIF_RESULT_OK) {
return addImageResult;
}