Skip to content

Commit 054b138

Browse files
Support changing frame size during encoding
1 parent d61dc3b commit 054b138

File tree

6 files changed

+113
-25
lines changed

6 files changed

+113
-25
lines changed

include/avif/avif.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,9 @@ struct avifCodecSpecificOptions;
10461046
// image in less bytes. AVIF_SPEED_DEFAULT means "Leave the AV1 codec to its default speed settings"./
10471047
// If avifEncoder uses rav1e, the speed value is directly passed through (0-10). If libaom is used,
10481048
// a combination of settings are tweaked to simulate this speed range.
1049+
// * Width and height: width and height of encoded image. Default value 0 means infer from first frame.
1050+
// For grid image, this is the size of one cell. Value must not be smaller than the largest frame
1051+
// to be encoded.
10491052
// * Some encoder settings can be changed after encoding starts. Changes will take effect in the next
10501053
// call to avifEncoderAddImage().
10511054
typedef struct avifEncoder
@@ -1058,6 +1061,8 @@ typedef struct avifEncoder
10581061
int speed;
10591062
int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default).
10601063
uint64_t timescale; // timescale of the media (Hz)
1064+
uint32_t width;
1065+
uint32_t height;
10611066
// changeable encoder settings
10621067
int minQuantizer;
10631068
int maxQuantizer;

src/codec_aom.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
666666
cfg->g_input_bit_depth = image->depth;
667667
cfg->g_w = image->width;
668668
cfg->g_h = image->height;
669+
cfg->g_forced_max_frame_width = encoder->width;
670+
cfg->g_forced_max_frame_height = encoder->height;
669671
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
670672
// Set the maximum number of frames to encode to 1. This instructs
671673
// libaom to set still_picture and reduced_still_picture_header to
@@ -761,8 +763,9 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
761763
} else {
762764
avifBool dimensionsChanged = AVIF_FALSE;
763765
if ((cfg->g_w != image->width) || (cfg->g_h != image->height)) {
764-
// We are not ready for dimension change for now.
765-
return AVIF_RESULT_NOT_IMPLEMENTED;
766+
cfg->g_w = image->width;
767+
cfg->g_h = image->height;
768+
dimensionsChanged = AVIF_TRUE;
766769
}
767770
if (alpha) {
768771
if (encoderChanges & (AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA | AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA)) {

src/codec_rav1e.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec,
7070
return AVIF_RESULT_NOT_IMPLEMENTED;
7171
}
7272

73+
// rav1e does not support overriding maximum frame width/height in sequence header
74+
if (encoder->width || encoder->height) {
75+
return AVIF_RESULT_NOT_IMPLEMENTED;
76+
}
77+
7378
avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
7479

7580
RaConfig * rav1eConfig = NULL;

src/codec_svt.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ static avifResult svtCodecEncodeImage(avifCodec * codec,
116116

117117
svt_config->source_width = image->width;
118118
svt_config->source_height = image->height;
119+
svt_config->forced_max_frame_width = encoder->width;
120+
svt_config->forced_max_frame_height = encoder->height;
119121
svt_config->logical_processors = encoder->maxThreads;
120122
svt_config->enable_adaptive_quantization = AVIF_FALSE;
121123
// disable 2-pass

src/write.c

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ avifEncoder * avifEncoderCreate(void)
297297
encoder->speed = AVIF_SPEED_DEFAULT;
298298
encoder->keyframeInterval = 0;
299299
encoder->timescale = 1;
300+
encoder->width = 0;
301+
encoder->height = 0;
300302
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
301303
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
302304
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
@@ -332,12 +334,15 @@ static void avifEncoderBackupSettings(avifEncoder * encoder)
332334
lastEncoder->speed = encoder->speed;
333335
lastEncoder->keyframeInterval = encoder->keyframeInterval;
334336
lastEncoder->timescale = encoder->timescale;
337+
lastEncoder->width = encoder->width;
338+
lastEncoder->height = encoder->height;
335339
lastEncoder->minQuantizer = encoder->minQuantizer;
336340
lastEncoder->maxQuantizer = encoder->maxQuantizer;
337341
lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha;
338342
lastEncoder->maxQuantizerAlpha = encoder->maxQuantizerAlpha;
339343
lastEncoder->tileRowsLog2 = encoder->tileRowsLog2;
340344
lastEncoder->tileColsLog2 = encoder->tileColsLog2;
345+
lastEncoder->speed = encoder->speed;
341346
}
342347

343348
// This function detects changes made on avifEncoder. It returns true on success (i.e., if every
@@ -355,7 +360,7 @@ static avifBool avifEncoderDetectChanges(const avifEncoder * encoder, avifEncode
355360

356361
if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->maxThreads != encoder->maxThreads) ||
357362
(lastEncoder->speed != encoder->speed) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
358-
(lastEncoder->timescale != encoder->timescale)) {
363+
(lastEncoder->timescale != encoder->timescale) || (lastEncoder->width != encoder->width) || (lastEncoder->height != encoder->height)) {
359364
return AVIF_FALSE;
360365
}
361366

@@ -694,6 +699,10 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
694699
return AVIF_RESULT_NO_CONTENT;
695700
}
696701

