Page MenuHome

Video rendering FFMpeg AV1 codec encoding support
ClosedPublic

Authored by Stephen Seo (seodisparate) on May 11 2022, 10:10 AM.

Details

Summary

Previously, the Blender video renderer did not have support for encoding
video to AV1 (not to be confused with the container AVI). The proposed
solution is to leverage the existing FFMpeg renderer to encode to AV1.

Note that avcodec_find_encoder(AV_CODEC_ID_AV1) usually returns
"libaom-av1" which is the "reference implementation" for AV1 encoding
(the default for FFMpeg, and is slow). "libsvtav1" is faster and
preferred so there is extra handling when fetching the AV1 codec for
encoding such that "libsvtav1" is used when possible.

This commit should only affect the options available for video
rendering, which includes the additional AV1 codec to choose from, and
setting "-crf".

Also note that the current release of FFMpeg for ArchLinux does not
support "-crf" for "libsvtav1", but the equivalent option "-qp" is
supported and used as a fallback when "libsvtav1" is used (as mentioned
here: https://trac.ffmpeg.org/wiki/Encode/AV1#SVT-AV1 ). (Actually, both
"-crf" and "-qp" is specified with the same value in the code. When a
release of FFMpeg obtains support for "-crf" for "libsvtav1" is
released, the code shouldn't be needed to change.)

The usage of the AV1 codec should be very similar to the usage of the
H264 codec, but is limited to the "mp4" and "mkv" containers.

This patch pertains to the "VFX & Video" module, as its main purpose is
to supplement the Video Sequencer tool with the additional AV1 codec for
encoded video output.


This is my first contribution to Blender, so I think it may be expected
to find issues that need to be fixed. In other words, I am certain that
this first patch submission may be rejected upon review to fix something
I may have overlooked. If it somehow passes review on the first patch,
then cool.

Diff Detail

Repository
rB Blender

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
source/blender/blenkernel/intern/writeffmpeg.c
753

Two possible structs that have codec information is AVCodec[1] and AVCodecContext[2], but only the codec is enumerated, while the encoder/decoder appears to be only be query-able by its string name.

[1]: https://ffmpeg.org/doxygen/trunk/structAVCodec.html
[2]: https://ffmpeg.org/doxygen/trunk/structAVCodecContext.html

Thanks for the update.

From just reading the code seems fine. For testing we'll need to re-compile the libraries.

To my understanding there are three encoder implementations:

  1. aom-av1 (Reference implementation. Has the most features but is generally considered the slowest of the three)
  2. rav1e (Rust implementation, has more features than SVT1 but seems to be a bit slower generally)
  3. SVT-AV1 (The fastest but from my understanding doesn't have all the features that the reference implementation or rav1e has)

From what I can see, it seems like there are still some cases where someone would want to use one of the other encoders besides SVT, for example quality if more important than speed.
Perhaps my information is outdated, but it seems like rav1e and aom is able to compress video at a higher quality than SVT because SVT doesn't have all features implemented in the standard yet.

To me it would make sense to be able to choose which encoder to use, if it is available on the system.
Now, all of these three are optional dependencies two ffmpeg, so if we wanted to we could only ship one (or more) of them with Blender.

However I don't know if this is something we want to support or not (the ability to choose the specific encoder)

Within Blender the important things are to be able to read AV1 stream, and be able to output an AV1. I would not expose the setting as a codec as it is rather messy to explain what the difference is to users. The way I see it if we want to support multiple codecs then it needs to be hooked up into a preset system somehow: when the best quality requested from options it could imply one codec or another.

When you're talking about missing features, does it mean that, for example, SVT-AV1 will be unable to read certain files?

Within Blender the important things are to be able to read AV1 stream, and be able to output an AV1. I would not expose the setting as a codec as it is rather messy to explain what the difference is to users. The way I see it if we want to support multiple codecs then it needs to be hooked up into a preset system somehow: when the best quality requested from options it could imply one codec or another.

When you're talking about missing features, does it mean that, for example, SVT-AV1 will be unable to read certain files?

From my understanding of it, SVT-AV1 is purely an AV1 encoder, and FFMpeg only uses dav1d for decoding AV1.

EDIT: It looks like aom is the only one that's both an encoder and decoder. Everything else appears to be only one or the other.

  • Sanity checks for AV1 encoding
  • Clang-format blenkernel/intern/writeffmpeg.c

I tested rendering with both libaom-av1 and libsvtav1, and libsvtav1 is usually faster for most cases. For now I will leave the code that prioritizes libsvtav1 first when encoding to AV1, but I may change this if I find a better alternative. The three AV1 encoders have some quirks for encoding, and I have added some sanity checks account for them, but there may be more things to fix. For example, libaom-av1 may have better performance if another parameter(s) is set (such as "-tiles" for more parallelism when encoding) and I haven't added/tested this yet. I may add more changes after some testing.

Whichever codecs are ultimately decided, please update intern/ffmpeg/tests/ffmpeg_codecs.cc as appropriate so that we can be sure that ffmpeg has been built properly now and in the future when updates are made.

Whichever codecs are ultimately decided, please update intern/ffmpeg/tests/ffmpeg_codecs.cc as appropriate so that we can be sure that ffmpeg has been built properly now and in the future when updates are made.

If libaom-av1 is used:

FFMPEG_TEST_VCODEC_NAME(libaom-av1, AV_PIX_FMT_YUV420P)

will not work, because there is a dash in the encoder's name.
If aom will be included, I will use the alternative:

TEST(ffmpeg, libaom_av1_AV_PIX_FMT_YUV420P)
{
  EXPECT_TRUE(test_codec_video_by_name("libaom-av1", AV_PIX_FMT_YUV420P));
}
  • Add tests in ffmpeg_codecs.cc for libaom-av1
  • Pick AV1 encoder based on user defined preset
  • Isolate setting AV1 parameters to one function
  • Recheck context->ffmpeg_preset to cover edge-cases
  • Fix "preset" being overridden when using AV1

With these commits, libaom-av1 is tested for in the gtest for
ffmpeg_codecs. However, these commits set up support for all three
possible encoders: librav1e, libsvtav1, and libaom-av1. As long as at
least one AV1 encoder is present, then AV1 encoding should work. The
gtest ensures that at least libaom-av1 is present.

I think I'm ready for review again, so please test away if needed.

I must note that because the patch adds a test for "libaom-av1" as the encoder, this implicitly requires the "aom" package (at least it's called this in ArchLinux), which also provides an AV1 decoder. So by including the "libaom-av1" encoder, the decoder should also be available, which should implicitly ensure that Blender can open AV1 videos (by leveraging FFMpeg).

After some testing, I've found that the rav1e encoder has been under-performing with the current settings (using VMAF). I will update soon with tweaked settings.

Dabi awarded a token.May 20 2022, 12:14 PM
Dabi added a subscriber: Dabi.
  • Tweak AV1 encoding settings

    After some testing, it was determined that rav1e has some quality issues compared to libaom-av1.

    Two types of videos were tested: a home video of a dog (one video from a smartphone camera, and one from a DSLR (both of which in 1920x1080)), and a presentation video (consisting of mostly still frames of things such as slideshow slides). rav1e produced VMAF scores lower than libaom-av1 regardless of the type of video. Strangely, rav1e produced a better VMAF score when directly encoded via ffmpeg (with rav1e_qp = aom_crf * 255 / 51), but not when encoding via Blender.

    Because of these results, the following encoder search pattern (encoder searching) is used:

    REALTIME speed: libsvtav1 -> default

    GOOD speed: libaom-av1 -> default

    SLOWEST/BEST speed: libaom-av1 -> rav1e -> default

    Note that FFMpeg's default encoder for AV1 is libaom-av1 on my machine.

    I am open to discussion, especially about the SLOWEST setting. rav1e produces lower VMAF scores with comparable settings to libaom-av1, but has better video compression (smaller filesizes). I'm not sure if rav1e should be the default for the SLOWEST preset since the quality is lower than libaom-av1 in my testing, but then again it performs better with video compression.

Can't test this with current libs, but code looks fine to me as well, so no objections here. I don't really have strong opinion on particular codecs/details as long as they are sensible.

Just to see if understand this correctly there's 3 AV1 libraries libaom-av1, librav1e, and libsvtav1. is it suggested we enable all 3 in the build we ship on blender.org? The updated unit test only seems to add a test for libaom-av1 can anyone clarify this? (asking since i'm the one most likely doing the work of updating our ffmpeg build)

Just to see if understand this correctly there's 3 AV1 libraries libaom-av1, librav1e, and libsvtav1. is it suggested we enable all 3 in the build we ship on blender.org? The updated unit test only seems to add a test for libaom-av1 can anyone clarify this? (asking since i'm the one most likely doing the work of updating our ffmpeg build)

I set up the encoder searching such that it should not fail if only one of the three encoders are available (which is depending on avcodec_find_encoder(AV_CODEC_ID_AV1) such that FFMpeg should return any available encoder). You can see mention of such in line 487 of writeffmpeg.c .

EDIT: So yes, I think only having libaom-av1 enabled is fine for the bare minimum.

intern/ffmpeg/tests/ffmpeg_codecs.cc
157

Hmm, perhaps you can make this a OR statement?
So "AOM or rav1e or svt".
Because we already have fallback code for actual encoding. So if one of them is available it should be fine.
However perhaps we should perhaps make a special case for rav1e as that only provides the encoder and not a decoder.

So if this test is there to check if we can both encode and decode, we should also check for dav1d if rav1e is the only one available out of the three.

source/blender/blenkernel/intern/writeffmpeg.c
496

For the tile statements here, I would like to know who the numbers came to be.
Is 8 and 3x3 some magic number that always scales well or could the be tweaked depending on the amount of CPUs available and the output resolution?

Besides my comments, I think this generally looks fine.

We will have to discuss a bit more which codecs we want to ship per default.
I think we could probably ship with only libaom and it should be fine.

@Stephen Seo (seodisparate) Perhaps for a later patch you could contribute a VMAF test script?
I think it would be really interesting in perhaps later down the line have some tests using it to ensure that expected video quality hasn't degraded between releases.

I'm also very interested in your findings where rav1e produced lower scores in Blender than when using ffmpeg via the command line.
Do you have any hunch on why this is the case?

intern/ffmpeg/tests/ffmpeg_codecs.cc
157

The ffmpeg tests here are to validate : are we shipping the libraries we think we are shipping? ie it is a "did lazydodo screw up the libs during the last update" test. having multiple options here that would have the test pass would defeat the purpose, these tests need to specifically test for the lib(s?) we decide to ship with.

intern/ffmpeg/tests/ffmpeg_codecs.cc
157

having multiple options here that would have the test pass would defeat the purpose

I talked to Sybren about this.
We can just check for libaom then.

However I think (and Sybren agrees) that we should probably separate out our internal packaging checks from our regular test suite. Because I think our make test tests should check for intent and not for details.

IE if we expect Blender to be able to encode/decode AV1, we shouldn't care too much about which library or version is used. As long as it works.

Otherwise the test suite is not that useful for people outside of the official Blender platform maintainers.
IE if a linux distro of a movie studio decided to only use the AV1 SVT library, our unit test would emit failures even if nothing it fundamentally wrong with that.

If we mix internal packaging checks with functionality checks, then it is very hard for outsiders and even our own developers to know if failing tests are to be taken seriously or not.

However this would need us to do quite a bit of work to split out our internal packaging tests from our regular test suite.
So this is not something I think we should fix now.

Stephen Seo (seodisparate) marked 3 inline comments as done.Jun 16 2022, 12:40 PM

Ok, from discussions it sounds like just leaving the test for AOM is fine. If there are any misunderstandings, please let me know. (I will continue reviewing comments/discussions.)

source/blender/blenkernel/intern/writeffmpeg.c
496

For the tile statements here, I would like to know who the numbers came to be.
Is 8 and 3x3 some magic number that always scales well or could the be tweaked depending on the amount of CPUs available and the output resolution?

From my understanding of it, "tiles" subdivides the frame such that each subdivision can be worked on by a separate thread.

For librav1e, this is specified as the total number of subdivisions. For libaom-av1, this is specified for both X and Y axes (hence, the "3x3"). Actually, I will change this to "4x2", since the dimensions of a frame usually have multiple "2" factors in both axes, and "3x3" probably doesn't subdivide things optimally (either the implementation actually subdivides the frame into 9 pieces, or it is treated as a "maximum").

  • Use 4x2 instead of 3x3 for libaom-av1 tiles option

This commit changes the "tiles" option passed to libaom-av1 when encoding to
AV1. The original setting was "3x3", but this has now been changed to "4x2".
This change should be more optimal as dimensions of a frame usually have factors
of "2" instead of "3". Note that "tiles" allows for subdivisions of a frame such
that it can be processed via multiple threads during encoding/decoding (which is
my understanding of it).

...
I'm also very interested in your findings where rav1e produced lower scores in Blender than when using ffmpeg via the command line.
Do you have any hunch on why this is the case?

I think it may have to do with the default bitrate settings being applied in Blender, whereas with ffmpeg, I made no such specification when using ffmpeg in the terminal. I probably will do more tests again since I changed an encoding parameter with AOM, but since the parameters for rav1e have not changed, those results will be the same for me.

EDIT: Actually, it seems that bitrate is set to zero when CRF is used (which I have set up AV1 to use). So if that's not the case, it should be some other setting being applied somewhere, but I'm not too familiar with Blender's code base..

  • Set tiles awareness of thread-count and frame dims

When setting the tiles parameter for rav1e and libaom encoders, they are now set
such that the number of available threads (in RenderData) and dimensions of the
frame are factors in deciding the tiles.

For rav1e, simply setting the number of tiles to be equal to the number of
threads is sufficient.

For libaom-av1, tiles are set in the form of "XxY", and the x and y dimensions
are checked as well as thread count to decide the tiles parameter setting.

Much thanks to ZedDB for mentioning thread-count checking and frame dimensions
checking.

  • Fix minor documentation typo
  • Use clang-format on intern/writeffmpeg.c

This should have been done earlier, sorry about that.

  • Dynamically set "tiles" for AOM based on threads

The previous implementation hard-coded values up to a system with 128 cores for
setting the "tiles" parameter for libaom-av1 encoder (AV1 encoding). The newer
implementation now handles the thread count to dynamically set "tiles".

  • Fix dynamic "tiles" setting for AOM AV1 encoding

Previous implementation made it possible for a tile setting to use a value that
isn't a power of 2. This commit fixes this.

  • Use "power_of_2_min_i()" instead of "...max_i()"

Function changed to "power_of_2_min_i()" during calculations during setting the
"tiles" parameter for AOM AV1 encoding for consistent values. (This was
partially fixed in the previous commit.)

This should, for example, have tiles set to "4x4" from 16-31 threads, and have
"8x4" (if x > y) from 32-63, and etc.

Due to the use of power_of_2_min_i(), a situation will arise where a non-power-of-2 amount of threads will not be fully utilized. However, if I switch the function calls to power_of_2_max_i(), then the "next power-of-two value" will be used in the "tiles" parameter setting for AOM AV1 encoding. So is it better to potentially under-utilize the specified/requested amount of threads, or to over-utilize? This probably isn't a problem if the thread-count is just a power-of-two value such as 8, 16, or 32, but values in between will currently under-utilize the specified amount of threads.

I've talked to Sergey about this and it is best if we under commit the amount of thread.
Usually (at least in my experience) it is not too bad to over commit, however for render farms and situations where you want Blender to never use more cores than specified.
(IE if we tell Blender to only use 6 thread, it doesn't use 8 threads)

Just to make sure:
I think this should be good to go now, right?

I think this should be good to go now, right?

We'd need to land an updated ffmpeg in svn first, otherwise you'll have a failing test on your hands

I've talked to Sergey about this and it is best if we under commit the amount of thread.
Usually (at least in my experience) it is not too bad to over commit, however for render farms and situations where you want Blender to never use more cores than specified.
(IE if we tell Blender to only use 6 thread, it doesn't use 8 threads)

Actually, when using libsvtav1, it prints to stdout the number of threads it will use. There is no clear option to manipulate the number of threads used, and when tested on my 16-core system, the number of threads printed was about 617.

Just to make sure:
I think this should be good to go now, right?

I currently see no need to add further changes unless someone notices something.

The libs to support this should have landed now (the ffmpeg test is passing for me locally), but this diff may needs a rebase to latest master for the bots to pass, as they appear to be checking out an older library set without aom when i tested just now

Rebase against master.

Luckily, there weren't any conflicts upon rebase. Hopefully the tests should pass now.

This revision is now accepted and ready to land.Aug 2 2022, 7:11 PM

Someone on chat asked about this diff, thought it was a fair question, platform wise the libs are ready, are we landing this for 3.4?

Are we landing this for 3.4?

I think so.
I haven't at least anyone tell me otherwise.

@Sergey Sharybin (sergey) do you have any opjections?

It should be fine to land the patch if it is ready.

Update to latest master

I landed it, i admit imho we could have done better here, i'm not entirely sure why this dragged on as long as it did after the libs landed.

This this is probably the usual causes causes.
IIRC we postponed this as it was too late to merge for 3.3.
Then at least I forgot about it at the start of the 3.4 period.

I guess we might be able to queue up things like this in a better way with gitea.
Perhaps we could add postponed merges like this into a "milestone" list for the appropriate bcon in the next release cycle?
The we would have a clearer "this feature is good to go for this release, but not merged yet" list the people can go over and poke devs about.