Transcoding a HDR video with tonemapping

Before HDR came into wider use, working with video was easier. You didn't have to care about colorspaces [1], it would display fine everywhere and working with it was painless. With HDR it's not so simple anymore: To display it a process called tonemapping needs to be applied and you even need a HDR-capable output device in order to see a meaningful difference compared to SDR (Standard Dynamic Range).

Here I will document how you can convert HDR video to SDR, e.g. to view it on an older device.

If you've tried to naively do this before your result probably looked like one of these:

pic1

misinterpreted as BT.709

pic2

no tonemapping


mpv (via OpenGL)

The first method uses mpv's gpu video filter, which under the hood performs normal rendering on the GPU and downloads the result back to the CPU for encoding. Kind of like taking a screenshot of every frame you see.

Here I pass it to x264 with a very low CRF, suitable for further processing, but you can swap these options as you like.

mpv --no-config hdr.mkv -vf 'gpu,format=yuv420p' --ovc=libx264 --ovcopts=crf=10 --aid=no --sid=no -o sdr.mkv

You will get the following result, success!

/images/hdr_tonemapped.jpg

libplacebo (via Vulkan)

libplacebo was born out of the rendering core of mpv and has seen big improvements over numerous stable releases in the last 6 years.

Since FFmpeg 5.0 it also exists as a filter, which allows its rendering capabilities (such as debanding, dithering or user shaders) to be applied directly while encoding. Notably it uses Vulkan to perform the work on the GPU.

ffmpeg -init_hw_device vulkan \
  -i hdr.mkv -map 0:v \
  -vf 'libplacebo=format=yuv420p:colorspace=bt709:color_primaries=bt709:color_trc=bt709:range=tv' \
  -c:v libx264 -crf 10 -y sdr.mkv

We can tune the pipeline by doing the decoding of the input video right on the GPU: [2]

ffmpeg -export_side_data +film_grain -init_hw_device vulkan -hwaccel vaapi -hwaccel_output_format vaapi \
  -i hdr.mkv -map 0:v \
  -vf 'libplacebo=format=yuv420p:colorspace=bt709:color_primaries=bt709:color_trc=bt709:range=tv' \
  -c:v libx264 -crf 10 -y sdr.mkv

Finally, if you have FFmpeg 7.1 or newer you can leverage the new Vulkan Video support to also offload the encoding, making the process entirely on-GPU:

ffmpeg -export_side_data +film_grain -init_hw_device vulkan=vk -filter_hw_device vk -hwaccel vaapi -hwaccel_output_format vaapi \
  -i hdr.mkv -map 0:v \
  -vf 'libplacebo=format=yuv420p:colorspace=bt709:color_primaries=bt709:color_trc=bt709:range=tv' \
  -c:v hevc_vulkan -y sdr.mkv

Afterword

Although the libplacebo-based process works quite well it is better to avoid the trouble if you can afford it. Upgrade your playback device to support HDR or find a different source for whatever video content it is that interests you.


Stream #0:0(eng): Video: vp9 (Profile 2), yuv420p10le(tv, bt2020nc/bt2020/smpte2084), 2560x1440, [...]
  Side data:
    Mastering Display Metadata, has_primaries:1 has_luminance:1 r(0.6800,0.3200) [...]