702+
if ((encoder->width && (encoder->width < firstCell->width)) || (encoder->height && (encoder->height < firstCell->height))) {
703+
return AVIF_RESULT_INCOMPATIBLE_IMAGE;
704+
}
705+
697706
if ((cellCount > 1) && !avifAreGridDimensionsValid(firstCell->yuvFormat,
698707
gridCols * firstCell->width,
699708
gridRows * firstCell->height,
@@ -997,6 +1006,9 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
9971006
// Begin write stream
9981007

9991008
const avifImage * imageMetadata = encoder->data->imageMetadata;
1009+
const uint32_t cellWidth = encoder->width ? encoder->width : imageMetadata->width;
1010+
const uint32_t cellHeight = encoder->height ? encoder->height : imageMetadata->height;
1011+
10001012
// The epoch for creation_time and modification_time is midnight, Jan. 1,
10011013
// 1904, in UTC time. Add the number of seconds between that epoch and the
10021014
// Unix epoch.
@@ -1200,11 +1212,11 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
12001212
}
12011213
}
12021214

1203-
uint32_t imageWidth = imageMetadata->width;
1204-
uint32_t imageHeight = imageMetadata->height;
1215+
uint32_t imageWidth = cellWidth;
1216+
uint32_t imageHeight = cellHeight;
12051217
if (isGrid) {
1206-
imageWidth = imageMetadata->width * item->gridCols;
1207-
imageHeight = imageMetadata->height * item->gridRows;
1218+
imageWidth = imageWidth * item->gridCols;
1219+
imageHeight = imageHeight * item->gridRows;
12081220
}
12091221

12101222
// Properties all av01 items need
@@ -1361,8 +1373,8 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
13611373
avifRWStreamWriteU16(&s, 0); // template int(16) volume = {if track_is_audio 0x0100 else 0};
13621374
avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0;
13631375
avifRWStreamWrite(&s, unityMatrix, sizeof(unityMatrix)); // template int(32)[9] matrix= // { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 };
1364-
avifRWStreamWriteU32(&s, imageMetadata->width << 16); // unsigned int(32) width;
1365-
avifRWStreamWriteU32(&s, imageMetadata->height << 16); // unsigned int(32) height;
1376+
avifRWStreamWriteU32(&s, cellWidth << 16); // unsigned int(32) width;
1377+
avifRWStreamWriteU32(&s, cellHeight << 16); // unsigned int(32) height;
13661378
avifRWStreamFinishBox(&s, tkhd);
13671379

