Skip to content

Commit b049ebb

Browse files
committed
Add custom codec callback in avifEncoder
1 parent 69e57b6 commit b049ebb

File tree

5 files changed

+155
-12
lines changed

5 files changed

+155
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ The changes are relative to the previous release, unless the baseline is specifi
2929
avifGainMapMetadataDouble structs.
3030
* Add avif(Un)SignedFraction structs and avifDoubleTo(Un)SignedFraction
3131
utility functions.
32+
* Add customEncodeImageFunc, customEncodeFinishFunc and customEncodeData fields
33+
to the avifEncoder struct to override the AV1 codec.
3234

3335
## [1.1.1] - 2024-07-30
3436

include/avif/avif.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,15 +1414,31 @@ AVIF_API avifResult avifDecoderNthImageMaxExtent(const avifDecoder * decoder, ui
14141414
// ---------------------------------------------------------------------------
14151415
// avifEncoder
14161416

1417+
struct avifEncoder;
14171418
struct avifEncoderData;
14181419
struct avifCodecSpecificOptions;
1420+
struct avifEncoderCustomEncodeImageArgs;
14191421

14201422
typedef struct avifScalingMode
14211423
{
14221424
avifFraction horizontal;
14231425
avifFraction vertical;
14241426
} avifScalingMode;
14251427

1428+
// If enabled in avifEncoder, called for each coded image item (color, alpha, and gain map), grid cell,
1429+
// and sequence (color, alpha).
1430+
// Returns AVIF_RESULT_OK if it overrides the AV1 codec encoding pipeline for that element.
1431+
// Returns AVIF_RESULT_NO_CONTENT if the AV1 codec encoding pipeline should be run for that element.
1432+
// Returns an error otherwise.
1433+
typedef avifResult (*avifEncoderCustomEncodeImageFunc)(struct avifEncoder * encoder,
1434+
const avifImage * image,
1435+
const struct avifEncoderCustomEncodeImageArgs * args);
1436+
// Only called if avifEncoderCustomEncodeImageFunc returned AVIF_RESULT_OK.
1437+
// Returns AVIF_RESULT_OK every time it outputs an AV1 sample.
1438+
// Returns AVIF_RESULT_NO_IMAGES_REMAINING once all samples were output.
1439+
// Returns an error otherwise.
1440+
typedef avifResult (*avifEncoderCustomEncodeFinishFunc)(struct avifEncoder * encoder, avifROData * sample);
1441+
14261442
// Notes:
14271443
// * The avifEncoder struct may be extended in a future release. Code outside the libavif library
14281444
// must allocate avifEncoder by calling the avifEncoderCreate() function.
@@ -1495,6 +1511,13 @@ typedef struct avifEncoder
14951511

14961512
// Version 1.1.0 ends here. Add any new members after this line.
14971513

1514+
// Override the AV1 codec if both not null. Warning: Experimental feature.
1515+
// May be used to provide the payload of an AV1 coded image item or sequence.
1516+
avifEncoderCustomEncodeImageFunc customEncodeImageFunc;
1517+
avifEncoderCustomEncodeFinishFunc customEncodeFinishFunc;
1518+
// Ignored by libavif. May be used by customEncodeImageFunc and customEncodeFinishFunc to point to user data.
1519+
void * customEncodeData;
1520+
14981521
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
14991522
int qualityGainMap; // changeable encoder setting
15001523
#endif
@@ -1524,6 +1547,22 @@ typedef enum avifAddImageFlag
15241547
} avifAddImageFlag;
15251548
typedef uint32_t avifAddImageFlags;
15261549

1550+
// Arguments passed to avifEncoderCustomEncodeImageFunc by the avifEncoder instance.
1551+
typedef struct avifEncoderCustomEncodeImageArgs
1552+
{
1553+
// Encoding settings requested by the avifEncoder instance for the current AV1 coded image item or sequence.
1554+
avifAddImageFlags addImageFlags;
1555+
int quantizer; // AV1 quality setting in range [AVIF_QUANTIZER_BEST_QUALITY:AVIF_QUANTIZER_WORST_QUALITY].
1556+
int tileRowsLog2; // Logarithm in base 2 of the number of AV1 tile rows.
1557+
int tileColsLog2; // Logarithm in base 2 of the number of AV1 tile columns.
1558+
1559+
// Description of the current AV1 coded image item or sequence.
1560+
avifBool isAlpha; // True if the current AV1 image item or sequence holds the translucency layer.
1561+
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
1562+
avifBool isGainmap; // True if the current AV1 image item or sequence holds the HDR gainmap layer.
1563+
#endif
1564+
} avifEncoderCustomEncodeImageArgs;
1565+
15271566
// Multi-function alternative to avifEncoderWrite() for advanced features.
15281567
//
15291568
// Usage / function call order is:

