HLS and fragmented MP4

Abstract

Some tips to convert a MP4 into a fMP4 format in H265 encoding.

At WWDC 2016, Apple announced support for fragmented MP4 (fMP4) as an alternative to MPEG-TS, which prior to their announcement was the only supported format.

So why use fragmented MP4 files? Well, according to Apple’s video encoding requirements in their HLS Authoring Specification, if you want to use HEVC/H.265, you have to use it (1.5). Fragmented MP4 files are also compatible with MPEG-DASH – an alternative to HLS – so you can use the same files; only the manifest file (playlist) is different. This means less encoding and less storage requirements, which should reduce costs.

In this post, I’ll demonstrate how to generate fMP4 files using ffmpeg. You’ll need a fairly recent version of ffmpeg that supports fMP4 – I used version 4.1.1 to create the files in this post.

For the video, I used the 1080p version of the Sintel trailer. You can download it here.

Let’s take the original video (sintel_trailer-1080p.mp4) and re-encode it. We’ll alter the size to 1280×720 and reduce the video bitrate but leave the audio as it is. Open up a terminal and run the following command:

ffmpeg -y \
  -i sintel_trailer-1080p.mp4 \
  -force_key_frames "expr:gte(t,n_forced*2)" \
  -sc_threshold 0 \
  -s 1280x720 \
  -c:v libx264 -b:v 1500k \
  -c:a copy \
  -hls_time 6 \
  -hls_playlist_type vod \
  -hls_segment_type fmp4 \
  -hls_segment_filename "fileSequence%d.m4s" \
  prog_index.m3u8

To generate fMP4 files instead of MPEG-TS, which is the default, set the segment type to fmp4 (highlighted).

The above command will generate the following files: the playlist file (prog_index.m3u8), a number of files with the extension .m4s (instead of .ts), and a file called init.mp4. You’ll need all of these files when you come to play the video.

Let’s take a look at the playlist and see how it compares:

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="init.mp4"
#EXTINF:6.000000,
fileSequence0.m4s
#EXTINF:6.000000,
fileSequence1.m4s
#EXTINF:6.000000,
fileSequence2.m4s
#EXTINF:6.000000,
fileSequence3.m4s
#EXTINF:6.000000,
fileSequence4.m4s
#EXTINF:6.000000,
fileSequence5.m4s
#EXTINF:6.000000,
fileSequence6.m4s
#EXTINF:6.000000,
fileSequence7.m4s
#EXTINF:4.208333,
fileSequence8.m4s
#EXT-X-ENDLIST

I’ve highlighted the differences. The #EXT-X-MAP tag specifies the location of the resource that contains the information required to parse the segments (the media initialization section). It applies to every segment that appears after it. A playlist containing an MPEG-2 transport stream can also apply the #EXT-X-MAP tag, but each segment typically includes the information required to parse it so it’s not needed.

The other difference is the file extension of the segments is .m4s instead of .ts.

HLS also supports byte range addressing. To use this feature with fMP4, set the -hls_flags option to single_file. Instead of creating several (smaller) files, it will create a single file containing all the media segments. If you look at the playlist, the location of each segment is specified as a byte offset in the file.

Now that we have the fMP4 files and the playlist, the next step is to play the video in the browser.

Most browsers don’t support HLS and fMP4 natively so you’ll need to use a video player that utilises Media Source Extensions (MSE). Thankfully, some players do but be aware that some don’t. For example, I tried using hls.js but it didn’t work – according to the documentation, support for fragmented MP4 is still in beta.

Here’s an HTML example that uses Video.js, which does support fMP4:


<!DOCTYPE html>
<html>
<head>
<link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">
</head>
<body>
<video class="video-js" width="1280" height="720" data-setup='{}' controls>
        <source src="prog_index.m3u8" type="application/x-mpegURL">
</video>
<script src='https://vjs.zencdn.net/7.4.1/video.js'></script>
</body>
</html>

Save the HTML. You’ll need to upload this along with all the other files created in the previous step to a web server. I tested playback in Chrome 72 and Firefox 65.0 on Ubuntu and the video played with no problems.

Conclusion

Using ffmpeg makes it easy to generate fMP4 files for HLS. If you need to support other streaming protocols like MPEG-DASH then it’s worth considering. If you encode videos with HEVC/H.265 then fMP4 is mandatory.