Oct 18, 2012

Displaying the camera preview - Instant Mustache #5

This article is part of a series of articles about the development process of Instant Mustache, a fun camera app that adds mustaches to all faces using face detection. Click here to get a chronological list of all published articles about Instant Mustache.

From the last article we already have three components: A CameraActivity with not much code, an empty CameraFragment and a CameraFragmentListener interface for the communication between fragment and activity. Now we need to write the actual CameraFragment code to display the camera preview.

The CameraPreview component

For displaying the preview we need an instance of SurfaceView to draw the actual camera picture on. We'll extend the SurfaceView to create our own view component called CameraPreview.

In the first articles we decided to use a square ratio for the preview. After some testing around it seems the emulator is the only "device" that supports a square sized camera preview and picture size out of the box. To work around this issue we would need to use a widely supported ratio (4:3) and crop the preview picture as well as the taken photo ourselves. To keep the code and the first version of the app small (and to follow the Minimal Marketable App principle) I decided to change this requirement. We will use the commonly supported ratio of 4:3.

We implement onMeasure() to set the dimension of the view to a 4:3 ratio:

onMeasure() - CameraPreview.java

Setting up the camera

Even though we know our desired ratio we can't just set a size via camera.setParameters(Camera.Parameters). Instead we have to query the Camera.Parameters object we get from getParameters() to retrieve a list of supported preview and picture sizes. Then we have to scan this list for a size that has our desired ratio. This is what determineBestSize() does:

determineBestSize() - CameraFragment.java
We are using a threshold for the preview and picture size to save some heap space for the bitmap transformations we'll do later. For now these limits are 640x480 for the preview size and 1280x960 for the  picture size.

Finally the determined values are used to setup the camera object:

setupCamera() - CameraFragment.java

The CameraPreview component will be used as view of our CameraFragment by returning it in onCreateView(). In addition to that our CameraFragment needs to implement SurfaceHolder.Callback in order to get notified when the surface is created or destroyed.

Accessing the camera

To access the camera of the device we need to call Camera.open(int cameraId) to obtain a Camera object. The camera ids are numbered starting with 0. As we currently don't want to support multiple cameras we will just call Camera.open(0).

Unfortunately using the camera can cause undefined exceptions to be thrown. Therefore we need to wrap some code accessing the camera object into try-catch blocks and catch the generic Exception object. This is usually considered bad practice but in this case encouraged to do by the documentation.

Sharing the camera resource

The camera can only be used by one application at a time so we need to release the camera every time the activity gets paused. Otherwise the user would not be able to use other camera applications while our activity is in the background. In some cases not releasing the camera can lead to the CameraService crashing. If this happens no camera application can be used until the user reboots his phone. We never want this to happen.

To be safe we will open the camera by calling Camera.open() in the onResume() method of the CameraFragment and releasing it again in onPause().

Once we have a Camera object and our surface is created we can assign the surface to the camera object and start the preview. We wrap the call to startPreview() in our own method called startCameraPreview() inside our fragment. This way we can setup the camera object before actually starting the preview.

startCameraPreview() - CameraFragment.java


Determining the display orientation

The screen can be rotated in four different angles (0°, 90°, 180°, 270°). In addition to that the camera can also be built into the device in four different angles by the manufacturer. Finally the camera can be on the front or on the back of the device. We will need to get all these angles and tell the camera object the display orientation so that the preview will be drawn on the surface using the right rotation.

This sounds tricky but fortunately there's a code snippet for that in the Android documentation. We'll use this code snippet to implement determineDisplayOrientation().

Screenshot

And that's how our app looks like so far:


As always the source code of the current version of the app is available at Github.