Creating A Production Ready Multi Bitrate HLS VOD stream

HLS is one of the most prominent video streaming formats on desktop and mobile browsers. Since end users have different screen sizes and different network performance, we want to create multiple renditions of the video with different resolutions and bitrates that can be switched seamlessly, this concept is called MBR (Multi Bit Rate).
For this task we will use ffmpeg, a powerful tool that supports conversion of various video formats from one to another, including HLS both as input and output.

In this guide will show a real world use of ffmpeg to create MBR HLS VOD stream from a static input file.

Installing FFMPEG

ffmpeg is a cross platform program that can run on Windows and OS X as well as Linux.

Windows

OS X

Ubuntu

sudo add-apt-repository ppa:mc3man/trusty-media  
sudo apt-get update  
sudo apt-get install -y ffmpeg

CentOS / Fedora

yum install -y ffmpeg

Latest binaries for all platforms, source code and more information is available at ffmpeg’s official website

Source Media

I will use a .mkv file beach.mkv, though ffmpeg will consume most of the common video formats. sample files can be downloaded from here and here.

To inspect the file properties run the following command:

ffprobe -hide_banner beach.mkv

The file is identified as mkv, 21 seconds long, overall bitrate 19264 kbps, containing one video stream of 1920x1080 23.98fps in h264 codec, and one AC3 audio stream 48kHz 640 kbps.

Multi Bitrate Conversion

First rendition

Lets build a command for one rendition:

ffmpeg -i beach.mkv -vf scale=w=1280:h=720:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -b:a 128k -c:v h264 -profile:v main -crf 20 -g 48 -keyint_min 48 -sc_threshold 0 -b:v 2500k -maxrate 2675k -bufsize 3750k -hls_time 4 -hls_playlist_type vod -hls_segment_filename beach/720p_%03d.ts beach/720p.m3u8

This will generate a VOD HLS playlist and segments in beach folder.

Multiple renditions

Each rendition requires its own parameters, though ffmpeg supports multiple inputs and outputs so all the renditions can be generated in parallel with one long command.
it's very important that besides the resolution and bitrate parameters the commands will be identical so that the renditions will be properly aligned, meaning key frames will be set in the exact same positions to allow smooth switching between them on the fly.

We will create 4 renditions with common resolutions:

ffmpeg -hide_banner -y -i beach.mkv \
  -vf scale=w=640:h=360:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod  -b:v 800k -maxrate 856k -bufsize 1200k -b:a 96k -hls_segment_filename beach/360p_%03d.ts beach/360p.m3u8 \
  -vf scale=w=842:h=480:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 1400k -maxrate 1498k -bufsize 2100k -b:a 128k -hls_segment_filename beach/480p_%03d.ts beach/480p.m3u8 \
  -vf scale=w=1280:h=720:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 2800k -maxrate 2996k -bufsize 4200k -b:a 128k -hls_segment_filename beach/720p_%03d.ts beach/720p.m3u8 \
  -vf scale=w=1920:h=1080:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 5000k -maxrate 5350k -bufsize 7500k -b:a 192k -hls_segment_filename beach/1080p_%03d.ts beach/1080p.m3u8

Master playlist

The HLS player needs to know that there are multiple renditions of our video, so we create an HLS master playlist to point them and save it along side the other playlists and segments.

playlist.m3u8

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=842x480
480p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8

Example Conversion Script

Here is an example conversion script create-hls-vod.sh

Running:

bash create-vod-hls.sh beach.mkv

will produce:

    beach/
      |- playlist.m3u8
      |- 360p.m3u8
      |- 360p_001.ts
      |- 360p_002.ts
      |- 480p.m3u8
      |- 480p_001.ts
      |- 480p_002.ts
      |- 720p.m3u8
      |- 720p_001.ts
      |- 720p_002.ts
      |- 1080p.m3u8
      |- 1080p_001.ts
      |- 1080p_002.ts  

FAQ

How to choose the right bitrate

Bitrate is dependant mostly on the resolution and the content type. when setting bitrate too low an image pixelization will occur especially in areas where there is rapid movement, when bitrate is too high the output files might be excessively big with any additional value.

To choose the right bitrate one must understand his type of content. content with high motion such as sports or news events will require higher bitrate to avoid pixelization while low motion content such as music concerts and interviews will suffice lower bitrate without apparent changes to quality.

Here are some good defaults to start from:

Quality Resolution bitrate - low motion bitrate - high motion audio bitrate
240p 426x240 400k 600k 64k
360p 640x360 700k 900k 96k
480p 854x480 1250k 1600k 128k
HD 720p 1280x720 2500k 3200k 128k
HD 720p 60fps 1280x720 3500k 4400k 128k
Full HD 1080p 1920x1080 4500k 5300k 192k
Full HD 1080p 60fps 1920x1080 5800k 7400k 192k
4k 3840x2160 14000k 18200 192k
4k 60fps 3840x2160 23000k 29500k 192k

How do I feed ffmpeg through stdin?

ffmpeg has a special pipe: flag that instruct ffmpeg to consume stdin as media.

cat clip.mp4 | ffmpeg -f mp4 -i pipe: output.avi

What’s the difference between avconv and ffmpeg

avconv is a fork (clone) of ffmpeg that was created by a group of developers of ffmpeg due to project management issues. While both are actively maintained, its recommended to use ffmpeg since it has larger community as explained here