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.