Video Streaming

12 Nov 2020

I decided that I wanted to try streaming some games...

However I recently have been on a 'self-hosting' kick (this site for example). I thought to myself "Why not host my own twitch?"... and so began my project to host my own live streams.

Also, as per the usual for me, it all had to run on linux. :)

tl;dr

  1. Desktop Recording
    • OBS Studio running on the machine I am recording.
  2. RTMP Server
    • nginx with extension running on my home server.
  3. RTMP -> HLS transformer
    • ffmpeg running alongside nginx on my home server.
  4. HLS Compatible Web Client
    • hls.js on a basically blank website, hosted by my nginx service.

The Details

OBS

First off is OBS running on my gaming desktop and configuring it to stream. In OBS settings panel "Stream" I set the service to "custom" and "Server" to my local home server's rtmp address rtmp://192.168.X.X/live

Next is setting the correct encoder. Since my machine is getting older (read: CPU is slow) I am using the only hardware encoder I have available configured via the "Output" tab in settings. I am using "FFMPEG VAAPI" which uses the h.264 codec. At first I tried just setting a relatively low bitrate however I found that the quality during rapid movement really suffered. After much experimentation I found the 'CQP' rate control option provided a nice balance of low bitrate when movement was slow and high quality video when movement was faster. I used QP of 15 for a while and it seemed pretty good. A value of 12 seems nearly flawless but does bump up bandwidth quite a bit.

RTMP Server

nginx RTMP Module

I simply downloaded this repo, compiled the extension, and started up nginx with the module enabled.

With this running you can connect straight to the rtmp stream at https://echols.io:1935/live/`streamid`

HLS Transformer

In short - I am using ffmpeg to read in the rtmp stream and output HLS video files and manifest that can be statically hosted.

ffmpeg -r 60 -i "rtmp://localhost:1935/live/test" -c:v copy -c:a copy -segment_list_flags live -g 4 -keyint_min 4 -f hls -hls_time 4 -hls_wrap 5 -hls_playlist_type event stream.m3u8

Using this configuration I am creating five 'chunks' at a time, each 4 seconds long. These are listed in the manifest "stream.m3u8" which is loaded by the client video player. However this was expensive at first because I was also re-encoding the video stream on the fly. I then changed the stream to be a "copy" stream which just takes the video frames and just shoves them through without alteration. (-c:v copy -c:a copy)

However, this caused a new issue, keyframes were not always aligned to the short clips which caused a short video artifacting if someone connected in between keyframes. Additionally this caused ffmpeg to spit out errors constantly. To address that I forced new keyframes at the start of each segment with -keyint_min 4

HLS Web Client

I host the web client with the ffmpeg output hls files using the nginx server (static file host).

The client itself is a simple webpage that loads hls.js and a html5 video element. This player is then pointed at the HLS files being generated by ffmpeg and hosted by nginx.

There is a lot of work I could do to improve the player experience. For example adding chat so we can have the authentic twitch experience ;)

Next?

Currently a lot of this is a manual process:

  • ffmpeg will crash if the rtmp stream stops and so I am manually starting it in the terminal.
  • The stream key is hardcoded currently to 'test' on both my OBS client and in the web client stream.
The next steps to automate this would be an interface that would startup the ffmpeg process and create a list of 'streams' that could be discovered. Perhaps even create a system to auto-detect when the rtmp stream is started and start ffmpeg automatically.