Zum Inhalt springen

MPEG-DASH and HLS Adaptive Bitrate Streaming with ffmpeg

Last updated on 13. April 2022

Online learning, product explanations, image videos, online fairs, in times of Corona we need video more than ever. But video has the problem, that the files are really big and hard to stream over the internet. Additionally the bad thing in the past was, that a little smartphone screen is getting the same big video as a huge desktop monitor. Most of the time the user experience needs a good sound all the time, but not always the utter most best video quality. MPEG-DASH and HLS are providing a solution by sending different little chunks of video and audio according to the stability and capability of the internet connection. The video is not getting stuck, the sound is heard clearly all the time and only the quality of the video varies. But in the end the user is getting a seamless video experience.

Many people use YouTube for this, but if you read the community rules, you will realise, that the rights you have to give for this service to Google/Alphabet are tremendous. And do you really want your website visitor to end up on YouTube watching videos, instead of checking out the content of your website? Yes, you can also embed YouTube, but you still give them a lot of rights and insights plus your site is not rated so high for SEO since you have „second hand content“, YouTube content on it and not something original.

It is not so difficult to stream directly from your own website, to keep your visitors on your site and your creative rights on the video by yourself.

ffmpeg inputfile.mov \ -c:v libx264 -s 640x360 -r25 -crf 21 -maxrate 1M -bufsize 2M -preset veryslow -keyint_min 100 -g 100 -sc_threshold 0 \ -c:a aac -b:a 128k -ac 1 \ -f hls -hls_time 4 -hls_playlist_type vod \ stream.m3u8

commanddescription
-c:v libx264codec for H264
–s 640×360size of the video
-r 25frames (rate) per second fps 25
-crf 21quality of the video Constant Rate Factor 17-18 visually lossless, 23 default, 0 lossless, 51 highest
-maxrate 1Mmaximum bitrate nevertheless of the quality factor crf
-bufsize 2Mthe size of blog among the maxrate in average is achieved
-preset veryslowthe time the codec takes to analyse the video and to get the best compression; goes from „ultrafast“ to „veryslow“, default is „medium“, for website video it can be „veryslow“, since we only have to wait once for ffmpeg to finish decoding“
-keyint_min 100keyframes minimum every 100 frames, if the video is 25fps this gives you minimum every 4 seconds a keyframe, apple recommends 2 seconds
-g 100maximum amout of GOP size, maximum every 100 frames there will be a keyframe, together with -keyint_min this gives a keyframe every 100 frames
-sc_threshold 0ffmpeg has scene detection, if a scene ends the software will set automatically a keyframe, this is distrubing here, we want equal blocks of exactly the same size; also important for buffering, 0 (stands for false) blocks this behavior
-c:a aacaudio codec aac
-b:a 128kbitrate for the audio; apple recommends 32-160 kb/s
-ac 1audio channels, here we stay with mono = 1
-f hlsthe output format is HLS, HTTP-Live-Streaming (for Apple devices)
-hls_time 4the size of the segments of audio and video, shorter: faster adaption to the internet line; longer: less files to handle, less disc space; the longer the video the bigger the hls_time; Set the target segment length in seconds. Default value is 2. Segment will be cut on the next key frame after this time has passed.
-hls_playlist_type eventEmit #EXT-X-PLAYLIST-TYPE:EVENT in the m3u8 header. Forces hls_list_size to 0; the playlist can only be appended to. This command forces the player not to delete old segments -> the life stream video can be searched -> a live stream can be paused; „vod“ = Emit #EXT-X-PLAYLIST-TYPE:VOD in the m3u8 header. Forces hls_list_size to 0; the playlist must not change. Video on Demand „vod“ MUST BE WRITTEN IN small LETTERS!

-> link to a ffmpeg H.264 manual

Different streams for diverent qualities

Apple recommendations

-> Apple detailed description of HLS

  • keyframes every 2 seconds
  • profile for the codec -profile:v high

Apple size recommendations

16:9 aspect ratioH.264/AVC kb/sFrame rate
416 x 234145≤ 30 fps
640 x 360365≤ 30 fps
768 x 432730≤ 30 fps
768 x 4321100≤ 30 fps
960 x 5402000same as source
1280 x 7203000same as source
1280 x 7204500same as source
1920 x 10806000same as source
1920 x 10807800same as source

Bitrate 2000k should be default, default = first in line

Tip: For frames per second just use 25fps, 30fps and 60fps is for the US norms, derived from the old US TV-standards.

Extended code for different streams

#!/bin/bash
VIDEO_IN=/Users/thomas/Desktop/Test/test20.mov
VIDEO_OUT=master
HLS_TIME=4
FPS=25
GOP_SIZE=100
CRF_P=21
PRESET_P=veryslow
V_SIZE_1=960x540
V_SIZE_2=416x234
V_SIZE_3=640x360
V_SIZE_4=768x432
V_SIZE_5=1280x720
V_SIZE_6=1920x1080

ffmpeg -i $VIDEO_IN \
    -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 -r $FPS -c:v libx264 -pix_fmt yuv420p -crf $CRF_P \
    -map v:0 -s:0 $V_SIZE_1 -maxrate:0 2M -bufsize:0 4M \
    -map v:0 -s:1 $V_SIZE_2 -maxrate:1 145k -bufsize:1 290k \
    -map v:0 -s:2 $V_SIZE_3 -maxrate:2 365k -bufsize:2 730k \
    -map v:0 -s:3 $V_SIZE_4 -maxrate:3 730k -bufsize:3 1460k \
    -map v:0 -s:4 $V_SIZE_4 -maxrate:4 1.1M -bufsize:4 2.2M \
    -map v:0 -s:5 $V_SIZE_5 -maxrate:5 3M -bufsize:5 6M \
    -map v:0 -s:6 $V_SIZE_5 -maxrate:6 4.5M -bufsize:6 9M \
    -map v:0 -s:7 $V_SIZE_6 -maxrate:7 6M -bufsize:7 12M \
    -map v:0 -s:8 $V_SIZE_6 -maxrate:8 7.8M -bufsize:8 15.6M \
    -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -c:a aac -b:a 128k -ac 1 -ar 44100\
    -f hls -hls_time $HLS_TIME -hls_playlist_type vod \
    -master_pl_name $VIDEO_OUT.m3u8 \
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3 v:4,a:4 v:5,a:5 v:6,a:6 v:7,a:7 v:8,a:8" stream_%v.m3u8
commanddescription
VIDEO_INconstant for the complete path to the input video
VIDEO_OUTname of the master-file name; no path possible -> path will be the folder in which the bash-script is located; the master-file has the reference to the different stream-files, which have the reference to the video-audio files (audio and video are not seperated like in MPEG-DASH)
V_SIZE_xthe different sizes of the output, sizes here as recommended by Apple
-pix_fmt yuv420pif encoding from .mov ffmpeg will produce a „yuv422p10le(tv, bt709/bt709/unknown, progressive)“ pixel format that is not readable by browsers; it is a 10bit pixelformat „10le“
-map a:0for each video we need a audio declaration (looks like a loop is going on in the background); 9 video streams 9 times the same audio in my case
-ar 44100sample rate for audio; a CD uses 44100 this is enough; most modern cameras use 48000Hz
-hls_playlist_type vodchanged to „vod“, since there is no input stream, just streaming from a website
-var_stream_map „v:0,a:0 … v:8,a:8“comma seperated pairs of video,audio streams which are mapped together, in case different video quality gets different audio quality
stream_%v.m3u8the %-sign will be replaced by the stream numbers

-> good link to ffmpeg hls

#!/bin/bash
VIDEO_IN=/Users/thomas/Desktop/Test/test20.mov
VIDEO_OUT=master
HLS_TIME=4
FPS=25
GOP_SIZE=100
CRF_P=21
PRESET_P=veryfast
V_SIZE_1=960x540
V_SIZE_2=416x234
V_SIZE_3=640x360
V_SIZE_4=768x432
V_SIZE_5=1280x720
V_SIZE_6=1920x1080

