Radio

Compile your own bleeding edge AV1 video encoders

Posted at

!☕ This tutorial will show you how to build an up-to-date ffmpeg video converter with development versions of the AV1 codec libraries from their source code repositories. It includes instructions for the aom reference AV1 codec library, the SVT-AV1 and rav1e encoders, the dav1d decoder and Nextflix vmaf video quality measurement algorithm. The tutorial is targeted to users that already know how to compile programs from source code and know how to use the command-line shell commands, either in Unix-like systems or with the Windows command prompt, so that they can track the latest development of AV1 codecs.

Hooded hacker at keyboard with binary code in front
The AV1 standard was released in 2018 and nowadays is supported by all major web browsers, except for Safari. Youtube is a major provider of AV1-encoded content, while Netflix and Facebook also provide some video content encoded with AV1.

The AV1 video codec is a modern video codec that represents both a technical improvement over its predecessors VP9 and HEVC (H.265) and a major improvement over MPEG’s licensing and royalty model. AV1 is open, royalty-free and controlled by a non-profit consortium of multimedia giants (Alliance for Open Media), that mandates that contributors agree to a royalty-free patent model and defends against patent lawsuits. AV1 is also expected to be part of future World Wide Web standards, since the alliance was motivated by the search of a new WebRTC video standard for web-based videoconferences.

Development on AV1 is far from finished, though. AV1 is a very demanding codec and initial versions of the reference implementation were more than 10 thousand times slower than more traditional codecs, like H.264 (AVC). Even in 2021, very high definition videos in Full HD resolution or better cannot be played smoothly on older computers or smartphones. Encoding is extremely slow with the highest quality settings, taking days to encode a video on a personal computer, while faster encoding modes are just beginning to catch up with older codecs. This means that state-of-the-art development continues and every new release of the AV1 libraries will contain a speed or quality improvement.

This tutorial shows how to build the development versions of the three open source codecs currently available: aom (the reference implementation), Intel’s SVT-AV1 and Xiph.org rav1e. The source code is directly pulled from their source code repositories, allowing keeping up with the latest development efforts. Besides the encoders, the dav1d decoder (which is also being tuned with architecture-specific optimizations) and the libvmaf library, used by Netflix to measure video quality, will be included, but using the last versions of them is not as important as the encoders, so they are optional.

Instructions will be given for three operating systems, Ubuntu Linux, macOS and Windows. In all cases, the libraries will be compiled and integrated with the latest ffmpeg video transcoding tool, which will be our front-end to all the libraries. All the tools used are free, open source software, and can be freely downloaded, compiled, installed, modified and distributed without any issues. The amount of programs and libraries is not trivial, so I recommend that you reserve one or two hours for the whole process, but if you don’t want to wait, there are scripts for many operating systems available. Pick the one for your operating system and run, for example:

$ sh ubuntu.sh

The scripts may be used both to install and to update the programs to the latest source code repository versions.

Before we start

Most dependency programs and libraries are straightforward to build, but there are a few gotchas to look out for. First thing is that the amount of build tools and libraries required is not small and we need to rely on a package manager to be able to install them with a single command. We’ll give examples with Ubuntu Linux, ma⁠cOS and Windows. For Ubuntu, the built-in package manager apt will be used. For Windows and macOS, some package manager with a large package database needs to be installed. We’ll use MSYS2 for Windows and MacPorts for macOS. Other package managers, like Homebrew on ma⁠cOS, or yum/dnf on Fedora Linux may also be used. On Windows, MSYS2 provides the package manager and also a set of Unix-style command line tools and terminal emulator (mintty). Their usage requires some knowledge of Unix tools. When using the MSYS2 terminal, remember to open it in MINGW64 mode, which is the appropriate mode to build native Windows programs. If you prefer not to use the MSYS2 terminal, you may also use the built-in Windows shells by adding MSYS2 configuration to the environment variables and you’re good to go:

