Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
16 changes: 12 additions & 4 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,12 +1038,17 @@ 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 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)
avifCodecChoice codecChoice;

// settings (see Notes above)
int keyframeInterval; // How many frames between automatic forced keyframes; 0 to disable (default).
uint64_t timescale; // timescale of the media (Hz)
// AV1 encoder settings.
int maxThreads;
int minQuantizer;
int maxQuantizer;
Expand All @@ -1051,8 +1057,6 @@ typedef struct avifEncoder
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)

// stats from the most recent write
avifIOStats ioStats;
Expand All @@ -1079,7 +1083,11 @@ typedef enum avifAddImageFlag
// 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)
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
104 changes: 60 additions & 44 deletions src/codec_aom.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct avifCodecInternal
#if defined(AVIF_CODEC_AOM_ENCODE)
avifBool encoderInitialized;
aom_codec_ctx_t encoder;
struct aom_codec_enc_cfg cfg;
avifPixelFormatInfo formatInfo;
aom_img_fmt_t aomFormat;
avifBool monochromeEnabled;
Expand Down Expand Up @@ -528,7 +529,7 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
avifAddImageFlags addImageFlags,
avifCodecEncodeOutput * output)
{
if (!codec->internal->encoderInitialized) {
if (!codec->internal->encoderInitialized || (addImageFlags & AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS)) {
// Map encoder speed to AOM usage + CpuUsed:
// Speed 0: GoodQuality CpuUsed 0
// Speed 1: GoodQuality CpuUsed 1
Expand Down Expand Up @@ -586,37 +587,41 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
}
}

codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
return AVIF_RESULT_UNKNOWN_ERROR;
}
struct aom_codec_enc_cfg * cfg = &codec->internal->cfg;

avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);
aom_codec_iface_t * encoderInterface = NULL;
if (!codec->internal->encoderInitialized) {
codec->internal->aomFormat = avifImageCalcAOMFmt(image, alpha);
if (codec->internal->aomFormat == AOM_IMG_FMT_NONE) {
return AVIF_RESULT_UNKNOWN_ERROR;
}

aom_codec_iface_t * encoderInterface = aom_codec_av1_cx();
struct aom_codec_enc_cfg cfg;
aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, &cfg, aomUsage);
if (err != AOM_CODEC_OK) {
avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
return AVIF_RESULT_UNKNOWN_ERROR;
avifGetPixelFormatInfo(image->yuvFormat, &codec->internal->formatInfo);

encoderInterface = aom_codec_av1_cx();
aom_codec_err_t err = aom_codec_enc_config_default(encoderInterface, cfg, aomUsage);
if (err != AOM_CODEC_OK) {
avifDiagnosticsPrintf(codec->diag, "aom_codec_enc_config_default() failed: %s", aom_codec_err_to_string(err));
return AVIF_RESULT_UNKNOWN_ERROR;
}
}

// Set our own default cfg.rc_end_usage value, which may differ from libaom's default.
// Set our own default cfg->rc_end_usage value, which may differ from libaom's default.
switch (aomUsage) {
case AOM_USAGE_GOOD_QUALITY:
// libaom's default is AOM_VBR. Change the default to AOM_Q since we don't need to
// hit a certain target bit rate. It's easier to control the worst quality in Q
// mode.
cfg.rc_end_usage = AOM_Q;
cfg->rc_end_usage = AOM_Q;
break;
case AOM_USAGE_REALTIME:
// For real-time mode we need to use CBR rate control mode. AOM_Q doesn't fit the
// rate control requirements for real-time mode. CBR does.
cfg.rc_end_usage = AOM_CBR;
cfg->rc_end_usage = AOM_CBR;
break;
#if defined(AOM_USAGE_ALL_INTRA)
case AOM_USAGE_ALL_INTRA:
cfg.rc_end_usage = AOM_Q;
cfg->rc_end_usage = AOM_Q;
break;
#endif
}
Expand Down Expand Up @@ -655,31 +660,31 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
}
}

cfg.g_profile = seqProfile;
cfg.g_bit_depth = image->depth;
cfg.g_input_bit_depth = image->depth;
cfg.g_w = image->width;
cfg.g_h = image->height;
cfg->g_profile = seqProfile;
cfg->g_bit_depth = image->depth;
cfg->g_input_bit_depth = image->depth;
cfg->g_w = image->width;
cfg->g_h = image->height;
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_SINGLE) {
// Set the maximum number of frames to encode to 1. This instructs
// libaom to set still_picture and reduced_still_picture_header to
// 1 in AV1 sequence headers.
cfg.g_limit = 1;
cfg->g_limit = 1;

// Use the default settings of the new AOM_USAGE_ALL_INTRA (added in
// https://crbug.com/aomedia/2959).
//
// Set g_lag_in_frames to 0 to reduce the number of frame buffers
// (from 20 to 2) in libaom's lookahead structure. This reduces
// memory consumption when encoding a single image.
cfg.g_lag_in_frames = 0;
cfg->g_lag_in_frames = 0;
// Disable automatic placement of key frames by the encoder.
cfg.kf_mode = AOM_KF_DISABLED;
cfg->kf_mode = AOM_KF_DISABLED;
// Tell libaom that all frames will be key frames.
cfg.kf_max_dist = 0;
cfg->kf_max_dist = 0;
}
if (encoder->maxThreads > 1) {
cfg.g_threads = encoder->maxThreads;
cfg->g_threads = encoder->maxThreads;
}

