Loop file and generate multiple video bitrates muxed in MPEG-TS with ffmpeg

Eyevinn Technology
4 min readApr 17, 2019

--

In a previous post I described how an MPEG-TS multicast stream can be generated with ffmpeg by looping an MP4 file. In this post I will describe how to extend that into generating an MPEG-TS containing multiple video streams each of different resolutions and bitrates.

A quick recap. The ffmpeg command line to produce an mpeg-ts multicast from a video file that is looping looked like this.

ffmpeg -stream_loop -1 -i /mnt/Sunrise.mp4 -map 0:v -vcodec copy -bsf:v h264_mp4toannexb -f h264 - | ffmpeg -framerate 25 -fflags +genpts -r 25 -re -i - -f lavfi -i anullsrc=r=48000:cl=stereo -c:a aac -shortest -vf drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='eyevinntechnology/toolbox-loopts':fontcolor=white@0.9:x=20:y=20:shadowcolor=black:shadowx=2:shadowy=1,drawtext=fontfile=/root/Vera.ttf:fontsize=200:text='%{localtime\:%T}':fontcolor=white@0.9:x=(w-tw)/2:y=250:shadowcolor=black:shadowx=2:shadowy=1,drawtext=fontfile=/root/Vera.ttf:fontsize=40:text='[%{n}/%{pts}]':fontcolor=white@0.9:x=(w-tw)/2:y=h-th-10:shadowcolor=black:shadowx=2:shadowy=1 -vcodec libx264 -preset veryfast -pix_fmt yuv420p -strict -2 -y -f mpegts -r 25 udp://239.0.0.1:1234?pkt_size=1316

We just need to extend it a little bit to generate an mpeg-ts multicast containing multiple video streams of different resolutions and bitrates to end up with this.

ffmpeg -stream_loop -1 -i /mnt/Sunrise.mp4 -map 0:v -vcodec copy -bsf:v h264_mp4toannexb -f h264 - | ffmpeg -thread_queue_size 512 -threads 4 -framerate 25 -fflags +genpts -r 25 -re -i - -f lavfi -i anullsrc=r=48000:cl=stereo -filter_complex [0:v]drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='eyevinntechnology/toolbox-loopts':fontcolor=white@0.9:x=20:y=20:shadowcolor=black:shadowx=2:shadowy=1,drawtext=fontfile=/root/Vera.ttf:fontsize=200:text='%{localtime\:%T}':fontcolor=white@0.9:x=(w-tw)/2:y=250:shadowcolor=black:shadowx=2:shadowy=1,drawtext=fontfile=/root/Vera.ttf:fontsize=40:text='[%{n}/%{pts}]':fontcolor=white@0.9:x=(w-tw)/2:y=h-th-10:shadowcolor=black:shadowx=2:shadowy=1[overlay];[overlay]split=4[o1][o2][o3][o4];[o1]scale=480:360,drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='480x360':fontcolor=white@0.9:x=(w-tw)/2:y=20:shadowcolor=black:shadowx=2:shadowy=1[out360];[o2]scale=854:480,drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='854x480':fontcolor=white@0.9:x=(w-tw)/2:y=20:shadowcolor=black:shadowx=2:shadowy=1[out480];[o3]scale=1280:720,drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='1280x720':fontcolor=white@0.9:x=(w-tw)/2:y=20:shadowcolor=black:shadowx=2:shadowy=1[out720];[o4]scale=1920:1080,drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='1920x1080':fontcolor=white@0.9:x=(w-tw)/2:y=20:shadowcolor=black:shadowx=2:shadowy=1[out1080] -map [out360] -map 1:0 -map [out480] -map [out720] -map [out1080] -c:a aac -shortest -c:v:0 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:0 400k -maxrate 400k -bufsize 16.0k -c:v:1 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:1 1000k -maxrate 1000k -bufsize 40.0k -c:v:2 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:2 2000k -maxrate 2000k -bufsize 80.0k -c:v:3 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:3 4500k -maxrate 4500k -bufsize 180.0k -strict -2 -y -f mpegts -r 25 udp://239.0.0.1:1234?pkt_size=1316

