Skip to content

Instantly share code, notes, and snippets.

@xioustic
Last active June 29, 2018 21:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xioustic/d9886b308e183916bb12637685efcc18 to your computer and use it in GitHub Desktop.
Save xioustic/d9886b308e183916bb12637685efcc18 to your computer and use it in GitHub Desktop.
Benchmarking FFMpeg Piped to VLC for Periscope Streams (HLS) on Windows

What is This

I wanted to pipe ffmpeg output to VLC on Windows which took a little while to figure out. ffmpeg cannot detect what format to use when piping, so I went through some obvious options but it was unclear what was best. So I wrote an extremely naive benchmarking script that would run a common stream for two minutes and then take the CPU/Memory usage data from tasklist /v before killing the two processes.

System Info

system

Proc: Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz  3.39GHz
RAM: 16.0 GB
OS: Windows 10 Pro

ffmpeg version

ffmpeg version N-83243-g2080bc3 Copyright (c) 2000-2017 the FFmpeg developers
built with gcc 5.4.0 (GCC)
configuration: --enable-gpl --enable-version3 --enable-cuda --enable-cuvid --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-nvenc --enable-avisynth --enable-bzlib --enable-fontconfig --enable-frei0r --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libfreetype --enable-libgme --enable-libgsm --enable-libilbc --enable-libmodplug --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-lzma --enable-decklink --enable-zlib
libavutil      55. 45.100 / 55. 45.100
libavcodec     57. 75.100 / 57. 75.100
libavformat    57. 63.100 / 57. 63.100
libavdevice    57.  2.100 / 57.  2.100
libavfilter     6. 70.100 /  6. 70.100
libswscale      4.  3.101 /  4.  3.101
libswresample   2.  4.100 /  2.  4.100
libpostproc    54.  2.100 / 54.  2.100

vlc version 2.2.4 Weatherwax

Results

Check results.md for the final table. I believe there is not enough data for any real conclusion since there was not much control (the stream was relatively consistent between runs but not exact by any means) and not much accuracy (cpu usage measured in seconds rather than ms or us). Regardless, it would appear mpegts would be the best overall format for this use case, with mpegtsraw being preferred if CPU was more valued than memory.

This makes sense; I believe HLS streams are m3u8 playlists that consist of ".ts" files which coincide with the mpegts format. Hopefully ffmpeg is just passing this through. The difference between mpegts and mpegtsraw makes some sense: mpegtsraw would have ffmpeg shouldering the cpu burden rather than VLC and it would make sense that ffmpeg would be the better workhorse.

Notes

The test.js file is extremely rough. It has some useful snippets for wrapping the Windows command line tools, such as getTaskList and killPid. However, nothing handles errors and it'd be much better if everything returned Promises. Finally, the test.js file uses console.log for its output rather than something more sane (like writing to a log file). So if you re-use test.js for something make sure you capture the output!

