Generating HLS Playlists with Bento4

In this post we’re going to look at how to use Bento4 to generate HLS playlists from MP4 files. In previous posts I’ve used ffmpeg to encode the videos and generate the playlist. However, sometimes it’s better to separate the encoding from the packaging. For example, you may want to support both DASH and HLS from the same encodings.

Bento4 also has support for MPEG DASH and many other formats, but in this post I’ll focus specifically on using the command line tools mp4hls and mp42hls to create HLS playlists. You can download the latest version from the website. Once you’ve downloaded it, make sure the bin directory is on your PATH. I used version 1.5.1.0 in the examples.

If you are familiar with Apple’s HLS tools, mp4hls and mp42hls can be used as a replacement for variantplaylistcreator and mediafilesegmenter respectively. Or put another way, if you want to create a master playlist for more than one video, use mp4hls. If you only want to create a playlist for a single video, use mp42hls instead.

For the examples, I’m going to assume there are 3 videos available: sample-1080p.mp4, sample-720p.mp4 and sample-360p.mp4.

Let’s begin.

Media Playlists

To segment a video and create a playlist for it, open up a terminal and run the following command:

$ mp42hls sample-1080p.mp4

If you look in the directory where you ran the command, you’ll see a number of segments (segment-0.ts, segment-1.ts and so on) and the playlist (stream.m3u8). The default segment duration is 6 seconds, which is what Apple recommends, but you can change this if you want to with the --segment-duration option.

If you want to choose the name of the playlist and the segments, you can do so like this:

$ mp42hls --index-filename prog_index.m3u8 \
     --segment-filename-template "fileSequence%d.ts" sample-1080p.mp4

The segment filename template uses a printf-style format string with a specifier (%d) for the segment number.

Depending on the duration of your videos, you could potentially end up with a large number of segments. Instead of having multiple segments, HLS supports creating a single file and referencing the segments in the file using a byte range. (You can think of it as taking all of the segments and concatenating them together to create one file.)

$ mp42hls --output-single-file sample-1080p.mp4

If you look in the output directory you should see the playlist (stream.m3u8) like before but only one segment file (stream.ts). The playlist now contains byte range tags that specify the length and the offset from the beginning of the file of each segment.

This feature requires HLS version 4 (or greater). The version number is set automatically by the tool when the single file option is specified. There is an option (--hls-version) that can be used to set the version of HLS your playlist is compatible with.

You may have noticed after running the previous command that a third file was created: iframes.m3u8. This playlist contains the locations of the I-frames in the video segments. The previous command created it automatically, but if you want to create an I-frame playlist (and you aren’t outputting a single file) you can do so with the --iframe-index-filename option:

$ mp42hls --iframe-index-filename iframes.m3u8 sample-1080p.mp4

Just like outputting a single file, I-frame playlists are a version 4 feature because they also make use of the byte range tag.

Master Playlists

To generate a master playlist from our imaginary videos, run this command:

$ mp4hls sample*

This command takes the list of videos, segments them, creates the playlist for each variant then outputs the master playlist, which references each individual playlist. In this example, instead of specifying the name of each video separately, we let the shell do it for us by using glob expansion. When the command finishes, there will be an output directory. Inside that directory will be the master playlist (master.m3u8) and 3 sub-directories: media-1, media-2, and media-3; there’s one sub-directory for each video. In each of those are the segments and the playlist that correspond to that particular variant.

It’s also possible to specify the name of the master playlist and media playlist. Here’s an example:

$ mp4hls --master-playlist-name=all.m3u8 \
     --media-playlist-name=prog_index.m3u8 sample*

If you want to change the location where the files are output, you can do so by using the -o and specifying the path of the directory to write to.

In addition to the video streams, it’s sometimes necessary to provide an audio-only stream (typically 64kbps). To do that, we can extract the audio from one of the videos and encode it appropriately using ffmpeg:

$ ffmpeg -i sample-1080p.mp4 -vn -c:a aac -b:a 64k -ac 2 audio_64kbps.mp4

To create the master playlist with the audio-only stream, run this command:

$ mp4hls sample* [+audio_fallback=yes]audio_64kbps.mp4

Inside the output directory, in addition to the previous 3 sub-directories, there is now a media-4 directory. Inside the master playlist are the following media playlist entries:

# Media Playlists
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2359034,BANDWIDTH=4135200,CODECS="avc1.640032,mp4a.40.2",RESOLUTION=1920x1080
media-1/stream.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=748116,BANDWIDTH=1295505,CODECS="avc1.64001E,mp4a.40.2",RESOLUTION=854x480
media-2/stream.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=1256065,BANDWIDTH=2203392,CODECS="avc1.64001F,mp4a.40.2",RESOLUTION=1280x720
media-3/stream.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=133642,BANDWIDTH=136500,CODECS="mp4a.40.2"
media-4/stream.m3u8

The last entry corresponds to the audio stream. You can tell because the CODECS attribute (highlighted) is set to AAC-LC audio. There is no video codec component.

Encryption

In this section, we’ll see how to encrypt videos.

The first step is to generate an encryption key. HLS supports AES-128, which means that keys have to be 128 bits in length (16 bytes). We can use openssl to create the key and write it out to a file:

$ openssl rand 16 > enc.key

This key will be used for both the encryption and decryption. Next, we run the command to create the playlist and encrypt the video segments:

$ mp42hls --encryption-key $(hexdump -e '"%x"' enc.key) \
    --encryption-key-uri https://hlsbook.net/enc.key sample-1080p.mp4

We specify the encryption key with the --encryption-key option but mp42hls expects the key to be a hexadecimal string so we have to convert it first. Next, we specify the location of the key with the --encryption-key-uri option. The client will attempt to fetch the key from this location in order to decrypt the video segments.

You can also encrypt each variant when creating a master playlist. To do this with the key we’ve just generated, run this command:

mp4hls --encryption-key=$(hexdump -e '"%x"' enc.key) \
     --encryption-key-uri https://hlsbook.net/enc.key sample*

If the --signal-session-key option is present, a EXT-X-SESSION-KEY tag will be added to the master playlist. This is an optimisation that allows the client to preload the decryption key; otherwise, it would have to fetch the media playlist first to determine the location of the key.

If you want to learn more about HLS encryption, check out this post.

Conclusion

Bento4 is a useful tool that works across multi-platforms so if you don’t have access to Apple’s HLS tools, it’s a good alternative. It’s also simpler to use than ffmpeg. If you don’t believe me, take a look at my post about how to create a master playlist with ffmpeg.

In addition to some of the features highlighted here, it also supports subtitles, multilingual audio, and DRM like Apple’s FairPlay Streaming.

Leave a Reply