FFmpeg Multi-Bitrate Ladder Encoding for HLS and DASH

This article provides a step-by-step guide on how to configure a multi-bitrate ladder for HLS (HTTP Live Streaming) and DASH (Dynamic Adaptive Streaming over HTTP) using FFmpeg. You will learn how to take a single high-quality video input, scale it into multiple resolutions and bitrates using FFmpeg’s filter graphs, and package those streams into adaptive playouts (.m3u8 for HLS and .mpd for DASH) to ensure smooth playback across varying network conditions.


Understanding the Bitrate Ladder

A bitrate ladder is a set of matching video streams encoded at different resolutions, frame rates, and bitrates. When network bandwidth fluctuates, the video player automatically switches between these streams (renditions) to prevent buffering.

For a standard 1080p source, a typical three-tier ladder might look like this: * 1080p (High): 1920x1080 at 5,000 kbps * 720p (Medium): 1280x720 at 2,800 kbps * 480p (Low): 854x480 at 1,400 kbps

To ensure seamless switching, all renditions must have aligned keyframes (GOP size).


1. Preparing the Filter Graph in FFmpeg

To avoid decoding the input file multiple times, use FFmpeg’s split filter to duplicate the input video, then scale each copy to the target resolution.

-filter_complex "[0:v]split=3[v1][v2][v3]; [v1]scale=w=1920:h=1080[v1out]; [v2]scale=w=1280:h=720[v2out]; [v3]scale=w=854:h=480[v3out]"

This configuration splits the input video stream into three identical streams and scales them to 1080p, 720p, and 480p respectively.


2. Encoding and Packaging for HLS

To generate HLS outputs, map the scaled video outputs and the audio track to individual stream specifiers. Use the -f hls muxer along with -var_stream_map to link specific video and audio streams together into variant playlists.

Here is the complete command:

ffmpeg -i input.mp4 \
  -filter_complex "[0:v]split=3[v1][v2][v3]; [v1]scale=w=1920:h=1080[v1out]; [v2]scale=w=1280:h=720[v2out]; [v3]scale=w=854:h=480[v3out]" \
  -map "[v1out]" -c:v:0 libx264 -b:v:0 5000k -maxrate:v:0 5350k -bufsize:v:0 7500k -g 48 -keyint_min 48 -sc_threshold 0 \
  -map "[v2out]" -c:v:1 libx264 -b:v:1 2800k -maxrate:v:1 3000k -bufsize:v:1 4200k -g 48 -keyint_min 48 -sc_threshold 0 \
  -map "[v3out]" -c:v:2 libx264 -b:v:2 1400k -maxrate:v:2 1500k -bufsize:v:2 2100k -g 48 -keyint_min 48 -sc_threshold 0 \
  -map a:0 -c:a:0 aac -b:a:0 192k \
  -map a:0 -c:a:1 aac -b:a:1 128k \
  -map a:0 -c:a:2 aac -b:a:2 96k \
  -f hls \
  -hls_time 4 \
  -hls_playlist_type vod \
  -master_pl_name master.m3u8 \
  -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
  stream_%v.m3u8

Key Parameters Explained:


3. Encoding and Packaging for DASH

DASH uses a Media Presentation Description (.mpd) file. Instead of individual variant playlists, DASH groups streams into “adaptation sets” (typically one set for video streams and one for audio streams).

Here is the command to encode and package the same ladder for DASH:

ffmpeg -i input.mp4 \
  -filter_complex "[0:v]split=3[v1][v2][v3]; [v1]scale=w=1920:h=1080[v1out]; [v2]scale=w=1280:h=720[v2out]; [v3]scale=w=854:h=480[v3out]" \
  -map "[v1out]" -c:v:0 libx264 -b:v:0 5000k -g 48 -keyint_min 48 -sc_threshold 0 \
  -map "[v2out]" -c:v:1 libx264 -b:v:1 2800k -g 48 -keyint_min 48 -sc_threshold 0 \
  -map "[v3out]" -c:v:2 libx264 -b:v:2 1400k -g 48 -keyint_min 48 -sc_threshold 0 \
  -map a:0 -c:a:0 aac -b:a:0 128k \
  -f dash \
  -seg_duration 4 \
  -use_timeline 1 \
  -use_template 1 \
  -adaptation_sets "id=0,streams=v id=1,streams=a" \
  manifest.mpd

Key Parameters Explained: