-
Notifications
You must be signed in to change notification settings - Fork 261
Correctly update encoder settings, clear csOptions after avifEncoderAddImage() #1058
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
Correctly update encoder settings, clear csOptions after avifEncoderAddImage() #1058
Conversation
f90ec00 to
331c5aa
Compare
331c5aa to
9a6758c
Compare
|
@y-guyon , @wantehchang : Please review, thanks.
After rethink I agree this is better.
Maybe "the first image" and "subsequent images" is enough?
Done. Also removed one of the two conditions that effectively checking the same thing.
Done. I wonder if libavif decoder handles frames with different widths and heights correctly. |
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.
It seems tests/gtest/avifchangesettingtest.cc passes as is, even with the csOptions behavior change in this PR. Ideally the coverage should be increased to catch future any csOptions behavior change. It can be done in another PR.
src/codec_aom.c
Outdated
| return AVIF_RESULT_UNKNOWN_ERROR; | ||
| } | ||
| } else { | ||
| cfg->g_usage = aomUsage; |
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.
I do not see where does this come from. A comment would be useful.
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. encoder->speed decides aomUsage and aomCpuUsed, so updating g_usage here. In "init" code path this field is set in aom_codec_enc_config_default().
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.
I see. Your message helped me more than the comment though, so maybe consider using the former as the latter. Something like // aomUsage was taken into account by aom_codec_enc_config_default() but it can only be manually updated afterwards.
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
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.
Changing cfg->g_usage like this is risky, because some other fields in cfg may be tightly coupled to cfg->g_usage and need to be changed accordinly. It is best to avoid this potential issue, by not allowing encoder->speed to change.
Similarly, it is best to not allow encoder->maxThreads to change.
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.
I was imagine some time-constrained encoding application that starts with better quality and tries to speed up if it's too slow. But if changing speed is risky, then I agree we'd better avoid that.
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.
I looked into this. I found that libaom's test/datarate_test.h changes AOME_SET_CPUUSED dynamically in the middle of a video sequence (search for speed_change_test_ in the file), and I also saw webrtc's libaom_av1_encoder.cc file do this. So I believe libaom supports this. However, libavif may need to change cfg->g_usage at the same time it changes AOME_SET_CPUUSED, so it is best to avoid this until it's really needed.
tongyuantongyu
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.
Ideally the coverage should be increased to catch future any csOptions behavior change.
csOptions field is internal so we can't directly check it. And given that options that can be set in this way are probably all persistent, it's also hard to tell from the encoded file. Maybe we need a test encoder.
src/codec_aom.c
Outdated
| return AVIF_RESULT_UNKNOWN_ERROR; | ||
| } | ||
| } else { | ||
| cfg->g_usage = aomUsage; |
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. encoder->speed decides aomUsage and aomCpuUsed, so updating g_usage here. In "init" code path this field is set in aom_codec_enc_config_default().
src/codec_aom.c
Outdated
| int tileRowsLog2 = AVIF_CLAMP(encoder->tileRowsLog2, 0, 6); | ||
| aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_ROWS, tileRowsLog2); | ||
| int tileColsLog2 = AVIF_CLAMP(encoder->tileColsLog2, 0, 6); | ||
| aom_codec_control(&codec->internal->encoder, AV1E_SET_TILE_COLUMNS, tileColsLog2); |
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.
We should make these aom_codec_control() calls only when necessary. Is it possible to determine which calls are necessary when updateConfig is true?
The reason is that aom_codec_control() is not as cheap as it looks, if you trace its code. It will cause libaom to validate all of its configuration settings. See https://crbug.com/aomedia/2732.
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.
These options can also be set using aom_codec_set_option().
If we only sync when these value changes, we can compare with previous value to find out which value needs to be updated; If these values should always be reflected in encoder, then we have to update them each time, as we can't know the real current value.
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,
I suggest some changes. I will need to carefully review the current code in src/codec_aom.c after this pull request is merged.
include/avif/avif.h
Outdated
| // key must be non-NULL, but passing a NULL value will delete that key, if it exists. | ||
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs, | ||
| // to be sent to codec at the next avifEncoderAddImage() call. | ||
| // See the codec documentation to know if a setting is permanent or applied only to the next frame. |
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.
Nit: change "permanent" to "persistent" or "sticky"
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
include/avif/avif.h
Outdated
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. | ||
| // key must be non-NULL, but passing a NULL value will delete that key, if it exists. | ||
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs, | ||
| // to be sent to codec at the next avifEncoderAddImage() call. |
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.
Nit: add "the" before "codec"
Optional: Perhaps we can say "to be consumed by the codec ..." The word "consume" more strongly implies that the key/value pairs will be cleared after they are sent to the codec.
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
include/avif/avif.h
Outdated
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs, | ||
| // to be sent to codec at the next avifEncoderAddImage() call. | ||
| // See the codec documentation to know if a setting is permanent or applied only to the next frame. | ||
| // key must be non-NULL, but passing a NULL value will delete that pending key, if it exists. |
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.
Nit: change "that" to "the"
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
src/codec_aom.c
Outdated
| return AVIF_RESULT_UNKNOWN_ERROR; | ||
| } | ||
| } else { | ||
| cfg->g_usage = aomUsage; |
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.
Changing cfg->g_usage like this is risky, because some other fields in cfg may be tightly coupled to cfg->g_usage and need to be changed accordinly. It is best to avoid this potential issue, by not allowing encoder->speed to change.
Similarly, it is best to not allow encoder->maxThreads to change.
include/avif/avif.h
Outdated
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs. | ||
| // key must be non-NULL, but passing a NULL value will delete that key, if it exists. | ||
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs, | ||
| // to be sent to codec at the next avifEncoderAddImage() call. |
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
include/avif/avif.h
Outdated
| // key must be non-NULL, but passing a NULL value will delete that key, if it exists. | ||
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs, | ||
| // to be sent to codec at the next avifEncoderAddImage() call. | ||
| // See the codec documentation to know if a setting is permanent or applied only to the next frame. |
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
include/avif/avif.h
Outdated
| // Codec-specific, optional "advanced" tuning settings, in the form of string key/value pairs, | ||
| // to be sent to codec at the next avifEncoderAddImage() call. | ||
| // See the codec documentation to know if a setting is permanent or applied only to the next frame. | ||
| // key must be non-NULL, but passing a NULL value will delete that pending key, if it exists. |
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
src/codec_aom.c
Outdated
| return AVIF_RESULT_UNKNOWN_ERROR; | ||
| } | ||
| } else { | ||
| cfg->g_usage = aomUsage; |
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.
I was imagine some time-constrained encoding application that starts with better quality and tries to speed up if it's too slow. But if changing speed is risky, then I agree we'd better avoid that.
| avifBool dimensionChanged = AVIF_FALSE; | ||
| if ((cfg->g_w != image->width) || (cfg->g_h != image->height)) { | ||
| cfg->g_w = image->width; | ||
| cfg->g_h = image->height; |
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.
Seems just changing g_w and g_h here only works when sending smaller frames, due to aom writing first frame's dimension to stream header.
To support larger frames, seems we need user to provide the expect dimension before starting encoding, and set g_forced_max_frame_width and g_forced_max_frame_height. I'm going to implement this in another PR.
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.
That's correct. An AV1 bitstream begins with a sequence header OBU, which advertises the maximum frame width and maximum frame height. Then each frame has a frame header OBU that may optionally specify a frame width and height smaller than the maximum. So if we are encoding multiple frames with increasing frame widths and heights, we will need a way to specify the maximum frame width and maximum frame height before encoding the first frame.
8b18345 to
9fec472
Compare
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.
Yuan: Please be careful when making this round of suggested changes. For some reason I find it difficult to review the changes to src/codec_aom.c. So we should refrain from making more big changes and try to stabilize this pull request. Thank you!
src/codec_aom.c
Outdated
| if ((cfg->g_w != image->width) || (cfg->g_h != image->height)) { | ||
| cfg->g_w = image->width; | ||
| cfg->g_h = image->height; | ||
| if (codec->internal->encoderInitialized) { |
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.
Please do this check first, immediately after line 688.
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
include/avif/avif.h
Outdated
| // a combination of settings are tweaked to simulate this speed range. | ||
| // * AV1 encoder settings and codec specific options set by avifEncoderSetCodecSpecificOption() | ||
| // will be applied / updated to AV1 encoder before each call to avifEncoderAddImage(). | ||
| // * Some encoder settings can be changed. Changes will take effect from next call to |
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.
Nit: Add "after encoding starts" to the end of the first sentence. Change "from next call" to "in the next call".
// * Some encoder settings can be changed after encoding starts. Changes will take effect in the next call to
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
include/avif/internal.h
Outdated
| AVIF_ENCODER_CONFIG_TILE_COLS_LOG_2 = (1 << 5), | ||
|
|
||
| AVIF_ENCODER_CONFIG_CODEC_SPECIFIC = (1 << 31) | ||
| } avifEncoderConfig; |
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.
Strictly speaking, if the avifEncoderConfig enum type is defined like this, it cannot take values that are the bitwise-OR of AVIF_ENCODER_CONFIG_xxx. You can find examples of one way to do this properly in avif.h.
I suggest the following:
typedef enum avifEncoderChange
{
AVIF_ENCODER_CHANGE_MIN_QUANTIZER = (1 << 0),
AVIF_ENCODER_CHANGE_MAX_QUANTIZER = (1 << 1),
AVIF_ENCODER_CHANGE_MIN_QUANTIZER_ALPHA = (1 << 2),
AVIF_ENCODER_CHANGE_MAX_QUANTIZER_ALPHA = (1 << 3),
AVIF_ENCODER_CHANGE_TILE_ROWS_LOG2 = (1 << 4),
AVIF_ENCODER_CHANGE_TILE_COLS_LOG2 = (1 << 5),
AVIF_ENCODER_CHANGE_CODEC_SPECIFIC = (1 << 31)
} avifEncoderChange;
typedef uint32_t avifEncoderChanges;
and then change the function parameter avifEncoderConfig updateConfig at line 283 to avifEncoderChanges encoderChanges.
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
| (alpha && ((updatedConfig & AVIF_ENCODER_CONFIG_MIN_QUANTIZER_ALPHA) || | ||
| (updatedConfig & AVIF_ENCODER_CONFIG_MAX_QUANTIZER_ALPHA)))) { | ||
| aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless); | ||
| } |
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 conditional expression is very complicated. I suggest we aim for clarify and rewrite it as follows:
if (!alpha) {
if ((updatedConfig & AVIF_ENCODER_CONFIG_MIN_QUANTIZER) || (updatedConfig & AVIF_ENCODER_CONFIG_MAX_QUANTIZER)) {
aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
}
} else {
if ((updatedConfig & AVIF_ENCODER_CONFIG_MIN_QUANTIZER_ALPHA) || (updatedConfig & AVIF_ENCODER_CONFIG_MAX_QUANTIZER_ALPHA)) {
aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
}
}
Optional: We can simplify the conditionals even further:
if (!alpha) {
if (updatedConfig & (AVIF_ENCODER_CONFIG_MIN_QUANTIZER | AVIF_ENCODER_CONFIG_MAX_QUANTIZER)) {
aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
}
} else {
if (updatedConfig & (AVIF_ENCODER_CONFIG_MIN_QUANTIZER_ALPHA | AVIF_ENCODER_CONFIG_MAX_QUANTIZER_ALPHA)) {
aom_codec_control(&codec->internal->encoder, AV1E_SET_LOSSLESS, lossless);
}
}
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
| // It reports if the change is valid, i.e. if any setting that can't change was changed. | ||
| // It also sets needUpdate to true if valid changes are detected. | ||
| static avifBool avifEncoderSettingsChanged(const avifEncoder * encoder, avifBool * needUpdate) | ||
| // It also reports detected changes in updatedConfig. |
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 function should sets *updatedConfig to 0 on entry. Otherwise, we need to document that it expects the caller to initialize *updatedConfig to 0.
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
src/codec_aom.c
Outdated
| } | ||
| } | ||
|
|
||
| avifBool dimensionChanged = AVIF_FALSE; |
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.
Nit: dimensionChanged => dimensionsChanged
"Dimensions" should be plural.
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
| if (!avifProcessAOMOptionsPreInit(codec, alpha, cfg)) { | ||
| return AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION; | ||
| } | ||
| } |
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.
Since we do not allow "end-usage" to change, we only need to call avifProcessAOMOptionsPreInit() before the aom_codec_enc_init() call. Please move lines 708-710 right before line 736, inside the if (!codec->internal->encoderInitialized) block.
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
| aom_codec_control(&codec->internal->encoder, AOME_SET_CQ_LEVEL, cqLevel); | ||
| } | ||
| #endif | ||
| } |
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.
Lines 774-785 should only be done when initializing the encoder or when cfg->rc_min_quantizer or cfg->rc_max_quantizer changed. If it takes more than five lines to implement this, just add a TODO 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.
Done
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.
LGTM.
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 a follow up of #1033.
The init logic was skipping some calls if the option is the default value. Now it no longer skip calls so that encoder settings can be reverted to default.
Switch to discard csOptions after being applied to encoder.