It's a Bird. It's Another Bird!
Editor's Note: Shawn will be revisiting his birdcam in the December issue of Linux Journal, so here's the original article in the series to refresh your memory.
My new full-time job is one that I can do from my home office. One of the perks of working at a home office is that an office with a window is almost guaranteed. Because I have an office window for the first time in my career (not counting the one year I had a part-time office facing the dumpster), I figured it would be the perfect opportunity to put up some bird feeders.
Unfortunately for my family, but very fortunately for the local birds, when I decide to do something, I usually go all in. Rather than a simple feeder with mixed bird seed, I decided to get various types of feeders, specialized seed, a bird bath with flowing water and trees planted for shade and cover. My family refers to the area outside my office window as "BirdTopia" (Figure 1). And they haven't even seen the heated bird bath and peanut feeders I have planned for winter!
Figure 1. BirdTopia as Seen via BirdCam
So, what does my obsession with bird watching have to do with Linux? Well, obsession demands that either I stare out my window all day and lose my job, or I figure out some way to watch my birds while staring at a computer screen. Enter: BirdCam. I needed a way to stream a live video feed of BirdTopia, without spending any more money. (The "not spend money" part was implied by my wife.)
The CameraBecause I don't share an office with anyone, my camera options didn't have to be pretty. I considered a USB Webcam, but all the Webcams I have are really low quality. Thankfully, I have a drawer full of old cell phones that have been replaced with newer models. I had three iPhone 3GS handsets and a Samsung Galaxy S2 with a cracked screen. The iPhones seemed to be in better shape, so first I tried to use one of them. I purchased a $5 application called iWebcamera, which turns an iOS device into an IP camera with a built-in Web server. Unfortunately, the iPhone 3GS has a pretty cruddy camera, so although the application worked well, I wasn't satisfied.
Next up was my Galaxy S2 phone with the cracked screen. Obviously the crack didn't matter, and the camera is much nicer. Also, the Google Play store has an app called IP Webcam that is completely free and completely awesome. The application puts a big-ugly ad on the screen of the phone, but the remotely viewed video has no ads at all. I highly recommend using an old Android device instead of using an old iOS device, if you happen to have the choice. I mounted the phone on the inside of my office window using a suction-mount cradle designed for a car (Figure 2).
Figure 2. This suction cup is an improvement over my original "lean against the window" design.
ViewingBoth the iOS app and the Android app have a built-in Web server that allows for direct viewing of the video stream (Figures 3 and 4). The iOS application's interface is far less advanced than the free Android program, but they both allow for either viewing the mjpeg video stream or a real-time snapshot. With the Android application (which is what I focus on from here out, because it's free, Linux-based and far better), the resolution of the full-motion video is less than that of the photo snapshot.
Figure 3. The iOS Web interface is functional, but sparse.
Figure 4. The Android Webcam software is far more robust.
The built-in Web server on the phone is probably sufficient if you just want to watch from one or two computers on your network. For me, however, it wasn't enough. I wanted to view my bird feeders from multiple computers, both internal and on the Internet. I also wanted to be able to share my BirdCam with the world, but I wanted to serve everything myself, rather than depend on a service like Ustream. And, that's where things started to get really, really fun.
Video Is for ChumpsWell, 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.
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 PhotosMy 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:
-
See if phone is on-line.
-
Get photo from phone (with a short timeout—by default, timeout is absurdly long, and an occasional hiccup shouldn't stall the entire system).
-
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.
-
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.
-
After download and conversion is complete,
mv
the temporary file to the live image. I added this extra step, because ifconvert
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. Themv
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 InternetIn 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.
To fulfill my personal needs, the bash scripts and the bit of JavaScript really did all I needed. I can view the BirdCam from multiple computers in my house, and even from the Internet. Each "video frame" is around 600K, so although it still uses significant bandwidth, it's nothing like trying to stream full video. I have noticed that with slow cellular connections, sometimes the image freezes, because it tries to refresh before the original image is loaded. I settled on 2500 milliseconds as the refresh time, because it seems to work from most locations (Figure 5).
Figure 5. With the ability to view BirdCam remotely, I can get some great shots, even when I'm not home!
If you visit my BirdCam now, you'll probably notice I've done a little more tweaking. I've added a query to the local weather station, and I added the current temperature to the annotation. The big change, however, is one you hopefully shouldn't notice. Because I knew I'd be sending this to tens of thousands of potential bird watchers, I figured I probably should scale my solution to the cloud. Granted, 5Mbit upload speed is decent, but not if 100 people are trying to check out my backyard!
My simple solution was to replace the sleep
command in the bird_update
script with an scp
command that uploads the bird.jpg file to my Web
hosting provider. I still try to be a good netizen and upload the
actual file to the ramdisk on my provider's server, but with the .jpg
file being served from the cloud, I'm not worried about an influx of
bird watchers. If you were worried about saturating my home connection,
fear not; I planned ahead.
If you set up a similar Webcam, I'd love to hear about it. You're also welcome to watch the exploits of my bird obsession as winter approaches. The bubbling bird bath soon will be replaced with a heated version, and I'll be adding more suet and peanuts for the winter birds. If you happen to be watching, and see a cool bird, don't hesitate to send me the photo! I had a visit from an Indigo Bunting one day that I saw via BirdCam, but that was when I was using an old iPhone, so the image is very poor quality. BirdTopia attracts more than just birds too. While the occasional squirrel is brave enough to visit, the most common non-aviary visitor is Zoey (Figure 6).
Figure 6. Bird water just tastes better.
I wish you an awesome weekend project! I have enjoyed playing with BirdCam almost as much as I enjoyed making a MAME cabinet years ago. There's just something about building with Linux that warms my heart.