Tuesday, February 20, 2007

Working with Cameras in AS3

Trying to get my Flash application to work with multiple cameras hasn't been a breeze, so to speak. Detecting cameras and their capabilities is a bit difficult with Flash, at least if you need it to work on most platforms.

The Flash Camera API is quite limited, but, when used wisely, will allow you to get more information about the connected camera than you'd think.

I've been able to get good results by taking a number of steps in acquiring a camera. First, let's think about the different cases that may keep your Flash application from getting a good signal:
1) there is no camera driver on the computer
2) there's a shutter in front of the camera
3) another program is using the camera
4) there are several drivers for the camera, but the default driver won't work with Flash
5) there are drivers, but the camera is not connected

It would be great to be able to distinguish between these cases, so that you may prompt the user with information on what to do to fix the problem. It turns out you can, at least almost.

The first thing to do is getting the default camera:

var cam :Camera = Camera.getCamera();


If there is no camera driver in the system, this method will return null, so case 1) is solved. If there is, you will get a reference to the default camera, although this doesn't mean you will actually get access to it. The next step is to try and get access by attaching the camera stream to a video display.

cam.addEventListener( StatusEvent.STATUS , onCamStatus );
video.attachCamera( cam );


If this is the first time requesting a camera, this will make Flash Player prompt the user to allow (or deny) access to the camera. Let's suppose that the user allows access. This will result in onCamStatus being called.

The next step is to verify that the camera actually has something to show. Unfortunately, Flash Player isn't much help there. Even if the camera doesn't have a signal, it won't peep. The best way to figure out if the camera is working is to check the motion level and the frame rate. BUT! don't check it right away, or be fooled. Now, here's where it gets tricky. To be able to monitor the activity level, you have to attach a listener. Otherwise, the activityLevel is always 0.

cam.addEventListener( ActivityEvent.ACTIVITY, onCamActivity );

Now, here's the trick: When you activate the camera and the motion detector, the activity spikes immediately (presumably because the first captured frame is a lot different from no frame at all). But this is not the kind of activity we're interested in. So the activity events are no use. But the activity detector IS. Instead of using the ActivityEvent, we need to poll the framerate and activity level with a timer.

timer.addEventListener( TimerEvent.TIMER, onCamCheck );
timer.start();

On the timer event, check the cam.currentFPS and cam.activityLevel. Now, there are different possible outcomes of this. Let's say you check these values a few dozen times. If the framerate is 0 all the time, it means that Flash doesn't get any signal from the camera (case 3, 4, 5). If you DO get a framerate > 0, but the activityLevel doesn't go beyond, say, 5, it probably means that there's a shutter in front of the camera or your user is a stiff (case 2).

In case 3, 4 or 5, it is a good idea to check whether there are other cameras and to prompt the user to select one. Here you need another little trick to make it work.

if (Camera.names.length > 1)
{
dialog = new SelectDialog();
dialog.addEventListener( Event.SELECT, onSelectCam );
PopUpManager.addPopUp( dialog, Sprite( Application.application ) );
}

Here, SelectDialog is your own brewed dialog window that dispatches the SELECT event if the user selects another camera. In my case, the SelectDialog class has a property selectedCam, which returns the selected index in the Camera.names array. The reason for this is that Camera.getCamera() doesn't accept the actual name of a camera, but it's index (contrary to documentation). So, here's the code for selecting the right camera:

private function onSelectCam( event :Event ) :void
{
var index :String = dialog.selectedCam;
var name :String = Camera.names[index];
cam = Camera.getCamera( index );
}

And there it is, all the building blocks you need to successfully get camera access. While it is not 100% bullet-proof, this will give you your best shot at helping the user fix his camera problems.

Friday, August 11, 2006

Having fun with Bitmaps in AS3

So, I wanted to grab a snapshot from my camera in Flash Player and send the image to the server to share it in a web conference.

Thanks to the excellent work of Tinic Uro of Adobe I started off with adding his AS3 port of a JPEG encoder to my app to make sure that it doesn't send megabytes data to my server. Have a look here for his article on the AS3 JPEGEncoder.

It turned out that my pc is a bit slow and will take about 8 seconds to encode a 640x480 bitmap. This wouldn't be so bad if Flash Player was multi-threaded, but as it is, the player (and the browser that runs it) will block during the encoding. On my newer laptop, the encoding only takes about 1 second. But hey, I'm probably not the only one that has an older PC, so I decided to do something about the blocking.

I modified the JPEGEncoder class so that it chops up the work in chunks and automatically adapts the chunksize to a maximum blocking delay that is configurable. It also emits ProgressEvent's and a complete event when done. And I threw in a cancel() function as well. Here's the source for JPEGEncoder with ProgressEvent.

With this modification, the encoder pauses for 10 ms about every 200ms to let the player do other things like updating the display list while only adding about 5% to the processing time. And I added a progress bar to show the user how the encoder is doing.

Have fun with it.