C:\>set MSYSTEM=MINGW64
C:\>set PATH=%PATH%;C:\msys2\mingw64\bin
C:\><unix commands available from here, ls, gcc, etc.>

One last important rule that impacts building rav1e: this library is written using very recent Rust compilers and tend to use the latest Rust language features, which is in a state of rapid evolution at the moment. This means that older operating systems, specially Linux distributions with long term support, will probably have outdated Rust compilers that can’t build rav1e. This is particularly true for Ubuntu 20.04 LTS, which is one of our example systems. In this case, we’ll use the official rustup tool to install recent versions of the Rust compiler, instead of using the one provided in the package database. On the other hand, the Windows and macOS package managers chosen have databases that are frequently updated to the latest versions and can be used instead of rustup.

Prerequisites

The prerequisite libraries and tools fall into 3 categories: 1) build tools required to compile the programs from source code, 2) libraries necessary to create video that complement the AV1 codec (e.g. audio codecs, subtitle renderer, Blu-Ray reader, etc.) and 3) libraries that are not necessary to create AV1 videos, but are useful to compare them with other codecs (H.264, VP9, etc.). The first two are mostly required, while the third is mostly optional and can be omitted if you prefer to use some other stable program for them (but it’s useful to have them included in the build because it will be easy to use a single program to encode with different formats). The commands for installing the prerequisites will be given and also a table with the complete list and the description of each one of them.

apt command line (requires privileges):

$ sudo apt install git gcc g++ cmake nasm yasm libsdl2-dev libopus-dev \
                   libmp3lame-dev libvorbis-dev libfdk-aac-dev         \
                   libvpx-dev perl libx264-dev libx265-dev libass-dev  \
                   libbluray-dev libssl-dev libfontconfig1-dev         \
                   libfreetype-dev libfribidi-dev meson xxd diffutils  \
                   ninja-build make

MacPorts command line (requires privileges):

$ sudo port install git cmake nasm yasm libsdl2 libopus lame libvorbis   \
                    libfdk-aac libvpx x264 x265 libass libbluray openssl \
                    fontconfig freetype fribidi meson diffutils rust     \
                    cargo cargo-c ninja

MSYS2 command line:

$ pacman -S --needed \
          git perl vim mingw-w64-x86_64-toolchain              \
          mingw-w64-x86_64-cmake mingw-w64-x86_64-nasm         \
          mingw-w64-x86_64-yasm mingw-w64-x86_64-opus          \
          mingw-w64-x86_64-lame mingw-w64-x86_64-libvorbis     \
          mingw-w64-x86_64-libvpx mingw-w64-x86_64-fdk-aac     \
          mingw-w64-x86_64-x264-git mingw-w64-x86_64-x265      \
          mingw-w64-x86_64-libass mingw-w64-x86_64-libbluray   \
          mingw-w64-x86_64-openssl mingw-w64-x86_64-fontconfig \
          mingw-w64-x86_64-freetype mingw-w64-x86_64-fribidi   \
          mingw-w64-x86_64-meson mingw-w64-x86_64-rust         \
          mingw-w64-x86_64-diffutils mingw-w64-x86_64-SDL2     \
          mingw-w64-x86_64-cargo-c mingw-w64-x86_64-ninja

In the examples, only MacPorts and MSYS2 install the Rust dependencies. Current version of rav1e requires Rust 1.51.0 or later (and this should keep rolling). For Ubuntu, download and install the Rust compiler with rustup and then install the cargo-c tool:

$ cd /tmp
$ wget -O install.sh https://sh.rustup.rs
$ sh install.sh
$ PATH=$HOME/.cargo/bin:$PATH cargo install cargo-c
$ rm install.sh

Note that rustup installs the Rust compiler and the cargo build tool in the $HOME/.cargo/bin directory by default, so we need to include this directory in the search path to be able to run cargo. Change the directory appropriatelly if you have configured the CARGO_HOME environment variable pointing to a different path.

The table with all the dependencies follows.

