Generate an HLS/DASH ABR ladder with FFmpeg: production recipe
Build an adaptive bitrate ladder (5-rendition standard) from a single master file. Closed-GOP discipline, VBV compliance, and the partial-success patterns that survive production load.
When to use this
Adaptive bitrate ladders are how HLS and DASH players adapt to changing bandwidth — the browser picks the right rendition mid-playback based on connection speed. Generate a ladder when your delivery target is HLS or DASH streaming (anything other than direct-progressive download). The standard 5-rendition ladder (240p / 360p / 480p / 720p / 1080p) covers ~95% of viewer connections; add 1440p / 2160p for premium content. Generate from a master encode — re-encoding from the source for each rendition compounds quality loss across runs.
Command variants
# 1080p
ffmpeg -i master.mp4 -c:v libx264 -preset medium -crf 21 \
-profile:v high -level 4.0 -pix_fmt yuv420p \
-x264opts keyint=48:min-keyint=48:no-scenecut \
-maxrate 5500k -bufsize 11000k \
-c:a aac -b:a 192k \
rendition_1080p.mp4
# 720p
ffmpeg -i master.mp4 -c:v libx264 -preset medium -crf 22 \
-profile:v high -level 4.0 -pix_fmt yuv420p -vf scale=1280:720 \
-x264opts keyint=48:min-keyint=48:no-scenecut \
-maxrate 3500k -bufsize 7000k \
-c:a aac -b:a 128k \
rendition_720p.mp4
# Repeat for 480p, 360p, 240p with appropriate maxrate/bufsizeMost flexible — each rendition can fail independently and retry without restarting the whole ladder. The right pattern at production scale.
ffmpeg -i master.mp4 \
-c:v libx264 -preset medium -profile:v high -level 4.0 -pix_fmt yuv420p \
-x264opts keyint=48:min-keyint=48:no-scenecut \
-map 0:v -map 0:v -map 0:v -map 0:v -map 0:v \
-filter:v:0 scale=1920:1080 -b:v:0 4500k -maxrate:v:0 5500k \
-filter:v:1 scale=1280:720 -b:v:1 2800k -maxrate:v:1 3500k \
-filter:v:2 scale=854:480 -b:v:2 1400k -maxrate:v:2 1800k \
-filter:v:3 scale=640:360 -b:v:3 800k -maxrate:v:3 1000k \
-filter:v:4 scale=426:240 -b:v:4 400k -maxrate:v:4 500k \
-c:a aac -b:a 128k \
-f hls -var_stream_map "v:0,a:0 v:1,a:0 v:2,a:0 v:3,a:0 v:4,a:0" \
-hls_time 4 -hls_playlist_type vod \
output_%v.m3u8Single ffmpeg run — faster end-to-end but if any rendition fails, you re-run the whole thing.
What each parameter does
crf 21-26CRF target per rendition. Higher renditions (1080p) get tighter CRF (~21); lower renditions (240p) accept ~26 because the smaller resolution masks quality loss.
maxrate / bufsizeEnforces VBV-compliant streams that won't break HLS bandwidth estimation. Per-rendition values; bufsize is typically 2× maxrate.
keyint=48:min-keyint=48:no-scenecutClosed GOP every 2 seconds (at 24fps) for clean segment boundaries. Without no-scenecut, scene-change keyframes break HLS player rendition switches.
-profile:v high -level 4.0Profile + level cap encoder features so older players can decode. High@4.0 is the standard 1080p compatibility target.
-c:a aac -b:a 128k/192kAudio re-encoded per-rendition. Higher renditions can use higher audio bitrate; standard mobile/desktop HLS targets 128k.
What this outputs
Five separate video files (per-rendition encoding) or a packaged HLS variant playlist (multi-output mode). Total bitrate sum across the ladder ~12 Mbps for 1080p ABR; ~25 Mbps for 4K-included ladders. The packaged HLS playlist references all renditions and is what your player consumes.
Pitfalls
- Scene-cut keyframes that don't align with segment boundaries cause visible artifacts at every rendition switch. The "no-scenecut" flag is non-negotiable for HLS.
- Single-invocation multi-output loses ~5-10% efficiency vs per-rendition encoding because the encoder can't fully optimize each rendition independently. At production scale, run them separately so you can also retry per-rendition.
- Keyframe interval must match segment length. 4-second segments with keyint=48 (at 24fps) align cleanly. Mismatched values produce inefficient segmentation.
- Audio re-encoding per-rendition wastes compute at scale. The optimization: encode audio once at the highest rate, then stream-copy to each rendition's output.
- VBV (maxrate / bufsize) misconfigured = streams that exceed bandwidth and break HLS adaptive logic in the player. Always set both for each rendition.
At production scale
At scale, ladder generation accounts for 4-6× the compute of master encoding. The per-rendition retry pattern (rendition 4 of 6 fails OOM, only rendition 4 retries on a higher-memory pool) becomes essential — without it, a single failed rendition burns the whole ladder. Above ~100K ladders/month, ladder generation is the dominant compute cost; below that, master encoding dominates. Multi-tenant ladder generation requires per-customer rendition matrices because broadcast vs OTT ladders differ in target bitrates and rendition counts.
MpegFlow models ladders as DAGs where each rendition is a separate stage. Per-rendition retry on higher-memory pools when one fails OOM, deterministic packaging that waits for all renditions or fails with a clear "rendition X failed" event you can route to QC. The full pattern is in our broadcast-grade VOD architecture.