#HLS
ffmpeg -i $VIDEO_IN \
    -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 -r $FPS -c:v libx264 -pix_fmt yuv420p -crf $CRF_P \
    -map v:0 -s:0 $V_SIZE_1 -maxrate:0 2M -bufsize:0 4M \
    -map v:0 -s:1 $V_SIZE_2 -maxrate:1 145k -bufsize:1 290k \
    -map v:0 -s:2 $V_SIZE_3 -maxrate:2 365k -bufsize:2 730k \
    -map v:0 -s:3 $V_SIZE_4 -maxrate:3 730k -bufsize:3 1460k \
    -map v:0 -s:4 $V_SIZE_4 -maxrate:4 1.1M -bufsize:4 2.2M \
    -map v:0 -s:5 $V_SIZE_5 -maxrate:5 3M -bufsize:5 6M \
    -map v:0 -s:6 $V_SIZE_5 -maxrate:6 4.5M -bufsize:6 9M \
    -map v:0 -s:7 $V_SIZE_6 -maxrate:7 6M -bufsize:7 12M \
    -map v:0 -s:8 $V_SIZE_6 -maxrate:8 7.8M -bufsize:8 15.6M \
    -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -c:a aac -b:a 128k -ac 1 -ar 44100\
    -f hls -hls_time $HLS_TIME -hls_playlist_type vod -hls_flags independent_segments \
    -master_pl_name $VIDEO_OUT.m3u8 \
    -hls_segment_filename stream_%v/s%06d.ts \
    -strftime_mkdir 1 \
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3 v:4,a:4 v:5,a:5 v:6,a:6 v:7,a:7 v:8,a:8" stream_%v.m3u8

Issues

Using the Apple mediastreamvalidator to check the code

-> Link to different Apple Tools

-> Apple Live Streaming Tools in the developer section

The mediastreamvalidator is part of a bundle and can be installed on a Mac using the normal programme install automatic for .dmg files.

Then go to Terminal navigate into the folder where the master.m3u8 is located

mediastreamvalidator master.m3u8

The answer was as follows:

MUST fix issues

Error: Measured peak bitrate compared to master playlist declared value exceeds error tolerance
--> Detail:  Measured: 3167.80 kb/s, Master playlist: 5090.80 kb/s, Error: 37.77%
--> Source:  master.m3u8
--> Compare: stream_6.m3u8

--> Detail:  Measured: 5919.37 kb/s, Master playlist: 6740.80 kb/s, Error: 12.19%
--> Source:  master.m3u8
--> Compare: stream_7.m3u8

--> Detail:  Measured: 5919.37 kb/s, Master playlist: 8720.80 kb/s, Error: 32.12%
--> Source:  master.m3u8
--> Compare: stream_8.m3u8

--> Detail:  Measured: 1052.42 kb/s, Master playlist: 943.80 kb/s, Error: 11.51%
--> Source:  master.m3u8
--> Compare: stream_3.m3u8

--> Detail:  Measured: 1507.76 kb/s, Master playlist: 1350.80 kb/s, Error: 11.62%
--> Source:  master.m3u8
--> Compare: stream_4.m3u8

Explanation

-> Explanation for max-bitrates from the Apple spezification

  • 1.26. For VOD content, the average segment bit rate MUST be within 10% of the AVERAGE-BANDWIDTH attribute. (See Declared versus measured values of bandwidths.)
  • 1.27. For VOD content, the measured peak bit rate MUST be within 10% of the BANDWIDTH attribute.
  • 1.28. For live/linear content, the average segment bit rate over a long (~1 hour) period of time MUST be less than 110% of the AVERAGE-BANDWIDTH attribute.
  • 1.29. For live/linear content, the measured peak bit rate MUST be less than 125% of the BANDWIDTH attribute.

My idea of giving the encoder the freedom to go down to lower bitrates in easy to code areas by giving the encoder only a „-crf“ qualtiy factor for encoding and then only set a top limit with the „-maxrate“ statement doesn’t work. In the first three error messages my real bitrate is lower then the declared bitrate by 37.7%, 32.12% and 12%. Stream 3 and Stream 4 are a little bit higher than the declared bitrate.

Conclusion

Instead of using a encoding quality factor „-crf“ I have to use a set bitrate with „-b:v:X“ for every stream and ommit the „-crf“ statement. To fine-tune the we can change the -bufsize:v:X between 150% of the set bitrate to 200% of the set bitrate. I took 175%. The bufsize is the area in witch the bitrate is calculated and adjusted by the encoder. The -maxrate:v:X can be set 7% (10%) above the set bitrate of -b:v:X.

#!/bin/bash
VIDEO_IN=/Users/thomas/Desktop/Test/test20.mov
VIDEO_OUT=master
HLS_TIME=4
FPS=25
GOP_SIZE=100
PRESET_P=veryfast
V_SIZE_1=960x540
V_SIZE_2=416x234
V_SIZE_3=640x360
V_SIZE_4=768x432
V_SIZE_5=1280x720
V_SIZE_6=1920x1080