package description OS notes
git source code download and version control all  
perl scripting language all used during aom build
mingw-w64-x86_64-toolchain C and C++ compiler toolchain windows includes the make build system
gcc C compiler linux macOS has clang built-in
g++ C++ compiler linux macOS has clang++ built-in
make build system linux used during ffmpeg build, macOS has make built-in
ninja another build system all used during all builds except ffmpeg
cmake meta build system all used during aom and SVT-AV1 builds
meson another meta build system all used during libvmaf and libdav1d builds
diffutils tools to compare files all used during ffmpeg build
xxd or vim xxd hexdump tool all used during ffmpeg build, is usually in unrelated vim package
nasm x86 assembler all x86 used during ffmpeg, rav1e, dav1d and libvmaf builds
yasm another x86 assembler all x86 used during aom and SVT-AV1 builds
SDL2 graphical toolkit all optional (required for ffplay test player program)
libopus opus audio codec all optional (required for encoding only)
lame MP3 audio codec all optional (required for encoding only)
libvorbis Vorbis audio codec all optional (required for encoding only)
libfdk-aac AAC audio codec all optional (required for encoding only)
libvpx VP8 and VP9 video codecs all optional (required for encoding only)
libx264 H.264/AVC video codec all optional (required for encoding only)
libx265 H.265/HEVC video codec all optional (required for encoding only)
libass ASS/SSA subtitle renderer all optional
fontconfig font configuration and access all optional
freetype font renderer all optional
fribidi Unicode bidirectional text algorithm all optional (required for Arabic, Hebrew, etc.)
libbluray Blu-Ray disc reading all optional
libssl OpenSSL cryptography library all optional (required for HTTPS streaming)
rust Rust compiler all version 1.51.0 or later required by rav1e
cargo Rust build tool all usually comes in rust package
cargo-c Rust to C wrapper all used during rav1e build

The nasm and yasm assemblers are probably unnecessary for ARM builds, which should use the GNU assembler from the binutils package. Other ISA architectures should also use the GNU assembler when accelerated code becomes available to them, in particular MIPS and RISC-V.

Installation

The actual installation process of our AV1 toolkit will be done in the user directory, in a way that it does not conflict with the stable versions that might exist installed on the system. We start by defining a prefix environment variable, pointing to our desired base directory for everything:

$ export PREFIX=$HOME/opt/ffmpeg
$ mkdir -p $PREFIX

If you’re using the Windows command prompt, then set the variable with:

C:\>set PREFIX=%USERPROFILE%\opt\ffmpeg
C:\>mkdir %PREFIX%

Now we download everything from their source code repositories (all repositories use git). Note that, currently, SVT-AV1 only supports x86, so skip it if you’re using another architecture.

$ mkdir -p $PREFIX/src
$ cd $PREFIX/src
$ git clone https://git.ffmpeg.org/ffmpeg.git
$ git clone https://aomedia.googlesource.com/aom
$ git clone https://github.com/xiph/rav1e
$ git clone https://github.com/AOMediaCodec/SVT-AV1
$ git clone https://github.com/Netflix/vmaf
$ git clone https://code.videolan.org/videolan/dav1d

Since the downloaded repositories are under control of a version control system, updating them is easy. If you repeat the process later, instead of executing git clone, just execute git pull to get the latest changes:

$ # Some days later, to update the SVT-AV1 source, for example
$ cd $PREFIX/src/SVT-AV1
$ git pull

Now let’s compile and install each one of the libraries, always pointing them to our base directory $PREFIX. A final preparation is to configure the cmake back-end to ninja, because it’s much faster than make when recompiling and because, overall, it’s faster than make:

$ export CMAKE_GENERATOR=Ninja

aom

We start by compiling and installing aom:

$ cd $PREFIX/src/aom
$ mkdir -p cmbuild
$ cd cmbuild
$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DBUILD_SHARED_LIBS=1 \
        -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=OFF -DENABLE_DOCS=off ..
$ cmake --build .
$ cmake --install .