format ffmpeg memory (KB) ffmpeg CPU (secs) vlc memory (KB) vlc CPU (secs) total memory (KB) total CPU (secs) score
mpegts 42416 23 106272 15 148688 38 5650144
mpegtsraw 65908 22 110960 13 176868 35 6190380
flv 67020 22 111244 13 178264 35 6239240
mp4 65388 24 111424 12 176812 36 6365232
asf 66348 21 110764 15 177112 36 6376032
data 66024 22 111212 14 177236 36 6380496
segment 66960 21 111140 15 178100 36 6411600
nut 65652 22 111112 15 176764 37 6540268
h264 65760 22 111532 15 177292 37 6559804
yuv4mpegpipe 66892 22 111068 15 177960 37 6584520
webm 66808 22 111724 15 178532 37 6605684
matroska 65696 22 111208 16 176904 38 6722352
hls 65024 25 111172 14 176196 39 6871644
avi 66044 24 110984 15 177028 39 6904092
hevc 65268 24 111032 16 176300 40 7052000
/*
Benchmarking FFmpeg Piping to VLC using Container Formats
*/
const spawn = require('child_process').spawn
const exec = require('child_process').exec
const { appendFileSync, writeFileSync } = require('fs')
const BENCHMARK_TIME = 20000
const sourceFile = 'https://prod-replay-us-east-1-public.periscope.tv/G91iY-tDWZKSmu_JmtPkAgwRjQSXSx4yGrhaF4UjUAXpBgm1umZ3dJ1QmePpWLZnTNBwsax0c7DyCaVwHDuusw/playlist_1485714943512596125.m3u8'
const sourceFileArgs = ['-icy', '0']
const playerPaths = {
'vlc': 'C:\\Program Files\\VideoLAN\\VLC\\vlc.exe',
'mpc': 'C:\\Program Files\\MPC-HC\\mpc-hc64.exe',
'potplayer': 'C:\\Program Files\\DAUM\\PotPlayer\\PotPlayerMini64.exe',
'ffmpeg': 'ffmpeg'
}
const formats = [
'yuv4mpegpipe', 'nut', 'avi', 'webm', 'matroska',
'segment', 'mpegtsraw', 'mpegts', 'mp4', 'hls',
'hevc', 'h264', 'flv', 'data', 'asf'
]
function benchmarkFormat (format, callback) {
console.log('benchmarking', format)
const playingProcess = spawn(
`"${playerPaths.ffmpeg}"`,
[
...sourceFileArgs,
'-i', sourceFile,
'-f', format,
'-codec', 'copy',
'-', '|',
`"${playerPaths.vlc}"`, '-'
],
{ shell: true }
)
let exitCode = null
let exitSignal = null
let errors = []
let stdout = ''
let stderr = ''
playingProcess.on('error', function (e) {
errors.push(e)
})
playingProcess.on('exit', function (code, signal) {
exitCode = code
exitSignal = signal
})
playingProcess.stdout.on('data', function (data) {
stdout += data
})
playingProcess.stderr.on('data', function (data) {
stderr += data
})
setTimeout(() => {
console.log('times up!')
getTaskList((err, tasklist) => {
console.log('got task list')
var results = tasklist.filter(task => task['Image Name'] === 'ffmpeg.exe' || task['Image Name'] === 'vlc.exe')
results.exitCode = exitCode
results.exitSignal = exitSignal
results.errors = errors
results.stdout = stdout
results.stderr = stderr
results.forEach((task) => {
killPid(task.PID)
})
callback(null, results)
})
}, BENCHMARK_TIME)
}
function getTaskList (callback) {
exec('tasklist /fo list /v', (err, stdout, stderr) => {
let lines = stdout.split('\n')
let curObj = {}
let tasks = []
lines.forEach(line => {
// skip blank lines
if (line.replace('\r', '') === '') {
// but flush if we have something
if (Object.keys(curObj).length) {
tasks.push(curObj)
curObj = {}
}
} else {
var splitted = line.split(':')
var attribute = splitted[0]
var value = splitted.slice(1).join(':').trim()
curObj[attribute] = value
}
})
if (Object.keys(curObj).length) tasks.push(curObj)
callback(null, tasks)
})
}
function getBenchmarkValues (pids, callback) {
pids = pids.map(pid => '' + pid)
getTaskList((err, res) => {
res = res.filter((task) => pids.indexOf('' + task.PID) !== -1)
callback(null, res)
})
}
function killPid (pid, callback = () => {}) {
exec('taskkill /F /PID ' + pid, (error, stdout, stderr) => {
if (error) return callback(error)
return callback(null, stdout)
})
}
if (require.main === module) {
let curBenchmark = 0
let finalResults = {}
let doBenchmark = () => {
if (curBenchmark >= formats.length) {
writeFileSync('results.json', JSON.stringify(finalResults, null, 2))
return console.log(JSON.stringify(finalResults, null, 2))
}
const thisFormat = formats[curBenchmark]
curBenchmark++
benchmarkFormat(thisFormat, (err, results) => {
console.log(results)
appendFileSync('results_append.json', JSON.stringify(results, null, 2))
finalResults[thisFormat] = results
setTimeout(doBenchmark, 100)
})
}
doBenchmark()
}
module.exports = {
getTaskList,
killPid,
getBenchmarkValues,
benchmarkFormat
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment