Simple hardware encoding for VP9 with semi-recent Intel processors and FFmpeg

My laptop processor, an Intel Core i5-8265U, is not very fast. This means that software video encoding usually takes a very long time, at around 4 fps - that is, encoding a video takes roughly seven times as long as watching it. That makes encoding of video recordings for archival or sharing very expensive and thereby effectively impossible for noncritical purposes, as it renders the laptop unavailable for other work.

Luckily, recent Intel processors (Kaby Lake-plus, at least, so that would probably be anything produced in the last five years) support hardware encoding of VP9 video, and FFmpeg has supported this since at least 2017. I collected the required arguments to FFmpeg in the following script.

#!/usr/bin/bash

infile=$1
# outfile replaces the infile extension with _hw_encoded.mkv
outfile=`echo $infile | sed 's/\(.*\)\..*/\1_hw_encoded.mkv/'`

args=(
    -hide_banner  # no banner

    # enable hardware acceleration
    -init_hw_device vaapi=foo:/dev/dri/renderD128
    -hwaccel vaapi
    -hwaccel_device foo

    # if hw decoding is available, keep result in hardware
    -hwaccel_output_format vaapi

    -i "$infile"

    # all filters should work in hardware, including the 'hwupload' one
    -filter_hw_device foo
    # if hardware decoding was available, then use the result
    #   if not, upload the software-decoded video
    -vf 'format=nv12|vaapi,hwupload'

    # use all audio, video and subtitle streams available
    -map 0

    # encode video using vp9_vaapi
    # global_quality 70 seems to be a decent tradeoff between file size and video quality.
    #   it can go as high as 255, resulting in seriously blocky video,
    #   but the resulting files are only roughly 40% smaller
    -c:v vp9_vaapi -global_quality:v 70
    #   using tweaks recommended at https://trac.ffmpeg.org/wiki/Hardware/VAAPI
    -bf 2 -bsf:v vp9_raw_reorder,vp9_superframe

    # keep all subtitles
    -c:s srt

    # encode audio using libopus, default settings
    -c:a libopus -vbr:a on -b:a 128k

    "$outfile")


nice ffmpeg "${args[@]}"

The script handles everything I throw at it pretty well, and the encoding occurs at a decent 120 fps; a 30x speed-up. Moreover, the CPU is pretty much idle during the new hardware encoding process, so that I can run (even multiple instances of) this script in the background and forget about it.

The quality/size tradeoff of the hardware-encoded file is not as good as with the high-quality software libvpx-vp9 encoder, yielding roughly a 50% increase in file size at similar video quality. The perfect, however, is the enemy of the good.