13681380
if (item->irefToID != 0) {
@@ -1470,21 +1482,21 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
14701482
avifBoxMarker stsd = avifRWStreamWriteFullBox(&s, "stsd", AVIF_BOX_SIZE_TBD, 0, 0);
14711483
avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
14721484
avifBoxMarker av01 = avifRWStreamWriteBox(&s, "av01", AVIF_BOX_SIZE_TBD);
1473-
avifRWStreamWriteZeros(&s, 6); // const unsigned int(8)[6] reserved = 0;
1474-
avifRWStreamWriteU16(&s, 1); // unsigned int(16) data_reference_index;
1475-
avifRWStreamWriteU16(&s, 0); // unsigned int(16) pre_defined = 0;
1476-
avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0;
1477-
avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3); // unsigned int(32)[3] pre_defined = 0;
1478-
avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->width); // unsigned int(16) width;
1479-
avifRWStreamWriteU16(&s, (uint16_t)imageMetadata->height); // unsigned int(16) height;
1480-
avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) horizresolution
1481-
avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) vertresolution
1482-
avifRWStreamWriteU32(&s, 0); // const unsigned int(32) reserved = 0;
1483-
avifRWStreamWriteU16(&s, 1); // template unsigned int(16) frame_count = 1;
1484-
avifRWStreamWriteChars(&s, "\012AOM Coding", 11); // string[32] compressorname;
1485-
avifRWStreamWriteZeros(&s, 32 - 11); //
1486-
avifRWStreamWriteU16(&s, 0x0018); // template unsigned int(16) depth = 0x0018;
1487-
avifRWStreamWriteU16(&s, (uint16_t)0xffff); // int(16) pre_defined = -1;
1485+
avifRWStreamWriteZeros(&s, 6); // const unsigned int(8)[6] reserved = 0;
1486+
avifRWStreamWriteU16(&s, 1); // unsigned int(16) data_reference_index;
1487+
avifRWStreamWriteU16(&s, 0); // unsigned int(16) pre_defined = 0;
1488+
avifRWStreamWriteU16(&s, 0); // const unsigned int(16) reserved = 0;
1489+
avifRWStreamWriteZeros(&s, sizeof(uint32_t) * 3); // unsigned int(32)[3] pre_defined = 0;
1490+
avifRWStreamWriteU16(&s, (uint16_t)cellWidth); // unsigned int(16) width;
1491+
avifRWStreamWriteU16(&s, (uint16_t)cellHeight); // unsigned int(16) height;
1492+
avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) horizresolution
1493+
avifRWStreamWriteU32(&s, 0x00480000); // template unsigned int(32) vertresolution
1494+
avifRWStreamWriteU32(&s, 0); // const unsigned int(32) reserved = 0;
1495+
avifRWStreamWriteU16(&s, 1); // template unsigned int(16) frame_count = 1;
1496+
avifRWStreamWriteChars(&s, "\012AOM Coding", 11); // string[32] compressorname;
1497+
avifRWStreamWriteZeros(&s, 32 - 11); //
1498+
avifRWStreamWriteU16(&s, 0x0018); // template unsigned int(16) depth = 0x0018;
1499+
avifRWStreamWriteU16(&s, (uint16_t)0xffff); // int(16) pre_defined = -1;
14881500
writeConfigBox(&s, &item->av1C);
14891501
if (!item->alpha) {
14901502
avifEncoderWriteColorProperties(&s, imageMetadata, NULL, NULL);

tests/gtest/avifchangesettingtest.cc

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void TestEncodeDecode(avifCodecChoice codec,
1818
GTEST_SKIP() << "Codec unavailable, skip test.";
1919
}
2020

21-
const uint32_t image_size = 512;
21+
const int image_size = 512;
2222
testutil::AvifImagePtr image =
2323
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV420,
2424
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
@@ -143,5 +143,66 @@ TEST(ChangeSettingTest, UnchangeableSetting) {
143143
AVIF_RESULT_CANNOT_CHANGE_SETTING);
144144
}
145145

146+
TEST(ChangeSettingTest, DISABLED_ChangeDimension) {
147+
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
148+
nullptr) {
149+
GTEST_SKIP() << "Codec unavailable, skip test.";
150+
}
151+
152+
const int size_small = 256;
153+
const int size_display = 512;
154+
155+
testutil::AvifImagePtr first =
156+
testutil::CreateImage(size_small, size_small, 8, AVIF_PIXEL_FORMAT_YUV420,
157+
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
158+
ASSERT_NE(first, nullptr);
159+
testutil::FillImageGradient(first.get());
160+
161+
testutil::AvifImagePtr second = testutil::CreateImage(
162+
size_display, size_display, 8, AVIF_PIXEL_FORMAT_YUV420, AVIF_PLANES_YUV,
163+
AVIF_RANGE_FULL);
164+
ASSERT_NE(second, nullptr);
165+
testutil::FillImageGradient(second.get());
166+
167+
testutil::AvifRwData encodedAvif;
168+
169+
// Encode
170+
{
171+
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
172+
ASSERT_NE(encoder, nullptr);
173+
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
174+
encoder->speed = AVIF_SPEED_FASTEST;
175+
encoder->timescale = 1;
176+
encoder->minQuantizer = 63;
177+
encoder->maxQuantizer = 63;
178+
encoder->width = size_display;
179+
encoder->height = size_display;
180+
181+
ASSERT_EQ(avifEncoderAddImage(encoder.get(), first.get(), 1, 0),
182+
AVIF_RESULT_OK);
183+
184+
ASSERT_EQ(avifEncoderAddImage(encoder.get(), second.get(), 1, 0),
185+
AVIF_RESULT_OK);
186+
187+
ASSERT_EQ(avifEncoderFinish(encoder.get(), &encodedAvif), AVIF_RESULT_OK);
188+
}
189+
190+
// Decode
191+
{
192+
testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy);
193+
ASSERT_NE(decoder, nullptr);
194+
195+
avifDecoderSetIOMemory(decoder.get(), encodedAvif.data, encodedAvif.size);
196+
ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK);
197+
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
198+
// libavif scales frame automatically.
199+
ASSERT_EQ(decoder->image->width, size_display);
200+
ASSERT_EQ(decoder->image->height, size_display);
201+
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
202+
ASSERT_EQ(decoder->image->width, size_display);
203+
ASSERT_EQ(decoder->image->height, size_display);
204+
}
205+
}
206+
146207
} // namespace
147208
} // namespace libavif

0 commit comments

Comments
 (0)