I’ve passed a few parameters to cmake when configuring: the first sets the target base directory to $PREFIX. The second enables shared libraries (DLLs), because the default is to install static libraries. Shared libraries are better, as long as the API doesn’t change, because we can update aom later independently of ffmpeg. With static libraries, both aom and ffmpeg would need to be recompiled.

The third option changes build type to release mode, disabling debug symbols and enabling optimizations. The last two parameters disable building of unnecessary secondary targets to speed up the build process. After building and installing, libaom should be installed at $PREFIX/lib:

$ cd $PREFIX/lib
$ ls libaom*
libaom.a  libaom.so  libaom.so.3  libaom.so.3.0.0 # for Ubuntu
$

For Windows, there’s no concept of a shared library path, only a binary path, but cmake doesn’t place libaom.dll in the right place, so we move it manually to the bin directory:

$ cd $PREFIX
$ mkdir -p bin
$ mv lib/libaom.dll bin/ # for Windows

SVT-AV1

Now aom is installed and we do the same thing with SVT-AV1, which also uses cmake as the build system. Right now, SVT-AV1 only supports the x86 architecture, so skip this library if you’re on a different architecture.

$ cd $PREFIX/src/SVT-AV1/Build
$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DBUILD_SHARED_LIBS=1 -DBUILD_APPS=OFF \
        -DENABLE_AVX512=ON -DCMAKE_BUILD_TYPE=Release ..
$ cmake --build .
$ cmake --install .

This will compile and install SVT-AV1. The configuration step also includes similar parameters as the aom ones, and one additional parameter, required to enable the AVX-512 instruction set extensions, available on modern CPUs. If your computer supports it, it will be automatically enabled during execution. AVX-512 support requires gcc 4.9.0 or later or clang 4.0 or later (Apple clang 900.0.37 from Xcode 9.0).

Again, for Windows users, move the DLLs from the lib to the bin directory:

$ cd $PREFIX
$ mv lib/libSvtAv1*.dll bin/

rav1e

The rav1e installation is tricky because it requires a recent Rust compiler. Currently it requrest rustc version 1.51.0, so check it with:

$ rustc --version # or $HOME/.cargo/bin/rustc --version
rustc 1.51.0

If the version is not correct, go back to the installation step and update your Rust version, possibly installing with the help of rustup. Remember that it installs rustc and cargo in $HOME/.cargo/bin.

After the version is confirmed to be correct, building rav1e is easy:

$ cd $PREFIX/src/rav1e
$ cargo cinstall --prefix=$PREFIX --release

If you’re using Rust from a different path, include it in the PATH environment variable instead:

$ PATH=$HOME/.cargo/bin:$PATH cargo cinstall --prefix=$PREFIX --release # for use with rustup

With aom, SVT-AV1 and rav1e installed, we already have the 3 open source encoders ready to be used by ffmpeg. Now we’ll optionally also build dav1d and libvmaf to complement our bleeding edge environment.

libvmaf

The libvmaf build system is meson, so we use it instead of cmake:

$ cd $PREFIX/src/vmaf/libvmaf
$ meson --prefix=$PREFIX --libdir lib --buildtype release \
        -Denable_docs=false -Denable_avx512=true build
$ ninja -C build install

For meson, we additionally configure the libdir parameter, because it might install the libraries in $PREFIX/lib64 but we want a single library directory, $PREFIX/lib, to keep it organized. meson already places DLLs in the bin directory, so this step is unnecessary for Windows.

To be able to use libvmaf, we’ll also need to install the model files, which are the way libvmaf is configured: when you run ffmpeg with libvmaf active, pass one of the models as a paremeter, which will configure libvmaf for operation. To install the models, copy them to the proper directory:

$ mkdir -p $PREFIX/share
$ cd $PREFIX/src/vmaf
$ cp -R -P -p model $PREFIX/share

Explanation of available models is available at vmaf source repository, but there’s a default one, vmaf_v0.6.1.json, that represents a model of living room watching conditions of a Full HD video.

dav1d

