So, Axis makes a pretty good webcam (I have the 206W
—pictured at right—for grCam,
I've been lazy, and just exposed the webcam at home up until now, partly because mine has a very noticeable green LED that blinks when people are connected to it (so I know not to do anything too embarassing without checking first), and partly because I was convinced this wasn't a problem worth learning PHP (for which I have, I confess, the barest modicum of respect; it's probably got something to do with the myriad “site-wide scripting!!!” 1337 hacks it engenders) over.
But since I had some unexpected time off after Europe and before actually starting with the new $CURRENT_EMPLOYER (more on that some other time when I feel like it, maybe), I started to tinker around with what Sean had sent me ages ago as what he did. Turns out that if you're doing everything in Perl/static HTML, it's less feasible to do the timing thing (mostly because you can't pad how long it takes to load the “image” by sleeping for a few thousand usecs if it's actually an image file, rather than a PHP script). Also, his implementation relies on having a daemonized Perl process running at all times, re-fetching new images to store (and forward, if anybody loads the page) which is, at best, clumsy. (In Sean's defense, he had better things to do, so he solved the problem the quick way and moved on.)
I was convinced that the right way to do this was, when someone connected to the webcam URL, to go grab the image from the webcam and just feed it back to the client, conveniently sanitizing the arguments passed through to the webcam's CGIs (incidentally, it's Linux-based and at least the OS is GPLed, but I never bothered to go find whether/where they'd posted their code; might have wasted a bunch of time with that anyway, since they probably over-engineered the problem), negating the remote access/DoS risks. (I guess there's still something of a DoS risk, but more on that later.) This would, I theorized, also mean that I could make my LED flashy light work properly (it doesn't flash if you just grab a single static JPEG, no matter how many of them you grab consecutively, which is what Sean's approach does).
So, after a bit of work, here's the
#!/usr/bin/perl -w
#
# copyright 2006, gr@eclipsed.net
# no warranty expressed or implied, use/modify/redistribute as you like
use strict;
use CGI qw/:standard/;;
use Socket;
my $debug = 0;
my $server = 'change.this.to.your.webserver';
my $port = 80;
my $proto = getprotobyname('tcp');
my $iaddr = inet_aton($server);
my $paddr = sockaddr_in($port, $iaddr);
print STDERR "query: $ENV{QUERY_STRING}\n" if $debug;
my $format;
# It's a bad idea to do browser-detect here, since it changes
# frequently and, for instance, Opera, which supports MJPEG, often
# pretends to be MSIE to get through stupid browser detects... but
# we'd still want to give them the MJPEG. So do that in your [D]HTML.
# if ((param('format') eq 'mjpg') && !($ENV{HTTP_USER_AGENT} =~ m/MSIE/)) {
# $format = "mjpg/video";
# } else {
# $format = "jpg/image";
# }
if (param('format') eq 'mjpg') {
$format = "mjpg/video";
} else {
$format = "jpg/image";
}
my $resolution = param('resolution');
$resolution = '320x240'
unless (defined($resolution) && ($resolution =~ m/[0-9x]+/));
print STDERR "format: $format\n" if $debug;
print STDERR "resolution: $resolution\n" if $debug;
my $path = "/axis-cgi/${format}.cgi?resolution=${resolution}";
socket(AXISCAM, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
connect(AXISCAM, $paddr) or die "connect: $!";
print STDERR "connected\n" if $debug;
print AXISCAM "GET $path\n";
print STDERR "GET sent\n" if $debug;
binmode AXISCAM;
my $rc = 0;
while (<AXISCAM>) {
if (m,^HTTP/1,) {
$rc = (split(' ', $_))[1];
last unless ($rc == 200);
} else {
print $_;
}
print STDERR "loop\n" if ($debug > 1);
}
print STDERR "image received, closing out\n" if $debug;
close AXISCAM;
(Incidentally,
This works well enough, but I'm not thrilled with spinning off a new connection to the webcam (over the home wireless network) every time someone hits the webserver's reproduction of it. I'd much rather have a process hanging about that would open a socket to the webcam upon request and then provide that, via SysV IPC or similar, to any and all requesting connections from CGIs, which would also get around the relatively modest processing and bandwidth limitations in the camera (as far as it's concerned, it'd be only serving one client). So that'll be coming… eventually.
You can see the appropriate ways to touch this API by viewing source at grcam.eclipsed.net, of course.
Post a Comment