int minQuantizer = AVIF_CLAMP(encoder->minQuantizer, 0, 63);
Expand All @@ -689,41 +694,52 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
maxQuantizer = AVIF_CLAMP(encoder->maxQuantizerAlpha, 0, 63);
}
avifBool lossless = ((minQuantizer == AVIF_QUANTIZER_LOSSLESS) && (maxQuantizer == AVIF_QUANTIZER_LOSSLESS));
cfg.rc_min_quantizer = minQuantizer;
cfg.rc_max_quantizer = maxQuantizer;
cfg->rc_min_quantizer = minQuantizer;
cfg->rc_max_quantizer = maxQuantizer;

codec->internal->monochromeEnabled = AVIF_FALSE;
if (aomVersion > aomVersion_2_0_0) {
// There exists a bug in libaom's chroma_check() function where it will attempt to
// access nonexistent UV planes when encoding monochrome at faster libavif "speeds". It
// was fixed shortly after the 2.0.0 libaom release, and the fix exists in both the
// master and applejack branches. This ensures that the next version *after* 2.0.0 will
// have the fix, and we must avoid cfg.monochrome until then.
// have the fix, and we must avoid cfg->monochrome until then.
//
// Bugfix Change-Id: https://aomedia-review.googlesource.com/q/I26a39791f820b4d4e1d63ff7141f594c3c7181f5

if (alpha || (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400)) {
codec->internal->monochromeEnabled = AVIF_TRUE;
cfg.monochrome = 1;
cfg->monochrome = 1;
}
}

if (!avifProcessAOMOptionsPreInit(codec, alpha, &cfg)) {
if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) {
return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION;
}

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",
aom_codec_error(&codec->internal->encoder),
aom_codec_error_detail(&codec->internal->encoder));
return AVIF_RESULT_UNKNOWN_ERROR;
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",
aom_codec_error(&codec->internal->encoder),
aom_codec_error_detail(&codec->internal->encoder));
return AVIF_RESULT_UNKNOWN_ERROR;
}
codec->internal->encoderInitialized = AVIF_TRUE;
} else {
if (aom_codec_enc_config_set(&codec->internal->encoder, cfg) != AOM_CODEC_OK) {
avifDiagnosticsPrintf(codec->diag,
"aom_codec_enc_config_set() failed: %s: %s",
aom_codec_error(&codec->internal->encoder),
aom_codec_error_detail(&codec->internal->encoder));
return AVIF_RESULT_UNKNOWN_ERROR;
}
}
codec->internal->encoderInitialized = AVIF_TRUE;

if (lossless) {
aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, 1);
Expand Down Expand Up @@ -754,8 +770,8 @@ static avifResult aomCodecEncodeImage(avifCodec * codec,
// set the min and max quantizers in the avifEncoder struct. If this is the case, set
// cq-level to a reasonable value for the user, otherwise the default cq-level
// (currently 10) will be unknowingly used.
assert(cfg.rc_end_usage == AOM_Q);
unsigned int cqLevel = (cfg.rc_min_quantizer + cfg.rc_max_quantizer) / 2;
assert(cfg->rc_end_usage == AOM_Q);
unsigned int cqLevel = (cfg->rc_min_quantizer + cfg->rc_max_quantizer) / 2;
aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel);
}
#endif
Expand Down
4 changes: 4 additions & 0 deletions src/codec_rav1e.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ static avifResult rav1eCodecEncodeImage(avifCodec * codec,
uint32_t addImageFlags,
avifCodecEncodeOutput * output)
{
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult result = AVIF_RESULT_UNKNOWN_ERROR;

RaConfig * rav1eConfig = NULL;
Expand Down
4 changes: 4 additions & 0 deletions src/codec_svt.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ static avifResult svtCodecEncodeImage(avifCodec * codec,
uint32_t addImageFlags,
avifCodecEncodeOutput * output)
{
if (addImageFlags & AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult result = AVIF_RESULT_UNKNOWN_ERROR;
EbColorFormat color_format = EB_YUV420;
EbBufferHeaderType * input_buffer = NULL;
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
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ if(AVIF_ENABLE_GTEST)
target_link_libraries(avify4mtest aviftest_helpers avif_apps ${GTEST_BOTH_LIBRARIES})
target_include_directories(avify4mtest PRIVATE ${GTEST_INCLUDE_DIRS})
add_test(NAME avify4mtest COMMAND avify4mtest)

add_executable(avifchangesettingtest gtest/avifchangesettingtest.cc)
target_link_libraries(avifchangesettingtest aviftest_helpers avif_apps ${GTEST_BOTH_LIBRARIES})
target_include_directories(avifchangesettingtest PRIVATE ${GTEST_INCLUDE_DIRS})
add_test(NAME avifchangesettingtest COMMAND avifchangesettingtest)
else()
message(STATUS "Most tests are disabled because AVIF_ENABLE_GTEST is OFF.")
endif()
Expand Down
Loading