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!