# HLS
ffmpeg -i $VIDEO_IN \
    -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 -r $FPS -c:v libx264 -pix_fmt yuv420p \
    -map v:0 -s:0 $V_SIZE_1 -b:v:0 2M -maxrate:0 2.14M -bufsize:0 3.5M \
    -map v:0 -s:1 $V_SIZE_2 -b:v:1 145k -maxrate:1 155k -bufsize:1 220k \
    -map v:0 -s:2 $V_SIZE_3 -b:v:2 365k -maxrate:2 390k -bufsize:2 640k \
    -map v:0 -s:3 $V_SIZE_4 -b:v:3 730k -maxrate:3 781k -bufsize:3 1278k \
    -map v:0 -s:4 $V_SIZE_4 -b:v:4 1.1M -maxrate:4 1.17M -bufsize:4 2M \
    -map v:0 -s:5 $V_SIZE_5 -b:v:5 3M -maxrate:5 3.21M -bufsize:5 5.5M \
    -map v:0 -s:6 $V_SIZE_5 -b:v:6 4.5M -maxrate:6 4.8M -bufsize:6 8M \
    -map v:0 -s:7 $V_SIZE_6 -b:v:7 6M -maxrate:7 6.42M -bufsize:7 11M \
    -map v:0 -s:8 $V_SIZE_6 -b:v:8 7.8M -maxrate:8 8.3M -bufsize:8 14M \
    -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -c:a aac -b:a 128k -ac 1 -ar 44100 \
    -f hls -hls_time $HLS_TIME -hls_playlist_type vod -hls_flags independent_segments \
    -master_pl_name $VIDEO_OUT.m3u8 \
    -hls_segment_filename stream_%v/s%06d.ts \
    -strftime_mkdir 1 \
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3 v:4,a:4 v:5,a:5 v:6,a:6 v:7,a:7 v:8,a:8" stream_%v.m3u8

For stream_1 I still got error messages so I changed to „-bufsize:1 220k“, that is 152%.

Adding MPEG-DASH streaming

MPEG-DASH is the new standard for Adaptive Bitrate Streaming. It is not supported directly by the browsers, but works with the help of the dash.js JavaScript-Player. This is the solution for all but Apple devices.

More about dash.js: cdn.dashjs.org

DASH

ffmpeg -i $VIDEO_IN \
    -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 \
    -r $FPS -c:v libx264 -pix_fmt yuv420p -c:a aac -b:a 128k -ac 1 -ar 44100 \
    -map v:0 -s:0 $V_SIZE_1 -b:v:0 2M -maxrate:0 2.14M -bufsize:0 3.5M \
    -map v:0 -s:1 $V_SIZE_2 -b:v:1 145k -maxrate:1 155k -bufsize:1 220k \
    -map v:0 -s:2 $V_SIZE_3 -b:v:2 365k -maxrate:2 390k -bufsize:2 640k \
    -map v:0 -s:3 $V_SIZE_4 -b:v:3 730k -maxrate:3 781k -bufsize:3 1278k \
    -map v:0 -s:4 $V_SIZE_4 -b:v:4 1.1M -maxrate:4 1.17M -bufsize:4 2M \
    -map v:0 -s:5 $V_SIZE_5 -b:v:5 3M -maxrate:5 3.21M -bufsize:5 5.5M \
    -map v:0 -s:6 $V_SIZE_5 -b:v:6 4.5M -maxrate:6 4.8M -bufsize:6 8M \
    -map v:0 -s:7 $V_SIZE_6 -b:v:7 6M -maxrate:7 6.42M -bufsize:7 11M \
    -map v:0 -s:8 $V_SIZE_6 -b:v:8 7.8M -maxrate:8 8.3M -bufsize:8 14M \
    -map 0:a \
    -init_seg_name init\$RepresentationID\$.\$ext\$ -media_seg_name chunk\$RepresentationID\$-\$Number%05d\$.\$ext\$ \
    -use_template 1 -use_timeline 1  \
    -seg_duration 4 -adaptation_sets "id=0,streams=v id=1,streams=a" \
    -f dash Dash/dash.mpd

ffmpeg commands for DASH

