Monochromatic

z3bra, the stripes apart

Desktop streaming

— 30 August, 2016

For teaching purposes (and cool internet points!) I recently needed to share my screen and microphone online. Being the unix enthusiast that I am, I looked into how I could do it "simple" command-line tools.

And here comes ffmpeg. ffmpeg is the swiss army knife for everything related to audio and video decoding/encoding. I've been using it for multiple tasks already, from converting .ogg to .mp3, to recording GIFs of my desktop.

Server part

I started looking into how I could "stream" my desktop online, and quickly found about the ffserver utility (which is part of the ffmpeg package).

ffserver provides a service to do the following:

A "Feed", from ffserver point, is a URL that a user will pass to ffmpeg as the output media, to start "uploading" or "streaming" a video to. ffserver will then start bufferizing this input locally, and expose this raw buffer via a "Stream". A stream will read from this buffer, and encode it in the specified format, with a bunch of options.

One can specify multiple output streams for a single feed, eg, to use different encoding formats.

Enough shittalks, here is what my /etc/ffserver.conf looked like:

# Port 80 was taken by the webserver
HTTPPort 8090
HTTPBindAddress 0.0.0.0
MaxHTTPConnections 64
MaxClients 28
MaxBandwidth 10000

CustomLog /var/log/ffserver.log

# Where to send data.
# URL will be: http://10.0.0.2:8090/0.ffm
<Feed 0.ffm>
    # buffer file and max size
    File /tmp/ffserver/0.ffm
    FileMaxSize 200K

    # Only allow this IP to send streaming data ACL
    allow 10.0.0.3
</Feed>

# How to expose the stream
# URL will be: http://10.0.0.2:8090/0.flv
<Stream 0.flv>
    # The feed to encode data from
    Feed 0.ffm

    # Video encoding options
    Format flv
    VideoCodec libx264
    VideoFrameRate 5
    VideoSize 1440x900
    VideoBitRate 512
    AVOptionVideo tune zerolatency
    AVOptionVideo flags +global_header

    # Audio encoding options
    AudioCodec aac
    AVOptionAudio flags +global_header
</Stream>

I limited my research for the perfect stream to either x264 or vp8 video encoding. At first, vp8 seemed appealing, being a royalty-free format. The WEBM container also seems to be pretty good for online videos. But x264 turned out to be faster, and of higher quality (especially thanks to the "zerolatency" setting). I had to switch to x264 also because I couldn't get the libvorbis codec for audio to synchronize well with the vp8 video stream.

The above configuration is the best quality/rapidity ratio I could get.

When the config is ready, you just need to fire up the server with

/usr/bin/ffserver -f /etc/ffserver.conf

Watcher part

In order to watch the stream, one has to use the URL defined by the tag. I personally use mplayer to watch it, but one can use the ffplay command provided by ffmpeg:

ffplay http://10.0.0.2:8090/0.flv

And that's ALL. You can hardly do simpler to watch a stream, right?

Feeder part

In order to feed yor stream to the ffserver, you can use ffmpeg directly. The format of the command is pretty simple. We have 2 inputs: the video and the audio. We also have 1 output: the FFM feed. The simplest command we can use is thus (recording our desktop, and microphone):

ffmpeg -f x11grab -i :0.0 -f alsa -i default http://10.0.0.2:8090/0.ffm

Let's break it down a bit:

This should start recording, and sending data to the stream. If you look at it, the output will not look really great, and we need to pass a few more flags to get a nice looking output that will record the full screen in a decent way:

ffmpeg -f x11grab -r 5 -s 1440x900 -thread_queue_size 1024 -i :0.0 \
       -f alsa -ac 1 -thread_queue_size 1024 -i default \
       -af 'highpass=f=200, lowpass=f=2000' \
       -fflags nobuffer \
       http://10.0.0.2:8090/0.ffm

Ok, that was odd. no worries, I'm no wizard and didn't came up with all these flags out of nowhere! Let's review them:

-f x11grab -r 5 -s 1440x900 -i :0.0

Record our X11 desktop with a framerate (-r) of 5 FPS, and record the screen at size (-s) 1440x900 (my screen size).

-f alsa -ac 1 -i default

Record from the default ALSA capture device, using MONO input (-ac).

-thread_queue_size 1024

For both input, this is to increase the max number of queued packets ffmpeg can handle. If the thread queue is full, ffmpeg will start dropping packets, which can lower the stream quality.

-af 'highpass=f=200, lowpass=f=2000'

Add an audio filter. My microphone is utter shit, and records a lot of white noise. This filters out frequencies below 200Hz and above 2000Hz (that's the typical voice range).

-fflags nobuffer

Avoid buffering frames when possible, so the stream is available as it is recorded.

And that's pretty much it! Note that ffmpeg can have multiple outputs, so you can record to the feed AND to a local file at the same time.

For instance, this is my ffstream script:

#!/bin/sh

STREAM=${1:-http://10.0.0.2:8090/0.ffm}
ffmpeg -f x11grab -r 5 -s 1440x900 -thread_queue_size 1024 -i :0.0 \
       -f alsa -ac 1 -thread_queue_size 1024 -i default \
       -af 'highpass=f=200, lowpass=f=2000' \
       -fflags nobuffer ${STREAM} \
       -af 'highpass=f=200, lowpass=f=2000' \
       -c:v libvpx -b:v 5M -c:a libvorbis webcast-$(date +%Y%m%d%H%M%S).webm

That's all folks!