Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions include/avif/avif.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ typedef enum avifResult
AVIF_RESULT_WAITING_ON_IO, // similar to EAGAIN/EWOULDBLOCK, this means the avifIO doesn't have necessary data available yet
AVIF_RESULT_INVALID_ARGUMENT, // an argument passed into this function is invalid
AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented
AVIF_RESULT_OUT_OF_MEMORY
AVIF_RESULT_OUT_OF_MEMORY,
AVIF_RESULT_CANNOT_CHANGE_SETTING, // a setting that can't change is changed during encoding
} avifResult;

AVIF_API const char * avifResultToString(avifResult result);
Expand Down Expand Up @@ -1037,8 +1038,8 @@ struct avifCodecSpecificOptions;
// 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 csOptions will be applied to AV1 encoder before encoding first image, and images
// added with AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS flag.
// * AV1 encoder settings and codecSpecificOptions will be applied to AV1 encoder when changed or when
// AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS flag is explicitly requested.
typedef struct avifEncoder
{
// Defaults to AVIF_CODEC_CHOICE_AUTO: Preference determined by order in availableCodecs table (avif.c)
Expand Down Expand Up @@ -1085,6 +1086,7 @@ typedef enum avifAddImageFlag
AVIF_ADD_IMAGE_FLAG_SINGLE = (1 << 1),

// Use this flag to update encode settings of AV1 encoder.
// This is enabled automatically if encoder settings is changed.
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS = (1 << 2)
} avifAddImageFlag;
typedef uint32_t avifAddImageFlags;
Expand Down
1 change: 1 addition & 0 deletions src/avif.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const char * avifResultToString(avifResult result)
case AVIF_RESULT_INVALID_ARGUMENT: return "Invalid argument";
case AVIF_RESULT_NOT_IMPLEMENTED: return "Not implemented";
case AVIF_RESULT_OUT_OF_MEMORY: return "Out of memory";
case AVIF_RESULT_CANNOT_CHANGE_SETTING: return "Can not change some settings during encoding";
case AVIF_RESULT_UNKNOWN_ERROR:
default:
break;
Expand Down
9 changes: 5 additions & 4 deletions src/codec_aom.c
Original file line number Diff line number Diff line change
Expand Up @@ -717,11 +717,12 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
}

aom_codec_flags_t encoderFlags = 0;
if (image->depth > 8) {
encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH;
}
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",
Expand Down
47 changes: 47 additions & 0 deletions src/write.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,13 @@ typedef struct avifEncoderData
{
avifEncoderItemArray items;
avifEncoderFrameArray frames;
avifEncoder lastEncoder;
avifImage * imageMetadata;
uint16_t lastItemID;
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);
Expand Down Expand Up @@ -317,6 +319,47 @@ void avifEncoderDestroy(avifEncoder * encoder)
void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value)
{
avifCodecSpecificOptionsSet(encoder->csOptions, key, value);
encoder->data->csOptionsUpdated = AVIF_TRUE;
}

avifBool avifEncoderCheckSettingsChange(avifEncoder * encoder, avifAddImageFlag * flags)
{
avifEncoder * lastEncoder = &encoder->data->lastEncoder;

// lastEncoder->data is used to mark that lastEncoder is initialized.
if (lastEncoder->data == NULL) {
lastEncoder->data = encoder->data;
goto copy;
}

if ((lastEncoder->codecChoice != encoder->codecChoice) || (lastEncoder->keyframeInterval != encoder->keyframeInterval) ||
(lastEncoder->timescale != encoder->timescale)) {
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)) {
*flags |= AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS;
goto copy;
}

copy:
lastEncoder->codecChoice = encoder->codecChoice;
lastEncoder->keyframeInterval = encoder->keyframeInterval;
lastEncoder->timescale = encoder->timescale;
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 = AVIF_FALSE;
return AVIF_TRUE;
}

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

if (!avifEncoderCheckSettingsChange(encoder, &addImageFlags)) {
return AVIF_RESULT_CANNOT_CHANGE_SETTING;
}

// -----------------------------------------------------------------------
// Validate images

