HDR-to-SDR conversion is the pipeline operation that derives an SDR variant from an HDR master. Every pipeline shipping HDR content also ships SDR fallback for the audience without HDR-capable displays, and the SDR variant is typically tone-mapped from the HDR master rather than separately graded. The conversion is non-trivial — linearization, color space transformation, tone mapping, and re-quantization in the right order with the right parameters. This page is the engineering reference.
What HDR-to-SDR conversion involves
The full pipeline:
- Linearize — convert PQ-encoded HDR values to linear light.
- Adjust color primaries — BT.2020 → BT.709 gamut conversion.
- Tone-map — compress HDR luminance range to SDR's 100-nit ceiling.
- Gamut clipping or compression — handle BT.2020 colors outside BT.709.
- Re-quantize — convert linear light back to BT.1886 (SDR gamma) encoding.
Each stage has parameters and choices. Get any wrong and output is broken (washed out, oversaturated, color shifted, banded).
Linearization
The first step: convert PQ-encoded values to linear light values normalized to a chosen peak.
PQ (SMPTE ST 2084) maps display values 0-1023 (10-bit) to absolute luminance 0.005-10,000 nits. The inverse transform (linearization) converts these encoded values to linear light proportional to peak.
For ffmpeg, this is the zscale=t=linear:npl=N filter:
zscale=t=linear:npl=100
npl (normal peak luminance) sets the linear-light reference. For SDR target (100-nit reference), use npl=100. The PQ values are normalized so that 100 nits = 1.0 in linear space.
After this stage, the data is in linear light, normalized for SDR rendering.
Color primaries conversion
HDR content is typically BT.2020 primaries (wider gamut than SDR's BT.709). Converting requires a 3×3 matrix transformation:
zscale=p=bt709
This converts from the source primaries to BT.709 in linear light space. The conversion is mathematically straightforward; the result has all colors expressed in BT.709 primary system.
Tone mapping
The compressed luminance range (HDR's wide range → SDR's narrow range) is the perceptually challenging part. Tone mapping algorithms:
- Linear — scale proportionally. Produces dim output.
- Reinhard — sigmoid curve. Cheap; flat-looking.
- Hable (Uncharted 2) — filmic curve. Production default for streaming.
- BT.2390 — ITU-R standard. Broadcast-aligned.
- ACES — film industry. For cinema-grade output.
For ffmpeg with the tonemap filter:
tonemap=tonemap=hable:desat=0
desat=0 preserves saturation; default desat=2.0 reduces saturation in highlights (sometimes desirable, sometimes not). For most streaming SDR variants, desat=0 produces cleaner output.
For higher-quality tone mapping with libplacebo:
ffmpeg -i hdr.mp4 -vf "libplacebo=tonemapping=bt2390:colorspace=bt709:format=yuv420p" -c:v libx264 -crf 22 sdr.mp4
libplacebo's tone mapping is more sophisticated than ffmpeg's built-in tonemap filter. For premium SDR derivation, libplacebo is the better choice when available.
Gamut handling
After tone mapping, some BT.2020 colors are still outside BT.709's range (the wider BT.2020 gamut has colors no SDR display can represent). Two approaches:
Clip — saturated colors outside BT.709 get clipped to the BT.709 boundary. Simple; produces hard-edge color clipping artifacts on saturated content.
Compress — gradually compress all colors toward the BT.709 boundary as they approach it. Smoother; preserves more relative color relationships at cost of overall saturation reduction.
For most production tone mapping, compression is preferred. ffmpeg's built-in tonemap doesn't have explicit gamut compression options; libplacebo's peak_detect=yes handles this more gracefully.
Re-quantization
The final stage: convert the tone-mapped linear light back to encoded values in the SDR target space.
zscale=t=bt709:m=bt709:r=tv,format=yuv420p
Components:
t=bt709— BT.1886 transfer function (SDR gamma curve, equivalent to BT.709 EOTF).m=bt709— BT.709 matrix coefficients (Y'CbCr conversion).r=tv— limited (TV) range (16-235 for 8-bit).format=yuv420p— output pixel format.
After this, the data is properly-encoded SDR ready for streaming.
The complete ffmpeg command
Putting it all together:
ffmpeg -i hdr_source.mp4 \
-vf "zscale=t=linear:npl=100, \
format=gbrpf32le, \
zscale=p=bt709, \
tonemap=tonemap=hable:desat=0, \
zscale=t=bt709:m=bt709:r=tv, \
format=yuv420p" \
-c:v libx264 -crf 22 -c:a copy \
sdr_output.mp4
Step by step:
zscale=t=linear:npl=100— to linear light, peak=100 nits.format=gbrpf32le— float-point GBR for accuracy.zscale=p=bt709— primaries to BT.709.tonemap=tonemap=hable:desat=0— apply Hable tone mapping.zscale=t=bt709:m=bt709:r=tv— back to BT.709 transfer/matrix/limited range.format=yuv420p— final pixel format.
This pipeline is multi-step because tone mapping is fundamentally a multi-stage color science operation.
Tone mapping algorithm comparison
For your specific content, validate algorithm choice:
# Test multiple algorithms; encode with each
for tm in clip linear reinhard hable mobius; do
ffmpeg -i hdr_source.mp4 -vf "...tonemap=tonemap=$tm..." -c:v libx264 -crf 22 "sdr_$tm.mp4"
done
Compare visually:
- Highlights (bright sun, lights, reflections).
- Shadows (dark scenes, blacks).
- Midtones (faces, mid-bright objects).
- Saturated colors (red emergency lights, blue skies, neon signs).
Different algorithms favor different content. Hable is a general-purpose default; algorithm choice can be content-specific for premium pipelines.
Verification
After conversion, verify:
Color signaling check:
ffprobe -v error -select_streams v:0 -show_entries stream=color_primaries,color_transfer,color_space sdr.mp4
Expected: bt709 / bt709 / bt709 (no longer BT.2020/PQ).
Histogram comparison:
Compare HDR source histogram with SDR output histogram. SDR should have:
- Narrower dynamic range (100-nit ceiling vs 1000-10000 in HDR).
- Tighter color distribution (BT.709 gamut vs BT.2020).
- Reasonable shadow and highlight detail.
If shadows are crushed or highlights are blown, tone mapping needs adjustment.
Side-by-side visual comparison:
Play HDR on HDR display alongside SDR on SDR display. The SDR should look reasonable — not dramatically different in mood/color from the HDR.
For premium content, golden-eyes review of the SDR variant is recommended.
Common HDR-to-SDR bugs
Bug 1: Skipped linearization.
Without zscale=t=linear, the tonemap filter operates in PQ space directly. Results are wrong.
Bug 2: Wrong primary conversion.
Operating on BT.2020 primaries in BT.709-aware downstream stages produces washed-out colors.
Bug 3: Wrong npl value.
Setting npl=10000 for SDR target produces overly compressed output. SDR target should use npl=100.
Bug 4: Missing format conversion.
format=gbrpf32le is required for the linear-light operations. Without it, integer math doesn't preserve precision.
Bug 5: Limited range vs full range mismatch.
Output range=tv for limited; range=pc for full. Mismatch produces washed-out or crushed output.
Bug 6: Wrong tonemap parameter.
Each tonemap algorithm has its own parameters. desat=0 vs desat=2.0 produces dramatically different output. Validate the parameter choice.
Performance considerations
Tone mapping is compute-expensive:
- Per-frame linear-light operations.
- Per-pixel float math.
- Multi-stage filter chain.
For real-time tone mapping (live HDR-to-SDR), GPU-accelerated tone mapping via libplacebo or vendor-specific filters is much faster. CPU-based tone mapping struggles with real-time at high resolutions.
For VOD pipelines, tone mapping happens once per asset; modest extra time per encode is acceptable.
SDR-from-HDR vs separately-graded SDR
Two approaches for SDR delivery:
Tone-mapped SDR: derive SDR from HDR master via algorithmic conversion. Cheaper; less control; output looks like HDR-tone-mapped (which it is).
Separately-graded SDR: colorist creates an SDR-specific grade alongside the HDR grade. More expensive; more control; output looks intentionally crafted for SDR.
For premium content (theatrical features), separately-graded SDR is the standard. For streaming content where budget is tighter, tone-mapped SDR is acceptable.
For pipeline operations, tone-mapped SDR is the default unless the customer's editorial pipeline produces separate SDR grades.
Operational considerations
Things that matter for HDR-to-SDR in production:
- Per-content validation — different content tone-maps differently. Validate visually before mass deployment.
- Algorithm consistency — pick one tone mapping algorithm; use across the catalog. Don't mix algorithms inconsistently.
- Audience tolerance — for premium content, golden-eyes on tone-mapped SDR catches issues. For volume content, automated metrics suffice.
- Encoder pinning — the ffmpeg version affects tone mapping behavior. Pin versions and re-validate on upgrades.
- Performance monitoring — tone mapping is expensive; instrument for capacity planning.
Quality measurement of tone-mapped output
Measuring tone-mapped SDR quality is harder than measuring same-format encoding quality. VMAF can compare tone-mapped SDR to a separately-encoded SDR baseline, but there's no clean "VMAF against HDR source" measurement (VMAF assumes same-format reference).
Practical approach:
- Maintain a "ground truth" SDR baseline — separately-graded SDR or accepted tone-mapped output. Use as reference.
- Measure VMAF against this baseline — captures tone-mapping algorithm changes.
- Subjective review — for content where automated metrics are uncertain.
Some specialized metrics (HDR-VQM, DRIM) are designed for HDR-to-SDR comparison but aren't widely deployed.
Tone mapping for live workflows
For live HDR-to-SDR (e.g., HDR live broadcast simulcast in SDR), the tone mapping must run in real-time. Considerations:
- Hardware tone mapping — some GPUs and broadcast hardware support real-time tone mapping. For 4K HDR live, hardware is essentially mandatory.
- Algorithm constraints — complex algorithms (libplacebo BT.2390 with peak detection) may not run real-time. Simpler algorithms (Hable, Reinhard) are more live-friendly.
- Latency budget — tone mapping adds frame-level latency. Plan within total latency budget.
Most live HDR-to-SDR workflows use NVIDIA's NVENC HDR/SDR processing or AMD's hardware tone mapping with broadcast-aligned algorithms.
What MpegFlow does with HDR-to-SDR conversion
MpegFlow's DAG runtime expresses HDR-to-SDR conversion as a discrete FfmpegExecutor tone-map stage via the tonemap filter; the partitioner persists it to job_stages with explicit dependency tracking; cross-stage data flow wires the upstream HDR rendition output into the tone-map stage's input; per-stage retry handles transient failures; sibling cancellation propagates fatal failures across rendition stages.
Default algorithm is Hable filmic via FFmpeg's tonemap filter; alternative algorithms (mobius, reinhard) are selectable via filter parameters. An FfprobeExecutor stage characterizes the source's HDR signaling upstream so the conversion receives accurate metadata via cross-stage data flow.
For multi-format HDR delivery (HDR10 + SDR), parallel sibling rendition stages produce the HDR and SDR outputs from the same upstream master — the tone-map stage feeds the SDR encode rendition while the HDR encode rendition runs without it. Dolby Vision and HDR10+ dynamic-metadata workflows that depend on metadata-aware tone mapping (DV display-mapping, HDR10+ scene-by-scene mapping) are operator-side work today rather than pipeline-native operations.
The strict-broker security model handles tone-mapping work like any pipeline payload — workers carry no ambient credentials; content access flows through short-lived presigned URLs scoped per stage; access is disposed on completion.
For customers building HDR-to-SDR pipelines, the conversation typically focuses on algorithm choice (defaults work; premium content benefits from custom config), QC procedures (automated metrics + human review), and performance planning (tone mapping is compute-expensive).
The general guidance: HDR-to-SDR conversion is precise but mechanical. Get the pipeline stages right; choose the algorithm intentionally; validate output visually; don't trust automated metrics alone for premium content. Tone mapping is one of the parts of HDR pipelines that's invisible when correct and painful when wrong.