The dav1d build system is also meson, just repeat similar steps again:

$ cd $PREFIX/src/dav1d
$ meson --prefix=$PREFIX --libdir lib --buildtype release \
        -Denable_tools=false -Denable_avx512=true build
$ ninja -C build install

This is enough for dav1d and now we’re ready to build our ffmpeg version that uses all libraries previously installed !

ffmpeg

ffmpeg is a powerful media transcoder and supports pretty much all open source media formats available. Its build configuration is also very flexible, so there will be a lot of parameters here. They’re not supposed to cause any problems by themselves, but since we’re installing a custom version on a separate path on multiple operating systems, some care will be required with the parameters. The most important of them for macOS and Linux is the rpath configuration, which is the shared library search path embedded into the binary, so that this version of ffmpeg will correctly point to our custom libraries, instead of using some stable version installed in the system directories.

Separate configuration commands will be given to each system. First, for Ubuntu:

$ cd $PREFIX/src/ffmpeg
$ PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig ./configure --prefix=$PREFIX    \
        --extra-ldflags="-L $PREFIX/lib -Wl,-rpath,$PREFIX/lib"         \
        --enable-gpl --enable-nonfree --disable-doc                     \
                                                                        \
        --enable-openssl --enable-libass --enable-libbluray             \
        --enable-libfreetype --enable-libfribidi                        \
        --enable-libfontconfig                                          \
                                                                        \
        --enable-libfdk-aac --enable-libmp3lame --enable-libopus        \
        --enable-libvorbis                                              \
                                                                        \
        --enable-libx264 --enable-libx265 --enable-libvpx               \
                                                                        \
        --enable-libvmaf --enable-librav1e --enable-libsvtav1           \
        --enable-libaom  --enable-libdav1d

First, the pkg-config search path is configured to include the path to our libraries. Then the base directory is configured. The third parameter (‑‑extra-ldflags) configures the linker. It embeds two configurations. The first (‑L $PREFIX/lib) prepends our lib directory in the linker search path. This is important because it places our library directory first on all linker commands that might have multiple directories being searched: system directories for the stable libraries, directories specific to the package manager and our custom paths. Placing our library directory first makes sure the linker prioritises it and doesn’t attempt to link with a preexisting stable version of the libraries. The second linker configuration (‑Wl,‑rpath,$PREFIX/lib) stores the custom library directory in the executable. This way, the default dynamic linker search path is overriden: when executing, the dynamic linker first searches our directory, then searches the system paths.

The next parameters activate support for the GPL and non-free features. The GPL features includes a tiny amount of code in ffmpeg that is licensed under the GNU General Public License, and also enables support for linking with GPL libraries that we use (libx264 and libx265). The non-free features are actually free-software too: they enable linking with OpenSSL and libfdk-aac. They’re tagged non-free because their licenses are incompatible with the GPL, so, per license rules, the resulting executable cannot be redistributed. If you need to be able to redistribute your final ffmpeg, remove the OpenSSL and libfdk-aac libraries.

Then we disable building the documentation. This assumes that you already have a stable ffmpeg installed. If you do not, then it might be useful to keep the documentation. It might require aditional build tools, though.

Finally, we enable support for all the libraries that we installed or built.

Configuration for other systems will be almost the same. A few parameters change, though. This is the configuration command for MacPorts:

$ cd $PREFIX/src/ffmpeg
$ PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig ./configure --prefix=$PREFIX \
        --extra-ldflags="-L $PREFIX/lib -L /opt/local/lib -Wl,-rpath,$PREFIX/lib -Wl,-rpath,/opt/local/lib" \
        --extra-cflags="-DTARGET_OS_OSX=1 -I $PREFIX/include"        \
        --extra-cxxflags="-DTARGET_OS_OSX=1 -I $PREFIX/include"      \
        --enable-gpl --enable-nonfree --disable-doc                  \
                                                                     \
        --enable-openssl --enable-libass --enable-libbluray          \
        --enable-libfreetype --enable-libfribidi                     \
        --enable-libfontconfig                                       \
                                                                     \
        --enable-libfdk-aac --enable-libmp3lame --enable-libopus     \
        --enable-libvorbis                                           \
                                                                     \
        --enable-libx264 --enable-libx265 --enable-libvpx            \
                                                                     \
        --enable-libvmaf --enable-librav1e --enable-libsvtav1        \
        --enable-libaom	 --enable-libdav1d

