Category: nginx

Live Streaming with Hardware Acceleration using a Raspberry Pi and RTMP/HLS

If you’ve been following my blog post series on the development of my ever so useful cat cam, powered by a Raspberry Pi, you’ll know I’ve made several attempts at a more stable and scalable streaming solution for my Cat Cam. As it stands today, I’ve been using Motion. While it’s a decent tool, Bandwidth has been my primary concern and I’d like to be able to stream real-time without sucking up what measly bits my ISP gives me if more than a few folks decide to show interest.

So far we’ve tried ffmpeg => ffserver and that turned out exactly how you probably thought it would. Next, I tried swapping ffserver with an Nginx-powered RTMP server. While not an entirely fruitless endeavor, there were some blockages that I just couldn’t get past.

I received a suggestion from a colleague to fire up the Raspberry Pi’s hardware encoder/decoder. Up until yesterday, I didn’t know this was a thing. Shame on me for not looking into it. So that’s what we’re going to cover in tonight’s post: taking some of what we learned from our first RTMP attempt and make the hardware do all the work. With any luck, we should see some real perf gains, possibly enough for live streams to start instantly (which would make web players happy).

Since I felt like including it here would deviate from the purpose of this post too much, I wrote up how to Add RTMP Support to Nginx if you installed it via apt-get like me. If you’re in that boat, take a moment to read over that post then come back to this one.

Setting up ffmpeg to use hardware H.264 encoding used to be a fat challenge, but they’ve since added support to the official codebase. If you followed my original ffmpeg post, you’ll have a recent enough version that includes this code, but we’ll still need to compile it.

What we’re looking for this time is the OpenMAX IL (Integration Layer) acceleration module.

pi@pi:/usr/src/ffmpeg $ sudo ./configure --enable-omx --enable-omx-rpisudo makesudo make install

That’ll take some time, as I’ve said before. You’ll have enough free time on your hands to get make something to eat. Come back in an hour or so and it should be done.

NOTE: If you run into ERROR: OpenMAX IL headers not found, then you’ll need to run apt-get install libomxil-bellagio-dev. Thanks, lordofduct in the comments for that one!

From this point forward, we’ll be starting ffmpeg similarly to how we did it before but with a slightly different codec.

ffmpeg -i /dev/video0 -framerate 30 -video_size 720x404 -vcodec h264_omx -maxrate 768k -bufsize 8080k -vf "format=yuv420p" -g 60 -f flv rtmp://example.com:8081/hls/live

I confirmed VLC is able to play the stream, which is excellent, and there are no lag or jitter issues. It’s about 10-15 seconds behind live, which is totally fine.

I was able to set up an HTML5 player using tools from Bitmovin. I’m not entirely happy with this setup, though, as the player isn’t free and only HLS is supported, right now1. In my next post I’ll cover a new idea that came to mind when looking into the coolness of Ruby on Rails 5: WebSockets.

Update July 11, 2017: @HPGMiskin pointed out libomxil-bellagio-bin is not a thing. I’ve pulled that from the optional step for missing OpenMAX headers.


Add RTMP Support to Nginx Installed From Apt

In the process of trying to figure out the best streaming solution for my cat cam, I had to deviate a bit. I combined my RTMP server for my cat cam and Web server for johnathanlyman.com into one and the latter didn’t have the RTMP module installed. This module is required for my attempts to push H.264 video and have Nginx relay it to whomever is watching, cutting down on the bandwidth of my one-to-one reverse proxy setup I have, now.

It’s a pretty straightforward process to re-compile Nginx, but there are a couple extra steps involved if you installed Nginx from a package repo. I’ll be sure to cover these. What we’re doing here is re-compiling a deb package. By going that route, we’ll adhere to the same methods in which Nginx was installed in the first place so we don’t have two competing installs.

Like my other RTMP/Nginx-inspired post, I’m using Ubuntu 16.04 (xenial), so everything will revolve around that.

Before we begin, we’ll want to make sure we’re updated and ready to go:

apt-get updateapt-get upgrade

Next, install software-properties-common if needed then add the nginx/stable ppa.

apt install software-properties-commonadd-apt-repository ppa:nginx/stable

Now we’ll be able to grab the source files from the repo.

cd /usr/srcapt-get build-dep nginxapt-get source nginx

Whatever directory you run that in is where the source and dependency files will appear. I chose /usr/src as that’s where we’ve been working in these previous posts. Mine looks something like this:

user@server:/usr/src# ls -altotal 1888drwxr-xr-x  4 root root    4096 Jul  8 17:20 .drwxr-xr-x 10 root root    4096 Apr 21 09:56 ..drwxr-xr-x 10 root root    4096 Jul  8 17:20 nginx-1.10.1-rw-r--r--  1 root root 1000448 May 31 19:05 nginx_1.10.1-0+xenial0.debian.tar.gz-rw-r--r--  1 root root    2765 May 31 19:05 nginx_1.10.1-0+xenial0.dsc-rw-r--r--  1 root root  909077 May 31 19:05 nginx_1.10.1.orig.tar.gz

Let’s move into the source folder and download the RTMP module:

cd nginx-1.10.1/debian/modules/git clone https://github.com/arut/nginx-rtmp-module

Back up one directory and open rules in a text editor. I’m using nano. Add the module to the end of the --add-module list under the common_configure_flags or full_configure_flags[1] like so:

[common|full]_configure_flags := \                        $(common_configure_flags) \						[...]                        --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module \                        # NEW MODULE BELOW @                        --add-module=$(MODULESDIR)/nginx-rtmp-module

Now that the module is in, let’s re-compile![2]

cd cd /usr/src/nginx-1.10.1dpkg-buildpackage -uc -b

Depending on how powerful your server is will determine largely how long this takes. I say go get a beverage and come back in a few minutes.

Once it’s done, you’ll get your set of .deb packages:

cd /usr/srcuser@server:/usr/src# ls -altotal 16088drwxr-xr-x  4 root root    4096 Jul  8 17:52 .drwxr-xr-x 10 root root    4096 Apr 21 09:56 ..drwxr-xr-x 10 root root    4096 Jul  8 17:20 nginx-1.10.1-rw-r--r--  1 root root   23788 Jul  8 17:52 nginx_1.10.1-0+xenial0_all.deb-rw-r--r--  1 root root    3756 Jul  8 17:52 nginx_1.10.1-0+xenial0_amd64.changes-rw-r--r--  1 root root 1000448 May 31 19:05 nginx_1.10.1-0+xenial0.debian.tar.gz-rw-r--r--  1 root root    2765 May 31 19:05 nginx_1.10.1-0+xenial0.dsc-rw-r--r--  1 root root  909077 May 31 19:05 nginx_1.10.1.orig.tar.gz-rw-r--r--  1 root root   43932 Jul  8 17:52 nginx-common_1.10.1-0+xenial0_all.deb-rw-r--r--  1 root root   35342 Jul  8 17:52 nginx-doc_1.10.1-0+xenial0_all.deb-rw-r--r--  1 root root  746780 Jul  8 17:52 nginx-extras_1.10.1-0+xenial0_amd64.deb-rw-r--r--  1 root root 6627998 Jul  8 17:52 nginx-extras-dbg_1.10.1-0+xenial0_amd64.deb-rw-r--r--  1 root root  471502 Jul  8 17:52 nginx-full_1.10.1-0+xenial0_amd64.deb-rw-r--r--  1 root root 3806376 Jul  8 17:52 nginx-full-dbg_1.10.1-0+xenial0_amd64.deb-rw-r--r--  1 root root  333962 Jul  8 17:52 nginx-light_1.10.1-0+xenial0_amd64.deb-rw-r--r--  1 root root 2428032 Jul  8 17:52 nginx-light-dbg_1.10.1-0+xenial0_amd64.deb

We’ll need to remove Nginx[3]. As long as we don’t purge, the config files will stay in place. It never hurts to get a backup, anyway, though.

apt-get remove nginx [nginx-core]

Now let’s install our newly compiled version of Nginx[4]:

dpkg --install /usr/src/nginx-[common|full]_1.10.1-0+xenial0.amd64.deb

If it didn’t blow up, we’re in decent shape. To be in even better shape, make sure your moduler was installed by running nginx -V. You should see something like the same line you added to the rules file from earlier (probably at the end):

[...] --add-module=/usr/src/nginx-1.10.1/debian/modules/nginx-rtmp-module

Since we tinkered with Nginx, mark it for version hold[5] so apt-get upgrade doesn’t wipe out our changes:

apt-mark hold nginx-full

That’s all you need to do. Happy sysadmin-ing!


  1. Whichever you pick will dictate which you install. ↩︎

  2. Word of the week, it seems. ↩︎

  3. I had nginx-core installed, so I had to remove that, as well. ↩︎

  4. Pick the flavor depending on where you put the module in the rules file from earlier. If you choose a different flavor, your module won’t be installed. ↩︎

  5. To undo this, use apt-mark unhold ↩︎


How I Solved Related Posts not Showing when Running WordPress, JetPack, and Nginx

Ever since I started this blog up again at the beginning of January, I wanted to have related posts be visible at the end of each post. I use JetPack and I figured turning Related Posts on would be easy and I would be all set.

Well, not so much.

I turned on Related Posts and discovered nothing was appearing. I knew there wasn’t really anything that could be wrong as I don’t have any errors in WordPress, PHP-FPM doesn’t report errors in any of my logs, and I can use other services like WordPress.com Stats and the like.

I did some digging and after my search turned up nothing, I reached out to Automatic support and asked about my JetPack issue. We worked through a few things including making sure WordPress.com Stats was working, the JetPack servers could see my site (which they did). We even checked to make sure the plugin was even grabbing data to display in the first place. This can be done by adding ?relatedposts to the end of any single post URL. You’ll get a JSON blob in return if it’s doing what it’s supposed to on the back end. We figured, well, that can’t be it either. Turns out, the answer wasn’t anywhere where I was expecting it to be.

My problem was with my Nginx server configuration for this site.

Here’s where it gets technical. For any path, you’ll have a set of URIs for the server to try before it gives up. The code usually looks like this:

try_files $uri $uri/ /index.php

In most cases, that’s right! WordPress will work just fine like this. You won’t notice errors and everything will parse fine… except for JetPack’s Related Posts plugin.

See, with the above line of code, we’re telling Nginx that it’s ok to try the URI by itself, with a slash after it, or try index.php by itself, depending on what the URI actually is. Nginx, however, is very detail-oriented and will only do exactly what you tell it to do. In my case, I left out a specific instruction: process index.php with arguments, please. By doing so, we turn the above line of code into this line of code:

try_files $uri $uri/ /index.php?q=$uri&$args

JetPack calls upon that JSON blob I told you about a bit ago just like that: index.php followed by the URI (not fancy, something like a post ID), and any additional arguments necessary after that. So in the case where my post ID is 105 and it’s grabbing the related posts for that, it would be calling

index.php?q=105&relatedposts=1

Boom.

I hope you find this helpful. If you did (or did not), leave a comment below.




Johnathan Lyman
Kenmore, WA,
United States
 
blogging, design, technology, software, development, gaming, photography