It's a Bird. It's Another Bird!

 in

Video Is for Chumps

Well, it's for chumps with insane bandwidth anyway. Although my business Internet connection here in my home office has 5Mbit upload speeds, it turns out that streaming multiple video feeds will saturate that type of bandwidth very quickly. I also had the problem of taxing the embedded Web server on the phone with more than one or two connections. I still hadn't given up on full streaming, so my first attempt at "Global BirdCam" was to re-encode the phone's video on my Linux server, which would be able to handle far more connections than an old Android handset.

Thankfully, VLC will run headlessly and happily rebroadcast a video stream. Getting just the right command-line options to stream mjpeg properly proved to be a challenge, but in the end, this long one-liner did the trick:


cvlc http://PHONE_IP:8080/videofeed --sout \
'#std{access=http{mime=multipart/x-mixed-replace; \
boundary=7b3cc56e5f51db803f790dad720ed50a}, \
mux=mpjpeg,dst=0.0.0.0:2000/}'

The cvlc alias just starts VLC headless. The mime and boundary stuff took the longest to figure out. Basically, I had to get that right, or Web browsers would just try to download a file instead of playing a stream. This method did work, actually, and I could connect multiple clients to the server on port 2000 and get the remuxed stream without overtaxing the phone. (The phone served out only the single feed to the server, and the server is far more robust.) Unfortunately, that didn't solve my bandwidth issue.

My Flipbook Solution

Although the VLC solution did work, it didn't really fit my needs. I couldn't stream to the Internet due to lack of bandwidth, and even if I could, my server could handle only a handful of clients before it petered out as well. What I ended up with as my final solution is rather elegant and very efficient.

You may recall I said the Android application allows for high-resolution snapshots to be taken along with a direct video feed. Rather than streaming video, I figured if I took a high-res photo every second, I could get a far better image and also save boatloads of bandwidth. I still wanted a video-like experience, so I concocted a handful of scripts and learned some JavaScript to make a sort of "flipbook video" stream on a regular Web page. This was a two-part process. I had to get constantly updated photos, plus I had to build a Web page to display them properly.

Step One: Getting the Photos

My first instinct was to use a cron job to fetch photos regularly from the Android phone. Because cron jobs run only every minute, I dismissed my first plan right away. I didn't need full-motion video, but "One Frame Per Minute" is pathetic by any standard. I ended up making a few scripts, one of which I launch via rc.local on system boot (Listings 1 and 2).

Listing 1. bird_update Script


#!/bin/bash
while true
do
   bird_getphoto
   sleep 1
done

Listing 2. bird_getphoto Script


#!/bin/bash
#Variables -- change to fit your needs
ORIGINAL_PHOTO=/dev/shm/birdtemp.jpg
MODIFIED_PHOTO=/dev/shm/birdmod.jpg
FINISH_PHOTO=/dev/shm/birds.jpg
CAMERA_IP=192.168.1.201
PHOTO_URL=http://192.168.1.201:8080/photo.jpg

if eval "ping -c 1 $CAMERA_IP > /dev/null"
then
   /usr/bin/wget -r --timeout=10 --quiet -O \
     $ORIGINAL_PHOTO \
     "$PHOTO_URL"
  
   convert $ORIGINAL_PHOTO \
     -quality 70% \
     -pointsize 64 \
     -fill white \
     -annotate +675+60 "    `date +"%I:%M:%S %p"`" \
     $MODIFIED_PHOTO

   rm $ORIGINAL_PHOTO
   mv $MODIFIED_PHOTO $FINISH_PHOTO
        fi

The first script, bird_update (Listing 1), is started via rc.local on my server. I could have called the larger script directly from rc.local and had it loop, but this way, I could make a change to the main script (bird_getphoto, Listing 2) and not worry about restarting the rc.local stuff. bird_update runs bird_getphoto, sleeps for a second and starts over. That means if I make a change to bird_getphoto, the changes would be reflected on the next iteration of the loop, with nothing to start over. Since I tweaked bird_getphoto about 6,000 times, this method was ideal.

The bird_getphoto is what does the "dirty work" so to speak. Stepping through the commands should be fairly self explanatory, but basically:

  1. See if phone is on-line.

  2. Get photo from phone (with a short timeout—by default, timeout is absurdly long, and an occasional hiccup shouldn't stall the entire system).

  3. Store photo in ramdisk. I did this to save on hard drive wear. I figure I'm saving a file every second, and it's silly to do that to spinning media every time.

  4. Compress and annotate the photo. At first I had my phone in portrait mode, so I had to rotate as well. The convert program, which is part of the ImageMagick package, is very powerful. I added a timestamp to the photo, mainly because I could.

  5. After download and conversion is complete, mv the temporary file to the live image. I added this extra step, because if convert stores directly to the final filename, it gets displayed as a corrupt image if the Web server tries to serve it out during the conversion process. The mv command is almost instantaneous, so I haven't seen any weird corruption after adding the extra step.

I'm almost embarrassed to admit how long it took me to fiddle around with commands, ideas, loops and so forth before coming up with the scripts shown here. As with all my articles, please feel free to change and/or improve on my ideas to best fit your needs. These scripts have been running smoothly for weeks now, and they seem to be fairly bulletproof when it comes to network failures and such. Getting the photos regularly updated, however, was only half the problem—as it turns out, the easier half.

Step 2: JavaScript, and Breaking the Internet

In order to display the constantly updated bird photos, it was easy enough to create a symbolic link from /dev/shm/birds.jpg to /var/www/birds/, where my Apache virtual host folder was located. I created a simple HTML file with an img tag, and I could see my bird feeders from anywhere. In order to get a refreshed image, however, I had to refresh the entire Web page. It worked, but it was ugly. I had to reach out for some JavaScript help.

Before getting to the final HTML file, it's important to explain that while getting JavaScript to refresh a single image on a page isn't terribly difficult, browsers are designed to cache as much as possible, so making sure the image was actually fetched from my server every couple seconds proved challenging. Listing 3 shows my final HTML file.

Listing 3. birds.html


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
 ↪"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<h3>The birds. Or not.</h3>
<script type="text/javascript">
refreshImage = function()
  {
    img = document.getElementById("cam");
    img.src = "http://example.com/birds.jpg?rand=" 
     ↪+ Math.random();
  }
</script>
<meta http-equiv="Content-Type" content="text/html; 
 ↪charset=iso-8859-1" />
</head>
<body onload="window.setInterval(refreshImage, 1*2500);">
<center>
<img style="width:100%;max-width:2048px" 
 ↪src="http://example.com/birds.jpg" id="cam" />
<br />
<small><em>This should constantly refresh</em></small>
</center>
</body>
</html>

A large part of the top of the file shown in Listing 3 is just defining the specific HTML standards in use. I'll be honest, most of it is over my head. The key part of the script is the JavaScript code, which defines an action for an image with a specific ID. You can see the ID is "cam" in the JavaScript and in the img tag below. The peculiar part of the script is the bit of random info after the photo URL. That's actually the part of the script that not only reloads the image every 2.5 seconds, but also loads the image with a ?rand=RANDOMNUMBER at the end. That's basically me fooling the browser into thinking there is a new image to download every time, so I don't get shown a cached image. There are several ways to do such a thing, but this proved to be the simplest and most cross-browser-friendly in my testing. There is concern of filling up caches or buffers on the server, but so far I haven't experienced any issues.

______________________

Shawn Powers is a Linux Journal Associate Editor. You might find him on IRC, Twitter, or training IT pros at CBT Nuggets.