-
Notifications
You must be signed in to change notification settings - Fork 254
Support update encoder settings during encoding #1033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support update encoder settings during encoding #1033
Conversation
tests/gtest/avifchangesettingtest.cc
Outdated
| // 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avifIO* is short enough, no need for auto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
tests/gtest/aviftest_helpers.h
Outdated
| //------------------------------------------------------------------------------ | ||
|
|
||
| struct AvifIOLimitedReader { | ||
| static constexpr uint64_t NoClamp = UINT64_MAX; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kNoClamp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
tests/gtest/aviftest_helpers.h
Outdated
| //------------------------------------------------------------------------------ | ||
|
|
||
| struct AvifIOLimitedReader { | ||
| static constexpr uint64_t NoClamp = UINT64_MAX; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::numeric_limits<uint64_t>::max() and #include <limits>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
tests/gtest/aviftest_helpers.cc
Outdated
| static avifResult avifIOLimitedReaderRead(struct avifIO* io, uint32_t readFlags, | ||
| uint64_t offset, size_t size, | ||
| avifROData* out) { | ||
| auto reader = (AvifIOLimitedReader*)io; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer reinterpret_cast<>() in C++ code (although here we could argue that it is C code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
tests/gtest/avifchangesettingtest.cc
Outdated
| namespace libavif { | ||
| namespace { | ||
|
|
||
| TEST(ChangeSettingTest, AOM) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a TEST_P with avifCodecChoice, /*allows_modifying_settings*/bool as parameters. It would shorten this file as the three tests share most logic and can be factored into a single one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TEST_P seems to test against different combinations of parameters. A helper function with some parameters looks more suitable here.
src/codec_aom.c
Outdated
| aom_codec_flags_t encoderFlags = 0; | ||
| if (image->depth > 8) { | ||
| encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
encoderFlags is only used at line 725 so the four lines above could be moved inside the condition.
(Unrelated note: I wonder what happens without this PR if you call avifEncoderAddImage() with different avifImage::depths)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
avifEncoderAddImage calls avifEncoderAddImageInternal, which already ensured subsequent images has same metadata with the first one at the very beginning "Validate images" section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avifEncoderAddImagecallsavifEncoderAddImageInternal, which already ensured subsequent images has same metadata with the first one at the very beginning "Validate images" section.
Thank you for taking a look.
Unfortunately the code you are referring to only makes sure all cells in a layer have the same features as the first cell, but they are not compared with the first layer.
Lines 642 to 649 in 1ce971e
| if ((cellImage->width != firstCell->width) || (cellImage->height != firstCell->height) || | |
| (cellImage->depth != firstCell->depth) || (cellImage->yuvFormat != firstCell->yuvFormat) || | |
| (cellImage->yuvRange != firstCell->yuvRange) || (cellImage->colorPrimaries != firstCell->colorPrimaries) || | |
| (cellImage->transferCharacteristics != firstCell->transferCharacteristics) || | |
| (cellImage->matrixCoefficients != firstCell->matrixCoefficients) || (!!cellImage->alphaPlane != !!firstCell->alphaPlane) || | |
| (cellImage->alphaPremultiplied != firstCell->alphaPremultiplied)) { | |
| return AVIF_RESULT_INVALID_IMAGE_GRID; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your careful check. The validation is indeed missing, and I'm adding it.
Thanks for doing that and also for adding a good unit test.
I would rather avoid adding a flag if there is no need to. I agree it adds a few functions and it requires a backup of
avifBool avifEncoderChanged(const avifEncoder* before, const avifEncoder* after) {
if (before->speed != after->speed ||
... ||
before->csOptions.count != after->csOptions.count) {
return AVIF_TRUE;
}
for (uint32_t i = 0; i < csOptions->count; ++i) {
avifCodecSpecificOption * entryBefore = &before->csOptions.entries[i];
avifCodecSpecificOption * entryAfter = &after->csOptions.entries[i];
if (strcmp(entryBefore->key, entryAfter->key) || strcmp(entryBefore->value, entryAfter->value)) {
// This could be a false positive if options got reordered but it is unlikely and it is not a big issue.
return AVIF_TRUE;
}
}
return AVIF_FALSE;
}
It's probably fine. I would be surprised if a single user changed |
6953709 to
5851a6d
Compare
|
I now agree with you. For codec specific options, I realized user can only change it using I was thinking about some users accidentally changed encoder settings, and found the program start to fail (when they are using rav1e or svt). But you are right this is certainly a bug and user should correct their usage. From comment of /*!
* \brief Desired dimensions for an externally triggered resize.
*
* When resize is triggered externally, the desired dimensions are stored in
* this struct until used in the next frame to be coded. These values are
* effective only for one frame and are reset after they are used.
*/
typedef struct {
int width; /*!< Desired resized width */
int height; /*!< Desired resized height */
} ResizePendingParams; |
y-guyon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From comment of
ResizePendingParamsin libaom,AOME_SET_SCALEMODEis reset after encoding each frame. I'm not sure if there are other options work in this way, so I keptAVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGSas a way to explicitly request reconfiguring.
Good catch. I still think libavif should have a consistent behavior, so why not remove AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS for simplicity and apply all codec flags at every avifEncoderAddImage() (or at least the ones we know will be reset by the codec)? The user can then remove them before doing another avifEncoderAddImage() if needed. The fact that it is not documented anywhere in avif.h makes me think it is more of a bug or oversight than a feature.
On the other hand, before this PR there was no way to modify a codec flag and take it into account past the first avifEncoderAddImage() call. I also wonder how bad calling avifProcessAOMOptionsPreInit() after aom_codec_enc_init() can be.
On top of that, the current documentation says that avifEncoderSetCodecSpecificOption() should not be called after avifEncoderAddImage():
Lines 1117 to 1123 in bbf459e
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. These | |
| // should be set as early as possible, preferably just after creating avifEncoder but before | |
| // performing any other actions. | |
| // key must be non-NULL, but passing a NULL value will delete that key, if it exists. | |
| // Setting an incorrect or unknown option for the current codec will cause errors of type | |
| // AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION from avifEncoderWrite() or avifEncoderAddImage(). | |
| AVIF_API void avifEncoderSetCodecSpecificOption(avifEncoder * encoder, const char * key, const char * value); |
This does not seem like a hard rule, just discouraged.
I can think of these options:
- Make it a hard rule, and return
AVIF_RESULT_CANNOT_CHANGE_SETTINGifavifEncoderSetCodecSpecificOption()is called afteravifEncoderAddImage(). This may be a limiting behavior but not a breaking change. No need forAVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS. - Remove the rule, update all/some codec flags at every
avifEncoderAddImage(), and make sure everything works as intended. No need forAVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS. This may require more work, especially testing, likely in another PR. - Remove the rule, discard all codec flags at every
avifEncoderAddImage(), and apply new ones added withavifEncoderSetCodecSpecificOption()at nextavifEncoderAddImage(). No need forAVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGS. This is closer to the libaom API spirit in my opinion, but slightly more obscure on the libavif side. It should be easier to implement as the burden of understanding libaom flags is forwarded to the libavif user, but it will require a lengthy and clear explanation as a comment inavif.h.
What do you think? Personally I would lean toward the last option.
189f381 to
0ebc70f
Compare
This function only set up value of
Thanks for remind this. I believe we should update the document, as we would definitely want to allow some options,
After reading AOM code again, I found that |
e203c69 to
539aeb7
Compare
| AVIF_RESULT_NOT_IMPLEMENTED, // a requested code path is not (yet) implemented | ||
| AVIF_RESULT_OUT_OF_MEMORY, | ||
| AVIF_RESULT_CANNOT_CHANGE_SETTING, // a setting that can't change is changed during encoding | ||
| AVIF_RESULT_INCOMPATIBLE_IMAGE // given image is not compatible with already encoded image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I do not mind adding values to this enum but maybe we should be careful not to have too many. There is avifEncoder::diag that was meant for that, so maybe we should just return INVALID_PARAMETER and use a descriptive diag string for rare-ish errors in future PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understand. If both are newly added, I would make AVIF_RESULT_INVALID_IMAGE_GRID and AVIF_RESULT_INCOMPATIBLE_IMAGE share one value, but AVIF_RESULT_INVALID_IMAGE_GRID is already there and is very specific about grid.
src/write.c
Outdated
| avifBool updateConfig = AVIF_FALSE; | ||
| if (!avifEncoderSettingsChanged(encoder, &updateConfig)) { | ||
| return AVIF_RESULT_CANNOT_CHANGE_SETTING; | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for else
wantehchang
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Yuan:
Thank you for the pull request and I am sorry about the late review. I reviewed this pull request while trying to import a new libavif snapshot into Google's internal repository today.
Regarding the rule for when avifEncoderSetCodecSpecificOption() can be called, the third option (quoted below) seems the best to me:
- Remove the rule, discard all codec flags at every
avifEncoderAddImage(), and apply new ones added withavifEncoderSetCodecSpecificOption()at nextavifEncoderAddImage().
It seems that you did not implement this option. Instead, you implemented the second option (quoted below):
- Remove the rule, update all/some codec flags at every
avifEncoderAddImage(), and make sure everything works as intended.
Did I understand it correctly?
| (!!imageMetadata->alphaPlane != !!firstCell->alphaPlane) || | ||
| (imageMetadata->alphaPremultiplied != firstCell->alphaPremultiplied) || | ||
| (encoder->data->alphaPresent && !firstCell->alphaPlane)) { | ||
| return AVIF_RESULT_INCOMPATIBLE_IMAGE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yuan:
I think the original comment at line 863 "Another frame in an image sequence" should be updated to "Another frame in an image sequence, or another layer in a layered image" or simply "Another frame in an image sequence or layered image".
I think the following new comment added at lines 866-870 should be removed.
// HEIF (ISO 23008-12:2017), Section 6.6.2.3.1:
// All input images shall have exactly the same width and height; call those tile_width and tile_height.
// MIAF (ISO 23000-22:2019), Section 7.3.11.4.1:
// All input images of a grid image item shall use the same coding format, chroma sampling format, and the
// same decoder configuration (see 7.3.6.2).
This new comment talks about a grid image, but we have already done the grid image checks at lines 682-714 above.
I believe we are dealing with an image sequence or layered image here. So we should allow the frames to have different widths and heights (width and height are specified in AV1 frame header), but we should certainly check if they have the same depth, yuvRange, etc (these are specified in AV1 sequence header).
Clean up the changes made in the following two pull requests to support updating encoder settings during encoding: AOMediaCodec#1033 AOMediaCodec#1058 In particular, restore the aomCodecEncodeImage() function in src/codec_aom.c to its original structure, plus a new block of code to handle encoder changes. Rename some functions and data members. Edit some comments and messages. In the avifEncoderChange enum, left-shift the unsigned int constant 1u because if we left-shift the signed int constant 1 by 31 bits, it will be shifted into the sign bit. Other miscellaneous cosmetic changes. AOMediaCodec#761
Clean up the changes made in the following two pull requests to support updating encoder settings during encoding: AOMediaCodec#1033 AOMediaCodec#1058 In particular, restore the aomCodecEncodeImage() function in src/codec_aom.c to its original structure, plus a new block of code to handle encoder changes. Rename some functions and data members. Edit some comments and messages. In the avifEncoderChange enum, left-shift the unsigned int constant 1u because if we left-shift the signed int constant 1 by 31 bits, it will be shifted into the sign bit. Other miscellaneous cosmetic changes. AOMediaCodec#761
Clean up the changes made in the following two pull requests to support updating encoder settings during encoding: AOMediaCodec#1033 AOMediaCodec#1058 In particular, restore the aomCodecEncodeImage() function in src/codec_aom.c to its original structure, plus a new block of code to handle encoder changes. Rename some functions and data members. Edit some comments and messages. In the avifEncoderChange enum, left-shift the unsigned int constant 1u because if we left-shift the signed int constant 1 by 31 bits, it will be shifted into the sign bit. Other miscellaneous cosmetic changes. AOMediaCodec#761
Clean up the changes made in the following two pull requests to support updating encoder settings during encoding: #1033 #1058 In particular, restore the aomCodecEncodeImage() function in src/codec_aom.c to its original structure, plus a new block of code to handle encoder changes. Rename some functions and data members. Edit some comments and messages. In the avifEncoderChange enum, left-shift the unsigned int constant 1u because if we left-shift the signed int constant 1 by 31 bits, it will be shifted into the sign bit. Other miscellaneous cosmetic changes. #761
This is the first part for #761.
@y-guyon, I'm using
AVIF_ADD_IMAGE_FLAG_UPDATE_SETTINGSdue to the following considerations, but I'm open to change:avifEncoder.csOptionsas well, but it's an kv map emulated using array, which is hard to do comparison.