Different from the Ubuntu command line we have an extra library search path (/opt/local/lib). This is required because MacPorts also uses a non-standard path, so libraries installed by MacPorts may also conflict with standard system libraries. If you’re using Homebrew, that’s not necessary: Homebrew places libraries on the standard /usr/local/lib directory.

Another difference is the compiler macro defined: ‑DTARGET_OS_OSX=1. I’m not sure this is necessary on every macOS version, but it works around a bug on a standard include file, TargetConditionals.h. Some versions of this file define TARGET_OS_MAC and others define TARGET_OS_OSX, supposedly meaning the same thing. On my system I had to include the macro to be able to compile.

The remainder of the configuration line is the same. Now for the Windows version:

$ cd $PREFIX/src/ffmpeg
$ PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig ./configure --prefix=$PREFIX    \
        --extra-ldflags="-L $PREFIX/lib" --disable-w32threads           \
        --enable-gpl --enable-nonfree --disable-doc                     \
                                                                        \
        --enable-openssl --enable-libass --enable-libbluray             \
        --enable-libfreetype --enable-libfribidi                        \
        --enable-libfontconfig                                          \
                                                                        \
        --enable-libfdk-aac --enable-libmp3lame --enable-libopus        \
        --enable-libvorbis                                              \
                                                                        \
        --enable-libx264 --enable-libx265 --enable-libvpx               \
                                                                        \
        --enable-libvmaf --enable-librav1e --enable-libsvtav1           \
        --enable-libaom  --enable-libdav1d

The differences here are the removal of rpath and disabling the w32threads wrapper. Windows doesn’t have a concept of DLL search path. DLLs in the same directory as the executable will be automatically loaded, so we don’t need rpath. The w32threads wrapper is built into ffmpeg, but the libvmaf filter requires the full POSIX threads wrapper instead, which is provided by the MSYS2 toolchain package.

The configuration step generates some files and gives a full report of the detected libraries and configured features. Now it’s the time to build and install ffmpeg.

$ make -j10
$ make install

And we’re done ! We’ve built an ffmpeg binary from the source code using the latest AV1 libraries available !

Testing and usage

The simplest test is to execute ffmpeg:

$ $PREFIX/bin/ffmpeg
ffmpeg version N-101925-g1050f94c22 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 10.2.0 (GCC)
  configuration: --prefix=/home/hdante/opt/ffmpeg --extra-ldflags='-L /home/hdante/opt/ffmpeg/lib -Wl,-rpath,/home/hdante/opt/ffmpeg/lib' --enable-gpl --enable-nonfree --disable-doc --enable-openssl --enable-libass --enable-libbluray --enable-libfreetype --enable-libfribidi --enable-libfontconfig --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libx264 --enable-libx265 --enable-libvpx --enable-libvmaf --enable-librav1e --enable-libsvtav1 --enable-libaom --enable-libdav1d
  libavutil      56. 72.100 / 56. 72.100
  libavcodec     58.136.100 / 58.136.100
  libavformat    58. 78.100 / 58. 78.100
  libavdevice    58. 14.100 / 58. 14.100
  libavfilter     7.111.100 /  7.111.100
  libswscale      5. 10.100 /  5. 10.100
  libswresample   3. 10.100 /  3. 10.100
  libpostproc    55. 10.100 / 55. 10.100
Hyper fast Audio and Video encoder
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...

Use -h to get full help or, even better, run 'man ffmpeg'
$