commanddescription
VIDEO_INconstant for the complete path to the input video
-preset veryslowthe time the codec takes to analyse the video and to get the best compression; goes from „ultrafast“ to „veryslow“, default is „medium“, for website video it can be „veryslow“, since this is only the time we once have to wait for ffmpeg to finish decoding“
-keyint_min 100keyframes minimum every 100 frames, if the video is 25fps this gives you minimum every 4 seconds a keyframe, apple recommends 2 seconds
-g 100maximum amout of GOP size, maximum every 100 frames there will be a keyframe, together with -keyint_min this gives a keyframe every 100 frames
-sc_threshold 0ffmpeg has scene detection, if a scene ends the software will set automatically a keyframe, this is distrubing here, we want equal blocks of exactly the same size; also important for buffering, 0 (stands for false) blocks this behavior
-r 25frames (rate) per second fps 25
-c:v libx264codec for H264
-pix_fmt yuv420pif encoding from .mov ffmpeg will produce a „yuv422p10le(tv, bt709/bt709/unknown, progressive)“ pixel format that is not readable by browsers; it is a 10bit pixelformat „10le“
-c:a aacaudio codec aac
-b:a 128kbitrate for the audio; apple recommends 32-160 kb/s
-ac 1audio channels, here we stay with mono = 1
-ar 44100sample rate for audio; a CD uses 44100 Hz this is enough; most modern cameras use 48000 Hz
-map v:0meaning: take the first video stream (index 0) and do something with it …
-s:0the size of the first (index 0) output stream e.g. 1920×1080
-b:v:1the bitrate of the second (index 1) output video stream, ist exepts k=kilo bytes/second and m=meg mytes/second
-maxrate 1Mmaximum bitrate during a range of video chunk (expressed through bufsize) in witch the maxrate is calculated
-bufsize 2Mthe size of blog among the maxrate in average is achieved
-map 0:awe only want one audio file to output for all video sizes, so here only one -map-command is listed for the output, if we would want more audio streams we would have to have two -map-commands …, we take from all inputs „0“ the audio „a“; ( „-map 0“ would take all inputs to do something with them)
-init_seg_name init\$RepresentationID\$.\$ext\$This gives the init-file a name. The default name is „init-stream“ since this are many files I shortend the name to „init“. If used with a dash-script the dollar sign must be escaped for the ffmpeg variables „$var$“ „\“.
-init_seg_name init\$RepresentationID\$.\$ext\$This gives the init-file a name. The default name is „init-stream“ since this are many files I shortend the name to „init“. If used with a dash-script in the ffmpeg variables e.g. „$var$“ the dollar sign must be escaped „\“.
-media_seg_name chunk\$RepresentationID\$-\$Number%05d\$.\$ext\$This gives the video and audio files a name. The default name is „chunk-stream“. There will be a lot of chunks, so I shortend the name to „chunk“ to save some download time. Fot the bash-script we need to escape the dollar signs.
-use_template 1In order not to list every single chunk of video or audio in the dash manifest it is better to use a template, that shows how to access the right chunk of video, instead of a long, long list. The template works with variables and patterns, instead of listing every chunk of video and audio, to choose from.
-use_timeline 1Enable (1) or disable (0) the use of a SegmentTimline inside a SegmentTemplate. The SegmentTimeline provides more possibilities then the duration attribute. It also enables to specify exact segment durations.
-seg_durationThe length of an individual segment. Only between segments the player can switch bandwidth. Default 8 seconds, I took 4.
-adaptation_sets „id=0,streams=v id=1,streams=a“Adaption set is a term from the Dash specification. Adaption Sets contain a media stream or many of them. In many cases you have one video adaption set and for each language a audio adaption set. In our case we have one adaption set (index 0) with different video streams and one adaption set (index 1) with one audio stream.
-f dashformat is dash
Dash/dash.mpdall output goes into the Dash folder, the manifest is dash.mpd
Recommendations of the ffmpeg documantation

-profie:v:1 baseline -provile:v0 main -b_strategy 0 -bf 1

Apple is suggesting „-profile:v high“ for the H264 codec to be used on HLS, but in most wikis it is recommended to leave the decission about the codec profile to the ffmpeg encoder libx264. The same applies for the B-frames and how to use them. Default is a maximum of 16 B-frames and the „-b_strategy“ of „1 / Fast“. „0“ would be very fast and is in the ffmpeg documentation used for the Dash commands. But also here it is better to leave the decission to the encoder. More information is on Google-Sites.

Putting everything together as a bash-script

HLS plus MPEG-DASH plus fallback file

Copy the script in a file.sh, put it into your project folder. Navigate in the bash or terminal into the folder and call the script:

