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:
misinterpreted as BT.709 |
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!
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.