Ok, this might need some further explanation. The principle here is to use the advanced filter graph functionality included in ffmpeg. The graph represented by the line above can be illustrated by the drawing below.

As before we have one ffmpeg process that loops an H.264 file and output the bitstream to stdout and another ffmpeg process that reads from stdin and generates the mpeg-ts.

ffmpeg ... -re -i - -f lavfi -i anullsrc=r=48000:cl=stereo

We define two input streams (0:0) and (1:0) where 0:0 is receiving a bitstream from stdin and 1:0 is a generated silent audio or a test tone. Then we setup the first part of the filter graph where we take the video stream (0:v) and overlay text on and output this on an intermediate stream we call [overlay].

-filter_complex [0:v]drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='eyevinntechnology/toolbox-loopts':fontcolor=white@0.9:x=20:y=20:shadowcolor=black:shadowx=2:shadowy=1,drawtext=fontfile=/root/Vera.ttf:fontsize=200:text='%{localtime\:%T}':fontcolor=white@0.9:x=(w-tw)/2:y=250:shadowcolor=black:shadowx=2:shadowy=1,drawtext=fontfile=/root/Vera.ttf:fontsize=40:text='[%{n}/%{pts}]':fontcolor=white@0.9:x=(w-tw)/2:y=h-th-10:shadowcolor=black:shadowx=2:shadowy=1[overlay]

Then we split the [overlay] stream into 4 streams [o1],[o2],[o3] and [o4].

[overlay]split=4[o1][o2][o3][o4]

Each stream is then scaled and a text specifying the resolution is added on top and a new intermediate stream is created and called [out360] (for the 360p resolution).

[o1]scale=480:360,drawtext=fontfile=/root/Vera.ttf:fontsize=12:text='480x360':fontcolor=white@0.9:x=(w-tw)/2:y=20:shadowcolor=black:shadowx=2:shadowy=1[out360]

We then map each of these 4 scaled down streams and the audio to an output mapping.

-map [out360] -map 1:0 -map [out480] -map [out720] -map [out1080]

which we then associate each with different encoding parameters. For audio:

-c:a aac -shortest

and for the videos we specify bitrate, maxrate and buffer size to cap the video bitrates. In addition to this we also set keyframe interval to a fixed length to make it possible to easier create chunks for http streaming further down the line.

-c:v:0 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:0 400k -maxrate 400k -bufsize 16.0k -c:v:1 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:1 1000k -maxrate 1000k -bufsize 40.0k -c:v:2 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:2 2000k -maxrate 2000k -bufsize 80.0k -c:v:3 libx264 -preset veryfast -pix_fmt yuv420p -g 50.0 -keyint_min 50.0 -force_key_frames expr:gte(t,n_forced*2) -b:v:3 4500k -maxrate 4500k -bufsize 180.0k

Then this is concluded with specifying the output format which in this case is mpegts and a multicast address as the destination.

-strict -2 -y -f mpegts -r 25  udp://host.docker.internal:9998?pkt_size=1316

If you don’t want to manually build this complex line yourself we have updated the loopts container in our open sourced toolbox with this functionality.

docker run --rm -v $PWD:/mnt eyevinntechnology/toolbox-loopts --withtc --multibitrate Sunrise.mp4 "udp://239.0.0.1:1234?pkt_size=1316"

Hope you will find this tool useful and handy to use!

If you have any further questions and comments on this blog drop a comment below or tweet me on Twitter (@JonasBirme).

Update 2017–04–18: added the force_key_frames option to ensure that the keyframes are aligned with all video streams.

Eyevinn Technology is the leading independent consultant firm specializing in video technology and media distribution, and proud organizer of the yearly nordic conference Streaming Tech Sweden.

--

--

Eyevinn Technology

We are consultants sharing the passion for the technology for a media consumer of the future.