MPEG DASH with only FFMPEG

Sometimes you want to host a video yourself without using youtube or other service. Plus you decide you want adaptive quality to give a good experience.
It is actually pretty simple if you don’t have a ton of content and scale; there is the MPEG DASH standard that you can host on any web server and is supported by most javascript video players.
I have had a good experience with clappr player.

It is possible to encode video for MPEG DASH streaming using only FFMPEG.

The parameters regarding keyframes and encoding profiles I mostly took from this article by facebook so should be sensible.

In the example I have chosen the following video sizes and bitrates:
W: 240 Bitrate: 350
W: 480 Bitrate: 700
W: 720 Bitrate: 2500
and a fallback version for browsers that don’t support DASH with W: 480 Bitrate: 400

Here I use Node JS module fluent-ffmpeg to build the extremely long command line and execute ffmpeg. To get the multiple video qualities in one step you use map to create multiple virtual video streams which you can then target in ffmpeg commands. You will end up with a directory with a .MPD file and video segment files as well as a .MP4 fallback video.

JavaScript
128 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const os = require('os');
var fs = require('fs');
if (os.platform() == 'win32') {
let binarypath = path.resolve('./ffmpeg/bin/');
let FfmpegPath = path.join(binarypath,'ffmpeg.exe');
try {
var FfmpegPathInfo = fs.statSync(FfmpegPath);
} catch (err) {
throw err;
}
ffmpeg.setFfmpegPath(FfmpegPath);
ffmpeg.setFfprobePath(path.join(binarypath,'ffprobe.exe'));
console.log('binarypath',path.join(binarypath,'ffmpeg.exe'));
}
function consoleEncode(fn) {
// height, bitrate
const sizes = [
[240, 350],
[480, 700],
[720, 2500],
];
const fallback = [480,400];
let name = path.basename(fn, path.extname(fn));
const targetdir = path.join(__dirname, name);
const sourcefn = path.resolve(fn);
console.log('source', sourcefn);
console.log('info', sizes);
console.log('info', targetdir);
try {
var targetdirInfo = fs.statSync(targetdir);
} catch (err) {
if (err.code === 'ENOENT') {
fs.mkdirSync(targetdir);
} else {
throw err;
}
}
var proc = ffmpeg({
source: sourcefn,
cwd: targetdir
});
var targetfn = path.join(targetdir, `${name}.mpd`);
proc
.output(targetfn)
.format('dash')
.videoCodec('libx264')
.audioCodec('aac')
.audioChannels(2)
.audioFrequency(44100)
.outputOptions([
'-preset veryfast',
'-keyint_min 60',
'-g 60',
'-sc_threshold 0',
'-profile:v main',
'-use_template 1',
'-use_timeline 1',
'-b_strategy 0',
'-bf 1',
'-map 0:a',
'-b:a 96k'
]);
for (var size of sizes) {
let index = sizes.indexOf(size);
proc
.outputOptions([
`-filter_complex [0]format=pix_fmts=yuv420p[temp${index}];[temp${index}]scale=-2:${size[0]}[A${index}]`,
`-map [A${index}]:v`,
`-b:v:${index} ${size[1]}k`,
]);
}
//Fallback version
proc
.output(path.join(targetdir, `${name}.mp4`))
.format('mp4')
.videoCodec('libx264')
.videoBitrate(fallback[1])
.size(`?x${fallback[0]}`)
.audioCodec('aac')
.audioChannels(2)
.audioFrequency(44100)
.audioBitrate(128)
.outputOptions([
'-preset veryfast',
'-movflags +faststart',
'-keyint_min 60',
'-refs 5',
'-g 60',
'-pix_fmt yuv420p',
'-sc_threshold 0',
'-profile:v main',
]);
proc.on('start', function(commandLine) {
console.log('progress', 'Spawned Ffmpeg with command: ' + commandLine);
});
proc.on('progress', function(info) {
console.log('progress', info);
})
.on('end', function() {
console.log('complete');
})
.on('error', function(err) {
console.log('error', err);
});
return proc.run();
}
consoleEncode('trailer.mov');
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
transcode.js

The command you end up executing looks like:

ffmpeg -i trailer.mov -y -acodec aac -ac 2 -ar 44100 -vcodec libx264 -f dash -preset veryfast -keyint_min 60 -g 60 -sc_threshold 0 -profile:v main -use_template 1 -use_timeline 1 -b_strategy 0 -bf 1 -map 0:a -b:a 96k -filter_complex [0]format=pix_fmts=yuv420p[temp0];[temp0]scale=-2:240[A0] -map [A0]:v -b:v:0 350k -filter_complex [0]format=pix_fmts=yuv420p[temp1];[temp1]scale=-2:480[A1] -map [A1]:v -b:v:1 700k -filter_complex [0]format=pix_fmts=yuv420p[temp2];[temp2]scale=-2:720[A2] -map [A2]:v -b:v:2 2500k trailer\trailer.mpd -acodec aac -ac 2 -ar 44100 -b:a 128k -vcodec libx264 -b:v 400k -filter:v scale=w=trunc(oh*a/2)*2:h=480 -f mp4 -preset veryfast -movflags +faststart -keyint_min 60 -refs 5 -g 60 -pix_fmt yuv420p -sc_threshold 0 -profile:v main trailer\trailer.mp4

Download the code: FFMPEGDASH.zip

And finally an example video encoded using the above code:
[open in new tab]

Comments are closed.