Expand Down
121 changes: 62 additions & 59 deletions tests/gtest/avifchangesettingtest.cc
Original file line number Diff line number Diff line change
@@ -1,46 +1,73 @@
// Copyright 2022 Yuan Tong. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause

#include <map>
#include <string>

#include "avif/avif.h"
#include "aviftest_helpers.h"
#include "gtest/gtest.h"

namespace libavif {
namespace {

TEST(ChangeSettingTest, AOM) {
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "AOM encoder unavailable, skip test.";
void TestEncodeDecode(avifCodecChoice codec,
const std::map<std::string, std::string>& init_cs_options,
bool can_encode, bool use_cq) {
if (avifCodecName(codec, AVIF_CODEC_FLAG_CAN_ENCODE) == nullptr) {
GTEST_SKIP() << "Codec unavailable, skip test.";
}

const uint32_t image_size = 512;
testutil::AvifImagePtr image =
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV444,
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV420,
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());

// Encode
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
encoder->codecChoice = codec;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->minQuantizer = 63;
encoder->maxQuantizer = 63;
encoder->timescale = 1;
// Force cbr mode, to ensure quality settings is fully complied.
avifEncoderSetCodecSpecificOption(encoder.get(), "end-usage", "cbr");

for (const auto& option : init_cs_options) {
avifEncoderSetCodecSpecificOption(encoder.get(), option.first.c_str(),
option.second.c_str());
}

if (use_cq) {
encoder->minQuantizer = 0;
encoder->maxQuantizer = 63;
avifEncoderSetCodecSpecificOption(encoder.get(), "end-usage", "q");
avifEncoderSetCodecSpecificOption(encoder.get(), "cq-level", "63");
} else {
encoder->minQuantizer = 63;
encoder->maxQuantizer = 63;
}

ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);

encoder->minQuantizer = 0;
encoder->maxQuantizer = 0;
if (use_cq) {
avifEncoderSetCodecSpecificOption(encoder.get(), "cq-level", "0");
} else {
encoder->minQuantizer = 0;
encoder->maxQuantizer = 0;
}

if (!can_encode) {
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_NOT_IMPLEMENTED);

return;
}

ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS |
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);

testutil::AvifRwData encodedAvif;
Expand All @@ -53,7 +80,7 @@ TEST(ChangeSettingTest, AOM) {
// The second frame is set to have far better quality,
// and should be much bigger, so small amount of data at beginning
// should be enough to decode the first frame.
auto io = testutil::AvifIOCreateLimitedReader(
avifIO* io = testutil::AvifIOCreateLimitedReader(
avifIOCreateMemoryReader(encodedAvif.data, encodedAvif.size),
encodedAvif.size / 10);
ASSERT_NE(io, nullptr);
Expand All @@ -62,52 +89,32 @@ TEST(ChangeSettingTest, AOM) {
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_WAITING_ON_IO);
((testutil::AvifIOLimitedReader*)io)->clamp =
testutil::AvifIOLimitedReader::NoClamp;
testutil::AvifIOLimitedReader::kNoClamp;
ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK);
ASSERT_EQ(avifDecoderNextImage(decoder.get()),
AVIF_RESULT_NO_IMAGES_REMAINING);
}

TEST(ChangeSettingTest, RAV1E) {
if (avifCodecName(AVIF_CODEC_CHOICE_RAV1E, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "rav1e encoder unavailable, skip test.";
}

const uint32_t image_size = 512;
testutil::AvifImagePtr image =
testutil::CreateImage(image_size, image_size, 8, AVIF_PIXEL_FORMAT_YUV444,
AVIF_PLANES_YUV, AVIF_RANGE_FULL);
ASSERT_NE(image, nullptr);
testutil::FillImageGradient(image.get());

// Encode
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_RAV1E;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->minQuantizer = 63;
encoder->maxQuantizer = 63;
encoder->timescale = 1;
TEST(ChangeSettingTest, AOM) {
TestEncodeDecode(AVIF_CODEC_CHOICE_AOM, {{"end-usage", "cbr"}}, true, false);
}

ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);
TEST(ChangeSettingTest, RAV1E) {
TestEncodeDecode(AVIF_CODEC_CHOICE_RAV1E, {}, false, false);
}

encoder->minQuantizer = 0;
encoder->maxQuantizer = 0;
TEST(ChangeSettingTest, SVT) {
TestEncodeDecode(AVIF_CODEC_CHOICE_SVT, {}, false, false);
}

// rav1e does not support updating settings.
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS |
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_NOT_IMPLEMENTED);
TEST(ChangeSettingTest, ChangeCsOptions) {
TestEncodeDecode(AVIF_CODEC_CHOICE_AOM, {}, true, true);
}