bash file.sh

The script expects a „HLS“ and a „Dash“ folder inside your project folder. The path to your video is set with the „VIDEO_IN“ configuration constant. If you want a good quality change „PRESET_P“ to „veryslow“!

#!/bin/bash
#HLS, Dash and fallback code from zazu.berlin 2020, Version 20200424

VIDEO_IN=/Users/thomas/Desktop/apache-official/TestB/test20.mov
VIDEO_OUT=master
HLS_TIME=4
FPS=25
GOP_SIZE=100
PRESET_P=veryslow
V_SIZE_1=960x540
V_SIZE_2=416x234
V_SIZE_3=640x360
V_SIZE_4=768x432
V_SIZE_5=1280x720
V_SIZE_6=1920x1080

# HLS
ffmpeg -i $VIDEO_IN -y \
    -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 -r $FPS -c:v libx264 -pix_fmt yuv420p \
    -map v:0 -s:0 $V_SIZE_1 -b:v:0 2M -maxrate:0 2.14M -bufsize:0 3.5M \
    -map v:0 -s:1 $V_SIZE_2 -b:v:1 145k -maxrate:1 155k -bufsize:1 220k \
    -map v:0 -s:2 $V_SIZE_3 -b:v:2 365k -maxrate:2 390k -bufsize:2 640k \
    -map v:0 -s:3 $V_SIZE_4 -b:v:3 730k -maxrate:3 781k -bufsize:3 1278k \
    -map v:0 -s:4 $V_SIZE_4 -b:v:4 1.1M -maxrate:4 1.17M -bufsize:4 2M \
    -map v:0 -s:5 $V_SIZE_5 -b:v:5 3M -maxrate:5 3.21M -bufsize:5 5.5M \
    -map v:0 -s:6 $V_SIZE_5 -b:v:6 4.5M -maxrate:6 4.8M -bufsize:6 8M \
    -map v:0 -s:7 $V_SIZE_6 -b:v:7 6M -maxrate:7 6.42M -bufsize:7 11M \
    -map v:0 -s:8 $V_SIZE_6 -b:v:8 7.8M -maxrate:8 8.3M -bufsize:8 14M \
    -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -map a:0 -c:a aac -b:a 128k -ac 1 -ar 44100\
    -f hls -hls_time $HLS_TIME -hls_playlist_type vod -hls_flags independent_segments \
    -master_pl_name $VIDEO_OUT.m3u8 \
    -hls_segment_filename HLS/stream_%v/s%06d.ts \
    -strftime_mkdir 1 \
    -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3 v:4,a:4 v:5,a:5 v:6,a:6 v:7,a:7 v:8,a:8" HLS/stream_%v.m3u8

# DASH
ffmpeg -i $VIDEO_IN -y \
    -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 -r $FPS -c:v libx264 -pix_fmt yuv420p -c:a aac -b:a 128k -ac 1 -ar 44100 \
    -map v:0 -s:0 $V_SIZE_1 -b:v:0 2M -maxrate:0 2.14M -bufsize:0 3.5M \
    -map v:0 -s:1 $V_SIZE_2 -b:v:1 145k -maxrate:1 155k -bufsize:1 220k \
    -map v:0 -s:2 $V_SIZE_3 -b:v:2 365k -maxrate:2 390k -bufsize:2 640k \
    -map v:0 -s:3 $V_SIZE_4 -b:v:3 730k -maxrate:3 781k -bufsize:3 1278k \
    -map v:0 -s:4 $V_SIZE_4 -b:v:4 1.1M -maxrate:4 1.17M -bufsize:4 2M \
    -map v:0 -s:5 $V_SIZE_5 -b:v:5 3M -maxrate:5 3.21M -bufsize:5 5.5M \
    -map v:0 -s:6 $V_SIZE_5 -b:v:6 4.5M -maxrate:6 4.8M -bufsize:6 8M \
    -map v:0 -s:7 $V_SIZE_6 -b:v:7 6M -maxrate:7 6.42M -bufsize:7 11M \
    -map v:0 -s:8 $V_SIZE_6 -b:v:8 7.8M -maxrate:8 8.3M -bufsize:8 14M \
    -map 0:a \
    -init_seg_name init\$RepresentationID\$.\$ext\$ -media_seg_name chunk\$RepresentationID\$-\$Number%05d\$.\$ext\$ \
    -use_template 1 -use_timeline 1  \
    -seg_duration 4 -adaptation_sets "id=0,streams=v id=1,streams=a" \
    -f dash Dash/dash.mpd

