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.

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.mp4

How the Video Filter (vf) Works

The video filter chain (-vf) performs the heavy lifting of tone-mapping in three distinct stages:

  1. zscale=t=linear:g=gbr: This step uses the zscale filter 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.
  2. 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. The desat=2 parameter desaturates bright highlights to prevent color clipping and preserve detail.
  3. 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=yuv420p converts 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:

To use Mobius, simply modify that part of the filter chain:

tonemap=tonemap=mobius:desat=2

Hardware-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.mp4

This method is much faster than CPU-based decoding and filtering, though it provides fewer fine-tuning options compared to the zscale library.