TEST(ChangeSettingTest, SVT) {
if (avifCodecName(AVIF_CODEC_CHOICE_SVT, AVIF_CODEC_FLAG_CAN_ENCODE) ==
TEST(ChangeSettingTest, UnchangableSetting) {
if (avifCodecName(AVIF_CODEC_CHOICE_AOM, AVIF_CODEC_FLAG_CAN_ENCODE) ==
nullptr) {
GTEST_SKIP() << "SVT encoder unavailable, skip test.";
GTEST_SKIP() << "Codec unavailable, skip test.";
}

const uint32_t image_size = 512;
Expand All @@ -120,24 +127,20 @@ TEST(ChangeSettingTest, SVT) {
// Encode
testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
ASSERT_NE(encoder, nullptr);
encoder->codecChoice = AVIF_CODEC_CHOICE_SVT;
encoder->codecChoice = AVIF_CODEC_CHOICE_AOM;
encoder->speed = AVIF_SPEED_FASTEST;
encoder->timescale = 1;
encoder->minQuantizer = 63;
encoder->maxQuantizer = 63;
encoder->timescale = 1;

ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_OK);

encoder->minQuantizer = 0;
encoder->maxQuantizer = 0;

// SVT does not support updating settings.
encoder->timescale = 2;
ASSERT_EQ(avifEncoderAddImage(encoder.get(), image.get(), 1,
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS |
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_NOT_IMPLEMENTED);
AVIF_ADD_IMAGE_FLAG_FORCE_KEYFRAME),
AVIF_RESULT_CANNOT_CHANGE_SETTING);
}

} // namespace
Expand Down
26 changes: 13 additions & 13 deletions tests/gtest/aviftest_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
static avifResult avifIOLimitedReaderRead(struct avifIO* io, uint32_t readFlags,
uint64_t offset, size_t size,
avifROData* out) {
auto reader = (AvifIOLimitedReader*)io;
auto reader = reinterpret_cast<AvifIOLimitedReader*>(io);

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

static void avifIOLimitedReaderDestroy(struct avifIO* io) {
auto reader = (AvifIOLimitedReader*)io;
auto reader = reinterpret_cast<AvifIOLimitedReader*>(io);
reader->underlayIO->destroy(reader->underlayIO);
delete reader;
}

avifIO* AvifIOCreateLimitedReader(avifIO* underlayIO, uint64_t clamp) {
return (avifIO*)new AvifIOLimitedReader{{
avifIOLimitedReaderDestroy,
avifIOLimitedReaderRead,
nullptr,
underlayIO->sizeHint,
underlayIO->persistent,
nullptr,
},
underlayIO,
clamp};
return reinterpret_cast<avifIO*>(
new AvifIOLimitedReader{{
avifIOLimitedReaderDestroy,
avifIOLimitedReaderRead,
nullptr,
underlayIO->sizeHint,
underlayIO->persistent,
nullptr,
},
underlayIO,
clamp});
}

//------------------------------------------------------------------------------

} // namespace testutil
} // namespace libavif
3 changes: 2 additions & 1 deletion tests/gtest/aviftest_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifndef LIBAVIF_TESTS_AVIFTEST_HELPERS_H_
#define LIBAVIF_TESTS_AVIFTEST_HELPERS_H_

#include <limits>
#include <memory>

#include "avif/avif.h"
Expand Down Expand Up @@ -62,7 +63,7 @@ bool AreImagesEqual(const avifImage& image1, const avifImage& image2,
//------------------------------------------------------------------------------

struct AvifIOLimitedReader {
static constexpr uint64_t NoClamp = UINT64_MAX;
static constexpr uint64_t kNoClamp = std::numeric_limits<uint64_t>::max();

avifIO io;
avifIO* underlayIO;
Expand Down