# Fallback video file
ffmpeg -i $VIDEO_IN -y -c:v libx264 -pix_fmt yuv420p -r $FPS -s $V_SIZE_1 -b:v 1.8M -c:a aac -b:a 128k -ac 1 -ar 44100 fallback-video-$V_SIZE_1.mp4

`

The HTML code for the streaming site

The HTML5 video element will choose the file to play in the order they are noted. Since MPEG-DASH is controlled by JavaScript it will be tried first, then HLS and if both do not work there is a regular video file in a medium size and bitrate.

The type declaration is according to the norm as follows:

AddType application/vnd.apple.mpegurl m3u8
AddType video/mp4 mp4
AddType application/dash+xml mpd

Make shure, that the server knows the types and add them to the .htaccess.

code on the video html5 site

<video id="videoPlayer" autoplay="autoplay" controls="controls" width="960" height="540">
  <source src="myProjectFolder/HLS/master.m3u8" type="application/vnd.apple.mpegurl" />
  <source src="myProjectFolder/fallback-video-960x540.mp4" type="video/mp4" />
</video>

<!-- dash-player - no browser is natively playing DASH -->
<script src="/JavaScript/dash.all.min.js"></script>
<script>

    /* initialize the dash.js player in the #videoPlayer element */
    (function () {
        let url = "myProjectFolder/Dash/dash.mpd";
        player = dashjs.MediaPlayer().create();
        player.initialize(document.querySelector("#videoPlayer"), url, false);
    })();

</script>

Note: Some browsers took over the behavior of Apple devices to auto-play videos only if they are silenced.

The dash.js player has a lot of APIs and hooks. More information you will find on: cdn.dashjs.org

Here is an example with chapter navigation from a json-file

Open tasks

  • The HLS and the DASH video conversion blocks are just the same, reuse the first encoded files from HLS also in Dash and run it all in one ffmpeg command.
  • Add a HTML document with rich snippets and twitter cards and embed it as an iframe.

Final thoughts

Enrich your company and don’t make YouTube even richer. Stream from your own website and fight for data democracy!

Author: Thomas Hezel, zazu.berlin 2020

17 Kommentare

  1. That’s a good article.

    But there is one problem. For files with only video without audio, your script won’t work.

    Is there any way to make it optional with or without audio?

    Thanks for writing a good article.

  2. Toni Riera Toni Riera

    Hi There!
    First of all, thanks! It’s wonderful. I’ve gone a step further on it, but got some issues:

    I’m using an OSSRS software where an OBS Studio application can stream to it, and then transcoding it to simulate a live video.

    The live video isn’t an issue because i’ve got it working, but the biggest issue becomes when trying to do it in DASH and HLS. As you’ve got a .mov to process and later to convert to many stream_%v directories and stream_%v.m3u8 on the HLS directory.

    What about when having an rtmp input (https://github.com/ossrs/srs allows inputs on rtmp port, and with ffmpeg you can transcode it), the thing is that we’re doing it with a live video that never ends, and the second part of the main script (adding MPEG-DASH) can’t start due to first ffmpeg process running. I’ve tried stopping the first ffmpeg killing with Ctrl+C that allows second ffmpeg process to start, but doesn’t seems to work properly.

    Got any idea?

    • Hello Toni,
      my experience is that HLS is basically not necessary. The JavaScript dash.js is working in the iOS and OS Mac world fine.
      There is also the possibility to do HLS with DASH -> „hls_playlist hls_playlist Generate HLS playlist files as well. The master playlist is generated with the filename hls_master_name. One media playlist file is generated for each stream with filenames media_0.m3u8, media_1.m3u8, etc.“ Check the ffmpeg documentation: https://ffmpeg.org/ffmpeg-formats.html#dash-1
      Thomas

  3. Mario Enriquez Mario Enriquez

    Thanks for this post

  4. Tobias Tobias

    Hey Thomas,
    Thank you for this helpful instruction!
    How can I set (in the bash-script) a path to a subfolder, e.g. „output_folder“, where the m3u8-files and the .ts-files are placed and all references in the m3u8-files are correct?
    Thanks in advance and best regards,
    Tobias

  5. Meg Meg

    HI Thomas,
    Thank you very much for this article. It’s very useful. I would very much like to host my own videos and I think I know just enough about coding to be able to hack things together from other people which is why your article here was so useful.
    I have been experimenting and have got hls and dash working with videojs as the player (via dashjs.org rather than hosted locally) and all teh video files stored on amazon s3.
    I have heard that fragmented mp4s can work as well. Do you have experience with converting to fragmented mp4s?
    Many Thanks

    • Hello Meg,
      fragmented mp4 is exactly what you are doing here: Dividing a video stream into fragments that you call on demand, depending on the quality of your internet connection. And of course, hosting on Amazon is not what one should do. Amazon is a threat to democracy and our whole democratic system, so please just stay away from it. (same for Azure) Host on your own server or use a local provider.

  6. Konstantin Konstantin

    Hi!
    Thanks for your useful post!
    Maybe you could help to solve a problem with videos that have no audio track at all.
    e.g. you can record a screen cast on a mac using Quick Time with disabled audio. Then use this script to convert it. It will lead to the following error
    Stream map ‚a:0‘ matches no streams.
    To ignore this, add a trailing ‚?‘ to the map.

    I tried to add a trailing question mark but then it fails on -var_stream_map because it doesn’t allow optional values

    Could you help with any ideas on a workaround, please?
    Best regards

    • Short but easy,: just use ffmpeg to add to audio streams. Or load it in the free editor DaVinci Resolve and add Audio.
      To change my setup I would need to do a couple of testes.

      Thomas

  7. Martin Lillo Martin Lillo

    Hallo Thomas! Ich habe einen Canvas LMS Server installiert, und möchte gerne wissen, wie ich HLS oder MPEG-DASH in Canvas hinzufügen kann. Muss ich mit diesen Koden arbeiten, oder gibt es einen direkteren Weg dafür?
    Da bin ich ratlos, so wenn du mir damit helfen könntest, dann wäre ich sehr dankbar.
    LG!!!

    • Hallo Martin,
      da ich mich auch mit Moodle beschäftige, habe ich dafür einen Plan:
      1. installiere FFMPG auf dem Server
      2. schreibe ein PHP-Programm, das jeden hochgeladenen Film automatisch in MPEG-DASH konvertiert (HLS braucht man mit dash.js nicht mehr)
      3. ergänze das PHP-Programm mit einem Formular in dem du die Metadaten des Filmes eingibts und das dir ein HTML-Dokument nur für diesen einen Film erstellt und von dort das Vidoe streamt
      4. nun hast Du ein HTML-mein-Video.html Dokument in dem das Video gestreamt wird und einen Ordner, auf den das HTML-Dokument zugreift in dem die MPEG-DASH-Dateien sind.
      5. in deinem LMS brauchst du dann nur noch das HTML5-Dokument.html in einen i-Frame laden und fertig, wenn alle html-Dokumente in einem Ordner sind können die User dann von einer Liste einfach auswählen.

      Es braucht also einiges an Programmierung, im Prinzip muss man eine eigene Extension oder ein Plugin erstellen.
      Wenn man weiter gehen möchte kann man das Ganze dann noch auf einen eigenen Video-Server auslagern, so dass das Lernen nicht durch rechenintensive Konvertierungen verlangsamt wird.

      TH

  8. Jarad Jarad

    On a dash output, couldn’t you just use „-hls_playlist 1“ to generate an HLS playlist? And isn’t this advantageous because both would use the same source? Also, I’m confused about your use of -maxrate and -bufsize because in my quick research, those apply primarily in situations like live streaming whereas here the intent seems to be VOD. Either way, this article was a gold mine for me. I absolutely love the tables you put together explaining the different argument parameters. Thank you!

    • Hello Jarad,
      Thank you for your comment and suggestions. Yes using „-hls_playlist 1“ would be possible, but then without individual control. Present I’m not using HLS any more because MPEG-DASH is just fine in Apple-Safari too. With „-maxrate“ and „-bufsize“ you could be right. Here I just used examples from the manual. It probably needs more digging in the manual.
      Thomas

  9. Moustafa Moustafa

    Hello Thomas! Thank you for the article! It seems to work for the h264 video codec. However, when using h265, the video played is just a black screen in the browser. I changed the „-c:v libx264“ to „-c:v libx265“. Are there any more changes that need to be done when using the h265 codec?

Schreibe einen Kommentar zu Jay Baek Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert