Proxying MJPEG video for broadcast with Octoprint

I just set up Octoprint on a Raspberry Pi to manage my Rostock Max V2 3D printer. You can view the webcam, timelapses of existing prints, and other cool stuff at: http://3dprinting.thedoble.com

Problem is that even at extremely low resolutions, my internet connection can’t handle more than one person accessing the webcam. My goal was to have my internet connection upload only one video stream, and have a server rebroadcast it to the viewers.

You can use this technique to reduce bandwidth for any webcam that you want to have lots of people viewing, but where you only have a limited upload.

Understanding octoprint’s webcam support

  • Octoprint uses mjpeg-streamer to take the USB webcam and publish it on a local webserver on port 8080 in static image, MJPEG stream and other formats. The octoprint web interface directs your browser to connect to the IP of your octoprint machine for live video, and connects to localhost to grab stills for the timelapse.
  • My webcam is a Logitech C525. It worked out of the box with OctoPi, however the bandwidth requirements were massive. 640×480 video at 5fps was overloading my cable’s 3mbps upload. Even stepping down to half that resolution was still using a pile of bandwidth. I wanted to use less than 500kbps and get a decent quality image.
  • On the upside, mjpeg-streamer used almost no CPU. Around 2%.

Trading CPU for bandwidth

  • Adding the quality command (eg: ‘-q 25’) to the /boot/octopi.txt camera_usb_options line doesn’t seem to do anything. I’m guessing because there’s some sort of hardware acceleration going on that means the JPEG compression is being skipped? Please reply if you know a way around this – it would be great to avoid the CPU hit from the next point …
  • Changing the camera to YUVY mode by adding the -y command enabled the -q setting to work, resulting in much smaller bandwidth usage, but much higher CPU usage. I found that 640×480 at 30fps would use almost one full CPU core of the Raspberry Pi.
  • The camera on OctoPi is managed by a service called webcamd – you can restart it by running ‘sudo service webcamd restart’ each time you change a setting in the octopi.txt config file, instead of rebooting the whole RasPi.
  • Webcamd writes a log to /var/log/webcamd.log – if you’re making setting changes and they arent showing up, read this log. Use ‘tail -n 50 /var/log/webcamd.log’
  • I settled on 544×288 at 15 FPS with JPEG quality 25. This uses one cpu core at 100%, and 400kbps of bandwidth usage. The image fits the octoprint interface nicely thanks to it’s widescreen aspect ratio and seems free of jitter!

Setting up a re-stream proxy

Next, I wanted to be able to have multiple people watching the stream without it affecting my upload. I found a node.js module called mjpeg-proxy. I know literally nothing about javascript, npm, node or any web stuff, but I managed to muddle my way through to get it to do what I wanted. Note that I am NOT an expert on any of the below! Perform at your own risk etc.

  • I set up a cheap ubuntu VPS for $5 a month from VULTR – it doesn’t have to be powerful, it’s literally just a bandwidth box.
  • Install Node which is like a package manager for javascripty stuff. ‘sudo apt-get install nodejs’
  • Install legacy node if you run ubuntu just in case. ‘sudo apt-get install nodejs-legacy’
  • Install build-essential so you can compile the node packages ‘sudo apt-get install build-essential’
  • Install mjpeg-proxy ‘sudo npm install mjpeg-proxy’ – this will dump it into your user directory under node_modules.
  • Install express – required for mjpeg-proxy. ‘sudo npm install express’
  • Navigate to ‘/node_modules/mjpeg-proxy/’ and make a new file called restream.js ‘sudo pico restream.js’
    var MjpegProxy = require('mjpeg-proxy').MjpegProxy;
    var express = require('express');
    var app = express();
    app.get('/index1.jpg', new MjpegProxy('OCTOPUBLICIP:PORT/?action=stream').proxyRequest);
    app.listen(8080);
  • When this script runs, mjpeg-proxy will contact your public IP address and access mjpeg-streamer, then re-broadcast it at /index1.jpg on port 8080.
  • You will need to forward a port on your router to the RasPi’s mjpeg-streamer port (8080).
  • To test that it works, run ‘node restream.js’ – if you just get a blank line returned, that’s good!
  • Go to http://yourserverIP:8080/index1.jpg and you should see your webcam.
  • Press Ctrl+C to end the process and return to the command line.

Setting up autorun

Next, lets make it so that the proxy will run automatically on system boot, and restart if it crashes. We can use another javascript thingy for that – it’s called ‘forever’

  • Install forever – ‘sudo npm -g install forever’
  • Navigate to the folder where we put our restream.js file and run ‘forever start restream.js’ – you should see a message saying it’s running.
  • You can check status or stop forever by typing ‘forever list’ and ‘forever stop restream.js’

This gets it running, but if the system reboots, you’ll have to re-run the command. Lets set up an init.d script to run forever as a service.

  • Copy the template to /etc/init.d and rename it to something like ‘restream’
  • Open the file and edit the following lines:
    • Provides: restream
    • dir: where the script is stored, eg “/root/node_modules/mjpeg-proxy/”
    • cmd=”forever start restream.js”
  • To start, type ‘/etc/init,d/restream start’
  • To check if it’s running, ‘/etc/init.d/restream status’
  • Note: I found that when I changed my mjpeg-streamer settings like resolution, I would have to reload the mjpeg-proxy as well. You can do this by running ‘/etc/init,d/restream restart’

Configuring Octoprint

Finally, we can tell octoprint to go to the VPS to get it’s video stream. Go to Settings > Webcam and change ‘Stream URL’ to http://yourserverIP:8080/index1.jpg

  • You may need to clear your browser cache.
  • Right clicking on the image and choosing ‘view image’ (firefox) or ‘open image in new tab’ (chrome) should show you the video stream with the IP of your VPS.

That’s it! Now anyone connecting to the octoprint server will see the video feed from your VPS instead of from the RasPi, saving you delicious bandwidth! Also, when there’s no-one watching the stream, the VPS proxy disconnects, so your connection isn’t uploading data for no reason.

Dirty DNS trickery

The only downside to this setup is that when you browse your octoprint server from within your own network, you’re both uploading and downloading the video feed. To get around this, you can add a sneaky DNS redirect.

  • Set up a public DNS entry for your server, it can be anything. mycheekydns.cheapdomains.com
  • Point this DNS entry at your VPS IP and update octoprint’s ‘Stream URL’ accordingly.
  • Sneaky part: Set up a static DNS entry on your local network’s router, pointing the VPS’s domain to your RasPi’s internal IP address. EG 10.0.0.1

Now, any computers on the local network that try to access the domain will be given your local RasPi’s address, so the webcam traffic won’t leave the network, and everyone outside the network resolves the VPS’s IP address and gets the restream feed.

EDIT: I thought this would work, but I can’t figure out how to make ‘express’ output ‘/?action=stream’ which is what mjpeg uses. Without this, the local/remote URLs don’t match in Octoprint and you get a 404 not found error. If anyone else has an idea please reply below.

Happy printing!

 

2 comments

  1. I’ve got it configured to accept a wildcard after the webcam directory and I think i should be able to perform the DNS trickery portion above which you said wasn’t working because the ‘/?action=stream’ portion.

    The following change is needed:
    app.get(‘/webcam/*’…..

Leave a Reply

Your email address will not be published. Required fields are marked *