Convert videos to H.265 / HEVC using FFmpeg and GPU hardware encoding
I’m using VDR for TV recording. Most public TV channels (via satellite) in Germany that I’m interested in are using MPEG-2 streams with H.264 encoding with a bitrate of about 10000-16000 kb/s. So for an hour of video it could take easily around 6 GByte on disk. This is quite a lot if you want to archive your videos.
I was wondering how to shrink these kind of videos without lowering the “visual” quality too much. And of course it should be fast and maybe even power efficient. First I used my CPU AMD 3900X for this task. This works quite well but uses quite some power. So I knew that my
AMD RX 5700XT GPU supports H.265 hardware encoding. Without expecting to much I played around a bit and the outcome was surprisingly good - and with way less power needed!
That said I’m by no means a video encoding expert and my main focus just was to keep the same “visual” quality when converting from the MPEG-2 streams in
H.265 and reduce the size of the original recording by about 50%+. And I also did no audio re-encoding. Audio is copied 1:1.
So what I did to judge about the video quality was comparing two videos side by side with
mpv. On the left the original video and on the right the one with the
H.265 encoding. I tried various video settings and compared again.
For the impatient here is what I came up with finally after playing around a while with various
ffmpeg \ -vaapi_device /dev/dri/renderD128 \ -i "concat:00001.ts|00002.ts|00003.ts" \ -vf 'format=nv12,hwupload' \ -map 0:0 \ -c:v hevc_vaapi \ -map 0:a \ -c:a copy \ -rc_mode CQP \ -global_quality 25 \ -profile:v main \ -v verbose video.mp4
We’ll talk about the parameters in just a sec. Just to mention it the
RX 5700 XT supports additional codecs for fast encoding. To figure out for which codecs GPU hardware video encoding acceleration can be used
vainfo command provides some information:
vainfo: VA-API version: 1.10 (libva 2.10.0) vainfo: Driver version: Mesa Gallium driver 20.3.4 for AMD Radeon RX 5700 XT (NAVI10, DRM 3.40.0, 5.10.14-arch1-1, LLVM 11.0.1) vainfo: Supported profile and entrypoints VAProfileMPEG2Simple : VAEntrypointVLD VAProfileMPEG2Main : VAEntrypointVLD VAProfileVC1Simple : VAEntrypointVLD VAProfileVC1Main : VAEntrypointVLD VAProfileVC1Advanced : VAEntrypointVLD VAProfileH264ConstrainedBaseline: VAEntrypointVLD VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice VAProfileH264Main : VAEntrypointVLD VAProfileH264Main : VAEntrypointEncSlice VAProfileH264High : VAEntrypointVLD VAProfileH264High : VAEntrypointEncSlice VAProfileHEVCMain : VAEntrypointVLD VAProfileHEVCMain : VAEntrypointEncSlice VAProfileHEVCMain10 : VAEntrypointVLD VAProfileHEVCMain10 : VAEntrypointEncSlice VAProfileJPEGBaseline : VAEntrypointVLD VAProfileVP9Profile0 : VAEntrypointVLD VAProfileVP9Profile2 : VAEntrypointVLD VAProfileNone : VAEntrypointVideoProc
For all entries with a value of
VAEntrypointEncSlice hardware encoding can be used and I’m using
VAProfileHEVCMain (which is
HEVC/H.265 Main profile). As you can see also
Main 10 profile is supported if you want to use that one and of course
H.264. To give you an impression about encoding speed (
H.265): Using the same parameter just with the different encodings I get around 350-400 frames per second (fps) with
H.265 and with
H.264 I get around 800 fps. So if you’re in a hurry you might prefer
H.264 ;-) But for 1080p and even 1280p videos
H.265 produces better results and smaller files.
So to get started you obviously need FFmpeg installed. It’s a complete, cross-platform solution to record, convert and stream audio and video (in case you never had heard of it). I’m running Archlinux which means that I’ve normally the latest package versions installed. In case of
ffmpeg that means
v4.3.1 at the moment. It should be included in every Linux distribution so just use your package manager to install it.
Let’s talk about the
FFmpeg parameters used above:
-vaapi_device /dev/dri/renderD128 (which is a shortcut to
-init_hw_device vaapi=vaapi0:/dev/dri/renderD128 -filter_hw_device vaapi0) initializes a new hardware device of type
VDR stores MPEG-2 transport streams (
.ts file suffix) in files using two GByte chunks. So I’ve to
concat the files which happens here:
-i specifies the input file(s).
concat is analogous to using
cat on UNIX-like systems. For more information see Concatenate. If your source video file is only one file then
-i whatever_file.mp4 works too of course. Btw. not all video files can be concatenated with
concat filter. MPEG-2 streams are one of the few options were it works.
-vf 'format=nv12,hwupload' creates the filtergraph and use it to filter the stream. The encoders only accept input as VAAPI surfaces. If the input is in normal memory, it will need to be uploaded before giving the frames to the encoder - in the ffmpeg utility, the
hwupload filter can be used for this. It will upload to a surface with the same layout as the software frame, so it may be necessary to add a format filter immediately before to get the input into the right format (hardware generally wants the
nv12 layout). The
hwupload filter also requires a device to upload to, which needs to be defined before the filter graph is created which I did with
Selecting which streams from which inputs will go into which output is either done automatically or with the
-map option. So using
-map 0:0 -c:v hevc_vaapi as in my example uses the first stream of the original file (which is the video in
H.264 encoding) and converts it to
H.265 in the destination file (
H.265 / MPEG-H part 2 (HEVC)). If you run the command later you’ll see the following debugging output:
Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> hevc (hevc_vaapi))
-map 0:a -c:a copy. This causes to copy the audio streams without modification to the new file. In the output it looks like this:
Stream #0:1 -> #0:1 (copy) Stream #0:2 -> #0:2 (copy) Stream #0:3 -> #0:3 (copy)
-rc_mode sets the
rate control mode. To see all possible modes
ffmpeg -h encoder=hevc_vaapi can be used (this command shows all
H.265 VAAPI options btw). These are the available rate control mode’s:
-rc_mode <int> E..V...... Set rate control mode (from 0 to 6) (default auto) auto 0 E..V...... Choose mode automatically based on other parameters CQP 1 E..V...... Constant-quality CBR 2 E..V...... Constant-bitrate VBR 3 E..V...... Variable-bitrate ICQ 4 E..V...... Intelligent constant-quality QVBR 5 E..V...... Quality-defined variable-bitrate AVBR 6 E..V...... Average variable-bitrate
Currently the driver only supports
VBR. After testing a while it turned out that
COP works best for me.
The most important parameter for me is
-global_quality. The lower the value the better is the video quality but of course also the files are larger. Values between
25 are the most interesting ones. The default value is
25 which isn’t that bad. For me this rules works best:
If I’ve a video/movie that was produced in HD a value between
21-23 works best and produces good results. But there are also videos/movies that where produced before HD was en vouge. When broadcasted they are scaled to HD e.g. In this case a value between
24-25 works best.
And finally there is
-profile:v main. This selects the
main should work with every output device. A list of profiles can be found here.
video.mp4 is the output file.
That’s it so far! Happy encoding! ;-)