src/write.c

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ typedef struct avifEncoderData
243243
// Fields specific to AV1/AV2
244244
const char * imageItemType; // "av01" for AV1 ("av02" for AV2 if AVIF_CODEC_AVM)
245245
const char * configPropName; // "av1C" for AV1 ("av2C" for AV2 if AVIF_CODEC_AVM)
246+
// Custom AV1 encoding function
247+
avifBool customEncodeImageFuncUsed;
246248
} avifEncoderData;
247249

248250
static void avifEncoderDataDestroy(avifEncoderData * data);
@@ -2104,17 +2106,38 @@ static avifResult avifEncoderAddImageInternal(avifEncoder * encoder,
21042106
// If alpha channel is present, set disableLaggedOutput to AVIF_TRUE. If the encoder supports it, this enables
21052107
// avifEncoderDataShouldForceKeyframeForAlpha to force a keyframe in the alpha channel whenever a keyframe has been
21062108
// encoded in the color channel for animated images.
2107-
avifResult encodeResult = item->codec->encodeImage(item->codec,
2108-
encoder,
2109-
cellImage,
2110-
isAlpha,
2111-
encoder->data->tileRowsLog2,
2112-
encoder->data->tileColsLog2,
2113-
quantizer,
2114-
encoderChanges,
2115-
/*disableLaggedOutput=*/encoder->data->alphaPresent,
2116-
addImageFlags,
2117-
item->encodeOutput);
2109+
const avifBool disableLaggedOutput = encoder->data->alphaPresent;
2110+
2111+
avifResult encodeResult = AVIF_RESULT_NO_CONTENT;
2112+
if (encoder->customEncodeImageFunc != NULL && encoder->customEncodeFinishFunc != NULL) {
2113+
const avifEncoderCustomEncodeImageArgs args = {
2114+
addImageFlags,
2115+
quantizer,
2116+
encoder->data->tileRowsLog2,
2117+
encoder->data->tileColsLog2,
2118+
isAlpha,
2119+
#if defined(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
2120+
item->itemCategory == AVIF_ITEM_GAIN_MAP
2121+
#endif
2122+
};
2123+
encodeResult = encoder->customEncodeImageFunc(encoder, cellImage, &args);
2124+
encoder->data->customEncodeImageFuncUsed = encodeResult != AVIF_RESULT_NO_CONTENT;
2125+
}
2126+
2127+
if (encodeResult == AVIF_RESULT_NO_CONTENT) {
2128+
encodeResult = item->codec->encodeImage(item->codec,
2129+
encoder,
2130+
cellImage,
2131+
isAlpha,
2132+
encoder->data->tileRowsLog2,
2133+
encoder->data->tileColsLog2,
2134+
quantizer,
2135+
encoderChanges,
2136+
disableLaggedOutput,
2137+
addImageFlags,
2138+
item->encodeOutput);
2139+
}
2140+
21182141
#if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM)
21192142
// Revert quality settings if they changed.
21202143
if (*encoderMinQuantizer != originalMinQuantizer || *encoderMaxQuantizer != originalMaxQuantizer) {
@@ -3094,7 +3117,14 @@ avifResult avifEncoderFinish(avifEncoder * encoder, avifRWData * output)
30943117
for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
30953118
avifEncoderItem * item = &encoder->data->items.item[itemIndex];
30963119
if (item->codec) {
3097-
if (!item->codec->encodeFinish(item->codec, item->encodeOutput)) {
3120+
if (encoder->data->customEncodeImageFuncUsed) {
3121+
avifROData sample = AVIF_DATA_EMPTY;
3122+
avifResult encodeResult;
3123+
while ((encodeResult = encoder->customEncodeFinishFunc(encoder, &sample)) != AVIF_RESULT_NO_IMAGES_REMAINING) {
3124+
AVIF_CHECKRES(encodeResult);
3125+
AVIF_CHECKRES(avifCodecEncodeOutputAddSample(item->encodeOutput, sample.data, sample.size, /*sync=*/AVIF_TRUE));
3126+
}
3127+
} else if (!item->codec->encodeFinish(item->codec, item->encodeOutput)) {
30983128
return avifGetErrorForItemCategory(item->itemCategory);
30993129
}
31003130

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ if(AVIF_ENABLE_GTEST)
109109
add_avif_gtest(avifcodectest)
110110
add_avif_internal_gtest_with_data(avifcolrconverttest)
111111
add_avif_internal_gtest(avifcolrtest)
112+
add_avif_gtest(avifcustomtest)
112113
add_avif_gtest_with_data(avifdecodetest)
113114
add_avif_gtest_with_data(avifdimgtest avifincrtest_helpers)
114115
add_avif_gtest_with_data(avifencodetest)
@@ -354,6 +355,7 @@ if(AVIF_CODEC_AVM_ENABLED)
354355
avifchangesettingtest
355356
avifcllitest
356357
avifcolrconverttest
358+
avifcustomtest
357359
avifdimgtest
358360
avifencodetest
359361
avifgridapitest

tests/gtest/avifcustomtest.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2024 Google LLC
2+
// SPDX-License-Identifier: BSD-2-Clause
3+
4+
#include <algorithm>
5+
6+
#include "avif/avif.h"
7+
#include "aviftest_helpers.h"
8+
#include "gtest/gtest.h"
9+
10+
namespace avif {
11+
namespace {
12+
13+
avifResult CustomEncodeImageFunc(avifEncoder* encoder, const avifImage*,
14+
const avifEncoderCustomEncodeImageArgs*) {
15+
if (encoder->customEncodeData != NULL) {
16+
return AVIF_RESULT_OK; // Overrides the AV1 codec encoding pipeline.
17+
} else {
18+
return AVIF_RESULT_NO_CONTENT; // Lets libavif encode the image item.
19+
}
20+
}
21+
22+
avifResult CustomEncodeFinishFunc(avifEncoder* encoder, avifROData* sample) {
23+
avifROData* av1_payload =
24+
reinterpret_cast<avifROData*>(encoder->customEncodeData);
25+
if (av1_payload->size != 0) {
26+
*sample = *av1_payload;
27+
*av1_payload = AVIF_DATA_EMPTY;
28+
return AVIF_RESULT_OK; // Outputs a sample.
29+
} else {
30+
return AVIF_RESULT_NO_IMAGES_REMAINING; // Done.
31+
}
32+
}
33+
34+
TEST(BasicTest, EncodeDecode) {
35+
ImagePtr image = testutil::CreateImage(12, 34, 8, AVIF_PIXEL_FORMAT_YUV420,
36+
AVIF_PLANES_YUV);
37+
ASSERT_NE(image, nullptr);
38+
testutil::FillImageGradient(image.get());
39+
40+
EncoderPtr encoder(avifEncoderCreate());
41+
ASSERT_NE(encoder, nullptr);
42+
testutil::AvifRwData encoded;
43+
ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded),
44+
AVIF_RESULT_OK);
45+
46+
const uint8_t* kMdat = reinterpret_cast<const uint8_t*>("mdat");
47+
const uint8_t* mdat_position =
48+
std::search(encoded.data, encoded.data + encoded.size, kMdat, kMdat + 4);
49+
ASSERT_NE(mdat_position, encoded.data + encoded.size);
50+
avifROData av1_payload{
51+
mdat_position + 4,
52+
static_cast<size_t>((encoded.data + encoded.size) - (mdat_position + 4))};
53+
54+
EncoderPtr encoder_custom(avifEncoderCreate());
55+
ASSERT_NE(encoder_custom, nullptr);
56+
encoder_custom->customEncodeData = reinterpret_cast<void*>(&av1_payload);
57+
encoder_custom->customEncodeImageFunc = CustomEncodeImageFunc;
58+
encoder_custom->customEncodeFinishFunc = CustomEncodeFinishFunc;
59+
testutil::AvifRwData encoded_custom;
60+
ASSERT_EQ(
61+
avifEncoderWrite(encoder_custom.get(), image.get(), &encoded_custom),
62+
AVIF_RESULT_OK);
63+
64+
ASSERT_EQ(encoded.size, encoded_custom.size);
65+
EXPECT_TRUE(std::equal(encoded.data, encoded.data + encoded.size,
66+
encoded_custom.data));
67+
}
68+
69+
} // namespace
70+
} // namespace avif

0 commit comments

Comments
 (0)