How to Convert HDR to SDR with FFmpeg
Converting High Dynamic Range (HDR) video to Standard Dynamic Range (SDR) is essential for ensuring compatibility with older displays and standard playback devices. This article provides a straightforward, step-by-step guide on how to perform HDR-to-SDR tone-mapping using FFmpeg, covering the necessary video filters, color space conversions, and command-line examples to achieve high-quality results.
The Recommended FFmpeg Command
To convert a 10-bit HDR video (usually in BT.2020 color space) to an
8-bit SDR video (BT.709 color space), you need to map the colors and
brightness levels accurately. The most effective way to do this in
FFmpeg is by utilizing the zscale and tonemap
filters.
Run the following command in your terminal:
ffmpeg -i input_hdr.mp4 -vf "zscale=t=linear:g=gbr,tonemap=tonemap=hable:desat=2,zscale=p=bt709:t=bt709:m=bt709,format=yuv420p" -c:v libx264 -crf 20 -c:a copy output_sdr.mp4How the Video Filter (vf) Works
The video filter chain (-vf) performs the heavy lifting
of tone-mapping in three distinct stages:
zscale=t=linear:g=gbr: This step uses thezscalefilter to convert the video transfer characteristics to linear light and changes the color gamut to GBR (Green, Blue, Red). Linearizing the input is required before applying tone-mapping mathematical algorithms.tonemap=tonemap=hable:desat=2: This is the core tone-mapping step. It applies the “Hable” algorithm (a popular filmic tone-mapping algorithm) to compress the high dynamic range of HDR down to SDR levels. Thedesat=2parameter desaturates bright highlights to prevent color clipping and preserve detail.zscale=p=bt709:t=bt709:m=bt709,format=yuv420p: This step converts the linearized, tone-mapped video back to the standard BT.709 color primaries (p), transfer characteristics (t), and color matrix (m) used by standard SDR displays. Finally,format=yuv420pconverts the pixel format from 10-bit to standard 8-bit YUV 4:2:0.
Alternative Tone-Mapping Algorithms
If you want to experiment with different visual styles, you can swap
out tonemap=hable with other built-in FFmpeg
algorithms:
- Mobius (
tonemap=mobius): Excellent for preserving contrast in midtones while smoothly rolling off highlights. It is often considered the most balanced option. - Reinhard (
tonemap=reinhard): A classic tone-mapping algorithm that offers a softer contrast. - Linear (
tonemap=linear): Scales the brightness linearly, which can sometimes result in a washed-out look but preserves relative luminance levels perfectly.
To use Mobius, simply modify that part of the filter chain:
tonemap=tonemap=mobius:desat=2Hardware-Accelerated Tone-Mapping (NVIDIA NVENC)
If you have an NVIDIA GPU, you can utilize hardware-accelerated
filters to speed up the process significantly. The cuda
upload and tonemap_cuda filters allow you to perform the
conversion on your GPU:
ffmpeg -hwaccel cuda -i input_hdr.mp4 -vf "hwupload,tonemap_cuda=t=bt709:format=yuv420p,hwdownload,format=yuv420p" -c:v libx264 -crf 20 -c:a copy output_sdr.mp4This method is much faster than CPU-based decoding and filtering,
though it provides fewer fine-tuning options compared to the
zscale library.