If it doesn’t work at this point, it means that it can’t find the required libraries. Go back to the configuration and make sure that the correct paths are being used. For Windows, the DLLs must be in the same directory as the binary. It’s also possible to specify the library search path when executing:

$ LD_LIBRARY_PATH=$PREFIX/lib $PREFIX/bin/ffmpeg

If this command works but directly executing the binary doesn’t, then the rpath configuration is missing.

An useful test is to confirm that it’s using the correct libraries:

$ cd $PREFIX/bin
$ ldd ffmpeg # for Linux and Windows
        linux-vdso.so.1 (0x00007ffd4d15d000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007f6ab33b4000)
        libxcb.so.1 => /usr/lib/libxcb.so.1 (0x00007f6ab338a000)
        libxcb-shm.so.0 => /usr/lib/libxcb-shm.so.0 (0x00007f6ab3385000)
        libxcb-shape.so.0 => /usr/lib/libxcb-shape.so.0 (0x00007f6ab3380000)
        libxcb-xfixes.so.0 => /usr/lib/libxcb-xfixes.so.0 (0x00007f6ab3376000)
        libasound.so.2 => /usr/lib/libasound.so.2 (0x00007f6ab3297000)
        (...)
        libvmaf.so.1 => /home/hdante/opt/ffmpeg/lib/libvmaf.so.1 (0x00007f6ab2e58000)
        libdav1d.so.5 => /home/hdante/opt/ffmpeg/lib/libdav1d.so.5 (0x00007f6ab2449000)
        libaom.so.3 => /home/hdante/opt/ffmpeg/lib/libaom.so.3 (0x00007f6ab199b000)
        librav1e.so.0 => /home/hdante/opt/ffmpeg/lib/librav1e.so.0 (0x00007f6ab14cb000)
        libSvtAv1Enc.so.0 => /home/hdante/opt/ffmpeg/lib/libSvtAv1Enc.so.0 (0x00007f6ab042a000)
        (...)
$ otool -L ffmpeg # for macOS
        (...)
        /Users/hdante/opt/ffmpeg/lib/libvmaf.1.dylib (compatibility version 1.0.0, current version 1.0.0)
        /Users/hdante/opt/ffmpeg/lib/libdav1d.5.dylib (compatibility version 5.0.0, current version 5.0.0)
        @rpath/libaom.3.dylib (compatibility version 3.0.0, current version 3.0.0)
        /Users/hdante/opt/ffmpeg/lib/librav1e.0.5.0.dylib (compatibility version 0.0.0, current version 0.5.0)
        @rpath/libSvtAv1Enc.0.dylib (compatibility version 0.0.0, current version 0.8.6)
        (...)

The output must show that all the AV1 libraries point to our custom libraries, not to some other path present in the system.

There’s another trick that I recommend, which is to create a link to the binary in a standard directory, but with a different name. It works on Linux and macOS. This way, you’ll be able to use both versions of ffmpeg, the stable one in general and the development one for AV1:

$ sudo ln -sf $PREFIX/bin/ffmpeg /usr/local/bin/ffmpeg-dev # for Linux and macOS
$ ffmpeg-dev

For Windows, a simple copy may be done and the path appended to the end of the search path:

$ cd $PREFIX/opt/ffmpeg/bin
$ cp ffmpeg.exe ffmpeg-dev.exe # for Windows
$ export PATH=$PATH:$PREFIX/bin

Now we’re ready to start encoding. Let’s start with the fastest encoder, SVT-AV1. Get some video source, it should be short for the tests, but it can be raw lossless video data acquired directly from the camera.

$ ffmpeg-dev -i crowd_run_1080p50.y4m -c:v libsvtav1 svt-av1-test.mkv

The command should take some time, even considering that SVT-AV1 is the fastest encoder. On my machine it runs at 3 frames per second, so the 10 second test file takes more than 2 minutes to encode. On the example command, the first parameter ‑i indicates an input file, the ‑c:v parameter indicates the video codec for all output streams and the command ends with the output file. It’s possible to see all the codecs available with the following command:

$ ffmpeg-dev -codecs
(...)
 DEV.L. av1                  Alliance for Open Media AV1 (decoders: libdav1d libaom-av1 av1 ) (encoders: libaom-av1 librav1e libsvtav1 )
(...)

ffmpeg includes a test player, called ffplay, that can be used to test the file:

$ $PREFIX/bin/ffplay svt-av1-test.mkv

It will play the video, then stop at the last frame. To quit the test player press q, or [Ctrl]+C on the command line. If you have other video players that support AV1 you can use them too, but keep in mind that it won’t use our version of libdav1d to decode the video.

Now we do the opposite test: we convert the AV1 file back to some other format:

$ ffmpeg-dev -i svt-av1-test.mkv -c:v libx264 decoded.mkv

It’s not necessary to specify libdav1d as the decoder because it’s the default for AV1. Test again the decoded file with ffplay:

$ $PREFIX/bin/ffplay decoded.mkv

Now we repeat the encoding test with rav1e and aom. I’ll use extra parameters to speed up the encoding, because the default parameters are really slow:

$ ffmpeg-dev -i crowd_run_1080p50.y4m -c:v librav1e -speed 10 rav1e-test.mkv
$ ffmpeg-dev -i crowd_run_1080p50.y4m -c:v libaom-av1 -cpu-used 6 aom-test.mkv

Details about the parameters and the encoders are available on the AV1 encoding guide on ffmpeg wiki and on the official ffmpeg codec documentation.

We arrive at the final step, VMAF calculation. The command line might get really complicated, but it’s not expected to change too much:

$ ffmpeg-dev -i svt-av1-test.mkv -i crowd_run_1080p50.y4m -lavfi \
        libvmaf=log_fmt=json:log_path=svt-av1-vmaf.json:model_path=$PREFIX/share/model/vmaf_v0.6.1.json \
        -f null -

For Windows, the paths must be properly escaped, due to the colon : character used by ffmpeg colliding with the Windows drive name:

$ MSYS2_ARG_CONV_EXCL="*" ffmpeg-dev -i svt-av1-test.mkv -i crowd_run_1080p50.y4m -lavfi \
        libvmaf=log_fmt=json:log_path=svt-av1-vmaf.json:model_path='C\\:/Users/hdante/opt/ffmpeg/share/model/vmaf_v0.6.1.json' \
        -f null - # for Windows with Unix shells

C:\>:: for Windows with command prompt
C:\>ffmpeg-dev -i svt-av1-test.mkv -i crowd_run_1080p50.y4m -lavfi libvmaf=log_fmt=json:log_path=svt-av1-vmaf.json:model_path="C\\:/Users/hdante/opt/ffmpeg/share/model/vmaf_v0.6.1.json" -f null -

The VMAF calculation requires two inputs: the first is the encoded file and the second is the reference file. The command has a null output, meaning no encoded file. Instead, the results are given by using the ‑lavfi libvmaf parameter, which activates VMAF as a filter. The calculation results are then specified with the log_path parameter. Here we also need to specify a model file, that’s the way libvmaf is configured. We use vmaf_v0.6.1.json, which is the default configuration. The resulting log file contains the VMAF value for each frame and at the end a set of overall results, including the VMAF mean value:

$ tail svt-av1-vmaf.json
    "vmaf": {
      "min": 77.585846,
      "max": 100.000000,
      "mean": 85.325577,
      "harmonic_mean": 85.081985
    }
  },
  "aggregate_metrics": {
  }
}

In this case (using the default VMAF model), we found out that the overall VMAF value for the encoding was 85.325577.

Conclusion

In this tutorial, we explained everything necessary to build your own bleeding edge AV1 encoder and now it’s time to start encoding some real videos. Whenever necessary, all the programs may be updated with simple git pull and make install/ninja install commands. As an extra bonus, you have the full source code and maybe you might want to contribute with some patches too ? Good luck !

Scripts with the whole installation process are available for multiple operating systems at gitlab.