Encoding video for distribution via MPEG-DASH
For a small project I was working on, I wanted to distribute video via MPEG-DASH instead of my usual go-to, HLS. Like HLS, MPEG-DASH supports delivering video via adaptive bit rates.
Getting it up and running was a bit finnicky though, so for my own reference (more than anything), I’m brain-dumping the process here.
To encode the data correctly, I used a couple of guides: 1, 2, 3. You’ll see I’ve borrowed equally from all of these. For displaying the video, I used dash.js. The dev guide was enough to get something up and running.
Step one: building dependencies
git clone https://chromium.googlesource.com/webm/libwebm
cd libwebm && cmake .
make
Optionally, cd /usr/local/bin && ln -s /path/to/mkvmuxer_sample
Step two: encoding the video
For this part of this guide, we follow the instructions at crookm.com. I am using an input filename of original.mkv
.
mkdir dash && \
ffmpeg -hide_banner -i original.mkv -c:v libvpx-vp9 -row-mt 1 \
-keyint_min 150 -g 150 -tile-columns 4 -frame-parallel 1 \
-movflags faststart -f webm -dash 1 -speed 3 -threads 4 \
-an -vf scale=426:240 -b:v 400k -r 30 -dash 1 dash/426x240-30-400k.webm && \
ffmpeg -hide_banner -i original.mkv -c:v libvpx-vp9 -row-mt 1 \
-keyint_min 150 -g 150 -tile-columns 4 -frame-parallel 1 \
-movflags faststart -f webm -dash 1 -speed 3 -threads 4 \
-an -vf scale=426:240 -b:v 600k -r 30 -dash 1 dash/426x240-30-600k.webm && \
ffmpeg -hide_banner -i original.mkv -c:v libvpx-vp9 -row-mt 1 \
-keyint_min 150 -g 150 -tile-columns 4 -frame-parallel 1 \
-movflags faststart -f webm -dash 1 -speed 3 -threads 4 \
-an -vf scale=640:360 -b:v 700k -r 30 -dash 1 dash/640x360-30-700k.webm && \
ffmpeg -hide_banner -i original.mkv -c:v libvpx-vp9 -row-mt 1 \
-keyint_min 150 -g 150 -tile-columns 4 -frame-parallel 1 \
-movflags faststart -f webm -dash 1 -speed 3 -threads 4 \
-an -vf scale=640:360 -b:v 900k -r 30 -dash 1 dash/640x360-30-900k.webm && \
ffmpeg -hide_banner -i original.mkv -c:a libvorbis -b:a 192k -vn -f webm -dash 1 dash/audio.webm
Step three: create cue points
# video
mkvmuxer_sample -i dash/426x240-30-400k.webm -o dash/426x240-30-400k_cued.webm
mkvmuxer_sample -i dash/426x240-30-600k.webm -o dash/426x240-30-600k_cued.webm
mkvmuxer_sample -i dash/640x360-30-700k.webm -o dash/640x360-30-700k_cued.webm
mkvmuxer_sample -i dash/640x360-30-900k.webm -o dash/640x360-30-900k_cued.webm
# audio
ffmpeg -i audio.webm -vn -acodec libvorbis -ab 192k -dash 1 audio_cued.webm
Finally, create the .mpd
ffmpeg \
-f webm_dash_manifest -i dash/426x240-30-400k_cued.webm \
-f webm_dash_manifest -i dash/426x240-30-600k_cued.webm \
-f webm_dash_manifest -i dash/640x360-30-700k_cued.webm \
-f webm_dash_manifest -i dash/640x360-30-900k_cued.webm \
-f webm_dash_manifest -i audio_cued.webm \
-c copy -map 0 -map 1 -map 2 -map 3 -map 4 \
-f webm_dash_manifest -adaptation_sets "id=0,streams=0,1,2,3 id=1,streams=4" manifest.mpd
Deploy
Stick it onto a host that supports byte range downloads; I use S3.
Player
You can test that you’ve correctly encoded the streams and generated the .mpd
file correctly by testing on the Dash.js reference
player.
Once it’s all running nicely (remember to set CORS correctly if serving from a different domain), you can grab the dash.min.js from the Dash Wiki and do something like the following:
<div>
<video id="videoPlayer" controls="" style="max-width: 100%"></video>
</div>
<script src="/path/to/dash.all.min.js"></script>
<script>
(function(){
var url = "/path/to/dash/manifest.mpd";
var player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector("#videoPlayer"), url, true);
})();
</script>
That’s all there is to it!