Skip to content

Commit 6953709

Browse files
auto detect change to avifEncoder
1 parent 25ea727 commit 6953709

6 files changed

Lines changed: 130 additions & 76 deletions

File tree

include/avif/avif.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ typedef enum avifResult
146146
AVIF_RESULT_WAITING_ON_IO, // similar to EAGAIN/EWOULDBLOCK, this means the avifIO doesn't have necessary data available yet
147147
AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid
148148
AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented
149-
AVIF_RESULT_OUT_OF_MEMORY
149+
AVIF_RESULT_OUT_OF_MEMORY,
150+
AVIF_RESULT_CANNOT_CHANGE_SETTING, // a setting that can't change is changed during encoding
150151
} avifResult;
151152

152153
AVIF_API const char * avifResultToString(avifResult result);
@@ -1037,8 +1038,8 @@ struct avifCodecSpecificOptions;
10371038
// image in less bytes. AVIF_SPEED_DEFAULT means "Leave the AV1 codec to its default speed settings"./
10381039
// If avifEncoder uses rav1e, the speed value is directly passed through (0-10). If libaom is used,
10391040
// a combination of settings are tweaked to simulate this speed range.
1040-
// * AV1 encoder settings and csOptions will be applied to AV1 encoder before encoding first image, and images
1041-
// added with AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS flag.
1041+
// * AV1 encoder settings and codecSpecificOptions will be applied to AV1 encoder when changed or when
1042+
// AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS flag is explicitly requested.
10421043
typedef struct avifEncoder
10431044
{
10441045
// Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c)
@@ -1085,6 +1086,7 @@ typedef enum avifAddImageFlag
10851086
AVIF_ADD_IMAGE_FLAG_SINGLE = (1 << 1),
10861087

10871088
// Use this flag to update encode settings of AV1 encoder.
1089+
// This is enabled automatically if encoder settings is changed.
10881090
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS = (1 << 2)
10891091
} avifAddImageFlag;
10901092
typedef uint32_t avifAddImageFlags;

src/avif.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const char * avifResultToString(avifResult result)
9696
case AVIF_RESULT_INVALID_ARGUMENT: return "Invalid argument";
9797
case AVIF_RESULT_NOT_IMPLEMENTED: return "Not implemented";
9898
case AVIF_RESULT_OUT_OF_MEMORY: return "Out of memory";
99+
case AVIF_RESULT_CANNOT_CHANGE_SETTING: return "Can not change some settings during encoding";
99100
case AVIF_RESULT_UNKNOWN_ERROR:
100101
default:
101102
break;

src/write.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,13 @@ typedef struct avifEncoderData
122122
{
123123
avifEncoderItemArray items;
124124
avifEncoderFrameArray frames;
125+
avifEncoder lastEncoder;
125126
avifImage * imageMetadata;
126127
uint16_t lastItemID;
127128
uint16_t primaryItemID;
128129
avifBool singleImage; // if true, the AVIF_ADD_IMAGE_FLAG_SINGLE flag was set on the first call to avifEncoderAddImage()
129130
avifBool alphaPresent;
131+
avifBool csOptionsUpdated;
130132
} avifEncoderData;
131133

132134
static void avifEncoderDataDestroy(avifEncoderData * data);
@@ -317,6 +319,47 @@ void avifEncoderDestroy(avifEncoder * encoder)
317319
void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
318320
{
319321
avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
322+
encoder->data->csOptionsUpdated = AVIF_TRUE;
323+
}
324+
325+
avifBool avifEncoderCheckSettingsChange(avifEncoder * encoder, avifAddImageFlag * flags)
326+
{
327+
avifEncoder * lastEncoder = &encoder->data->lastEncoder;
328+
329+
// lastEncoder->data is used to mark that lastEncoder is initialized.
330+
if (lastEncoder->data == NULL) {
331+
lastEncoder->data = encoder->data;
332+
goto copy;
333+
}
334+
335+
if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
336+
(lastEncoder->timescale != encoder->timescale)) {
337+
return AVIF_FALSE;
338+
}
339+
340+
if ((lastEncoder->maxThreads != encoder->maxThreads) || (lastEncoder->minQuantizer != encoder->minQuantizer) ||
341+
(lastEncoder->maxQuantizer != encoder->maxQuantizer) || (lastEncoder->minQuantizerAlpha != encoder->minQuantizerAlpha) ||
342+
(lastEncoder->maxQuantizerAlpha != encoder->maxQuantizerAlpha) || (lastEncoder->tileRowsLog2 != encoder->tileRowsLog2) ||
343+
(lastEncoder->tileColsLog2 != encoder->tileColsLog2) || (lastEncoder->speed != encoder->speed) ||
344+
(encoder->data->csOptionsUpdated)) {
345+
*flags |= AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS;
346+
goto copy;
347+
}
348+
349+
copy:
350+
lastEncoder->codecChoice = encoder->codecChoice;
351+
lastEncoder->keyframeInterval = encoder->keyframeInterval;
352+
lastEncoder->timescale = encoder->timescale;
353+
lastEncoder->maxThreads = encoder->maxThreads;
354+
lastEncoder->minQuantizer = encoder->minQuantizer;
355+
lastEncoder->maxQuantizer = encoder->maxQuantizer;
356+
lastEncoder->minQuantizerAlpha = encoder->minQuantizerAlpha;
357+
lastEncoder->maxQuantizerAlpha = encoder->maxQuantizerAlpha;
358+
lastEncoder->tileRowsLog2 = encoder->tileRowsLog2;
359+
lastEncoder->tileColsLog2 = encoder->tileColsLog2;
360+
lastEncoder->speed = encoder->speed;
361+
encoder->data->csOptionsUpdated = AVIF_FALSE;
362+
return AVIF_TRUE;
320363
}
321364

322365
// This function is used in two codepaths:
@@ -606,6 +649,10 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
606649
return AVIF_RESULT_NO_CODEC_AVAILABLE;
607650
}
608651

652+
if (!avifEncoderCheckSettingsChange(encoder, &addImageFlags)) {
653+
return AVIF_RESULT_CANNOT_CHANGE_SETTING;
654+
}
655+
609656
// -----------------------------------------------------------------------
610657
// Validate images
611658

tests/gtest/avifchangesettingtest.cc

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,73 @@
11
// Copyright 2022 Yuan Tong. All rights reserved.
22
// SPDX-License-Identifier: BSD-2-Clause
33

4+
#include <map>
5+
#include <string>
6+
47
#include "avif/avif.h"
58
#include "aviftest_helpers.h"
69
#include "gtest/gtest.h"
710

811
namespace libavif {
912
namespace {
1013

11-
TEST(ChangeSettingTest, AOM) {
12-
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
13-
nullptr) {
14-
GTEST_SKIP() << "AOM encoder unavailable, skip test.";
14+
void TestEncodeDecode(avifCodecChoice codec,
15+
const std::map<std::string, std::string>& init_cs_options,
16+
bool can_encode, bool use_cq) {
17+
if (avifCodecName(codec, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) {
18+
GTEST_SKIP() << "Codec unavailable, skip test.";
1519
}
1620

1721
const uint32_t image_size = 512;
1822
testutil::AvifImagePtr image =
19-
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV444,
23+
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV420,
2024
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
2125
ASSERT_NE(image, nullptr);
2226
testutil::FillImageGradient(image.get());
2327

2428
// Encode
2529
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
2630
ASSERT_NE(encoder, nullptr);
27-
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
31+
encoder->codecChoice = codec;
2832
encoder->speed = AVIF_SPEED_FASTEST;
29-
encoder->minQuantizer = 63;
30-
encoder->maxQuantizer = 63;
3133
encoder->timescale = 1;
32-
// Force cbr mode, to ensure quality settings is fully complied.
33-
avifEncoderSetCodecSpecificOption(encoder.get(), "end-usage", "cbr");
34+
35+
for (const auto& option : init_cs_options) {
36+
avifEncoderSetCodecSpecificOption(encoder.get(), option.first.c_str(),
37+
option.second.c_str());
38+
}
39+
40+
if (use_cq) {
41+
encoder->minQuantizer = 0;
42+
encoder->maxQuantizer = 63;
43+
avifEncoderSetCodecSpecificOption(encoder.get(), "end-usage", "q");
44+
avifEncoderSetCodecSpecificOption(encoder.get(), "cq-level", "63");
45+
} else {
46+
encoder->minQuantizer = 63;
47+
encoder->maxQuantizer = 63;
48+
}
3449

3550
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
3651
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
3752
AVIF_RESULT_OK);
3853

39-
encoder->minQuantizer = 0;
40-
encoder->maxQuantizer = 0;
54+
if (use_cq) {
55+
avifEncoderSetCodecSpecificOption(encoder.get(), "cq-level", "0");
56+
} else {
57+
encoder->minQuantizer = 0;
58+
encoder->maxQuantizer = 0;
59+
}
60+
61+
if (!can_encode) {
62+
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
63+
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
64+
AVIF_RESULT_NOT_IMPLEMENTED);
65+
66+
return;
67+
}
68+
4169
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
42-
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS |
43-
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
70+
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
4471
AVIF_RESULT_OK);
4572

4673
testutil::AvifRwData encodedAvif;
@@ -53,7 +80,7 @@ TEST(ChangeSettingTest, AOM) {
5380
// The second frame is set to have far better quality,
5481
// and should be much bigger, so small amount of data at beginning
5582
// should be enough to decode the first frame.
56-
auto io = testutil::AvifIOCreateLimitedReader(
83+
avifIO* io = testutil::AvifIOCreateLimitedReader(
5784
avifIOCreateMemoryReader(encodedAvif.data, encodedAvif.size),
5885
encodedAvif.size / 10);
5986
ASSERT_NE(io, nullptr);
@@ -62,52 +89,32 @@ TEST(ChangeSettingTest, AOM) {
6289
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
6390
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_WAITING_ON_IO);
6491
((testutil::AvifIOLimitedReader*)io)->clamp =
65-
testutil::AvifIOLimitedReader::NoClamp;
92+
testutil::AvifIOLimitedReader::kNoClamp;
6693
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
6794
ASSERT_EQ(avifDecoderNextImage(decoder.get()),
6895
AVIF_RESULT_NO_IMAGES_REMAINING);
6996
}
7097

71-
TEST(ChangeSettingTest, RAV1E) {
72-
if (avifCodecName(AVIF_CODEC_CHOICE_RAV1E, AVIF_CODEC_FLAG_CAN_ENCODE) ==
73-
nullptr) {
74-
GTEST_SKIP() << "rav1e encoder unavailable, skip test.";
75-
}
76-
77-
const uint32_t image_size = 512;
78-
testutil::AvifImagePtr image =
79-
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV444,
80-
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
81-
ASSERT_NE(image, nullptr);
82-
testutil::FillImageGradient(image.get());
83-
84-
// Encode
85-
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
86-
ASSERT_NE(encoder, nullptr);
87-
encoder->codecChoice = AVIF_CODEC_CHOICE_RAV1E;
88-
encoder->speed = AVIF_SPEED_FASTEST;
89-
encoder->minQuantizer = 63;
90-
encoder->maxQuantizer = 63;
91-
encoder->timescale = 1;
98+
TEST(ChangeSettingTest, AOM) {
99+
TestEncodeDecode(AVIF_CODEC_CHOICE_AOM, {{"end-usage", "cbr"}}, true, false);
100+
}
92101

93-
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
94-
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
95-
AVIF_RESULT_OK);
102+
TEST(ChangeSettingTest, RAV1E) {
103+
TestEncodeDecode(AVIF_CODEC_CHOICE_RAV1E, {}, false, false);
104+
}
96105

97-
encoder->minQuantizer = 0;
98-
encoder->maxQuantizer = 0;
106+
TEST(ChangeSettingTest, SVT) {
107+
TestEncodeDecode(AVIF_CODEC_CHOICE_SVT, {}, false, false);
108+
}
99109

100-
// rav1e does not support updating settings.
101-
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
102-
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS |
103-
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
104-
AVIF_RESULT_NOT_IMPLEMENTED);
110+
TEST(ChangeSettingTest, ChangeCsOptions) {
111+
TestEncodeDecode(AVIF_CODEC_CHOICE_AOM, {}, true, true);
105112
}
106113

107-
TEST(ChangeSettingTest, SVT) {
108-
if (avifCodecName(AVIF_CODEC_CHOICE_SVT, AVIF_CODEC_FLAG_CAN_ENCODE) ==
114+
TEST(ChangeSettingTest, UnchangableSetting) {
115+
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
109116
nullptr) {
110-
GTEST_SKIP() << "SVT encoder unavailable, skip test.";
117+
GTEST_SKIP() << "Codec unavailable, skip test.";
111118
}
112119

113120
const uint32_t image_size = 512;
@@ -120,24 +127,20 @@ TEST(ChangeSettingTest, SVT) {
120127
// Encode
121128
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
122129
ASSERT_NE(encoder, nullptr);
123-
encoder->codecChoice = AVIF_CODEC_CHOICE_SVT;
130+
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
124131
encoder->speed = AVIF_SPEED_FASTEST;
132+
encoder->timescale = 1;
125133
encoder->minQuantizer = 63;
126134
encoder->maxQuantizer = 63;
127-
encoder->timescale = 1;
128135

129136
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
130137
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
131138
AVIF_RESULT_OK);
132139

133-
encoder->minQuantizer = 0;
134-
encoder->maxQuantizer = 0;
135-
136-
// SVT does not support updating settings.
140+
encoder->timescale = 2;
137141
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
138-
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS |
139-
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
140-
AVIF_RESULT_NOT_IMPLEMENTED);
142+
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
143+
AVIF_RESULT_CANNOT_CHANGE_SETTING);
141144
}
142145

143146
} // namespace

tests/gtest/aviftest_helpers.cc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
214214
static avifResult avifIOLimitedReaderRead(struct avifIO* io, uint32_t readFlags,
215215
uint64_t offset, size_t size,
216216
avifROData* out) {
217-
auto reader = (AvifIOLimitedReader*)io;
217+
auto reader = reinterpret_cast<AvifIOLimitedReader*>(io);
218218

219219
if (offset + size > reader->clamp) {
220220
return AVIF_RESULT_WAITING_ON_IO;
@@ -225,25 +225,25 @@ static avifResult avifIOLimitedReaderRead(struct avifIO* io, uint32_t readFlags,
225225
}
226226

227227
static void avifIOLimitedReaderDestroy(struct avifIO* io) {
228-
auto reader = (AvifIOLimitedReader*)io;
228+
auto reader = reinterpret_cast<AvifIOLimitedReader*>(io);
229229
reader->underlayIO->destroy(reader->underlayIO);
230230
delete reader;
231231
}
232232

233233
avifIO* AvifIOCreateLimitedReader(avifIO* underlayIO, uint64_t clamp) {
234-
return (avifIO*)new AvifIOLimitedReader{{
235-
avifIOLimitedReaderDestroy,
236-
avifIOLimitedReaderRead,
237-
nullptr,
238-
underlayIO->sizeHint,
239-
underlayIO->persistent,
240-
nullptr,
241-
},
242-
underlayIO,
243-
clamp};
234+
return reinterpret_cast<avifIO*>(
235+
new AvifIOLimitedReader{{
236+
avifIOLimitedReaderDestroy,
237+
avifIOLimitedReaderRead,
238+
nullptr,
239+
underlayIO->sizeHint,
240+
underlayIO->persistent,
241+
nullptr,
242+
},
243+
underlayIO,
244+
clamp});
244245
}
245246

246247
//------------------------------------------------------------------------------
247-
248248
} // namespace testutil
249249
} // namespace libavif

tests/gtest/aviftest_helpers.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#define LIBAVIF_TESTS_AVIFTEST_HELPERS_H_
66

77
#include <memory>
8+
#include <limits>
89

910
#include "avif/avif.h"
1011

@@ -62,7 +63,7 @@ bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
6263
//------------------------------------------------------------------------------
6364

6465
struct AvifIOLimitedReader {
65-
static constexpr uint64_t NoClamp = UINT64_MAX;
66+
static constexpr uint64_t kNoClamp = std::numeric_limits<uint64_t>::max();
6667

6768
avifIO io;
6869
avifIO* underlayIO;

0 commit comments

Comments
 (0)