Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Taking a Picture Using android.media.Camera

Published? true
FormatLanguage: WikiFormat

Problem:

You want to have more control on the various stages involved when taking a picture.

Solution:

Create a SurfaceView and implement the callbacks fired when the user takes a picture in order to have control over the whole process of image capturing.

Discussion:

Sometimes you may want more control over the stages involved when taking a picture or you may want to access and modify the raw image data acquired by the camera. In these cases, using a simple Intent to take a picture is not enough.

We're going to create a new Activity and customize the view to make it full screen inside the onCreate method.

public class TakePictureActivity extends Activity {
    private Preview mCameraView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Force screen in landscape mode as showing a video in 
        // potrait mode is not easily doable on all devices
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                
        // Hide window title and go fullscreen
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        mCameraView= new Preview(this);
        setContentView(mCameraView);
    }
}

The Preview class is the bulk of the recipe. It handle the Surface where the pixels are drawn and the Camera object.

We define a ClickListener in the constructor so the user can take a picture by just tapping once on the screen. Once we get the notification of the click we take a picture passing as parameters four (all optional) callbacks.

class Preview extends SurfaceView implements SurfaceHolder.Callback, PictureCallback  {

    private SurfaceHolder mHolder;
    private Camera mCamera;
    private RawCallback mRawCallback;

    public Preview(Context context) {
        super(context);

        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mRawCallback = new RawCallback();
            
        setOnClickListener(new OnClickListener() {
                        
            @Override
            public void onClick(View v) {
                mCamera.takePicture(mRawCallback, mRawCallback, null, 
                                Preview.this);
            }
        });
    }

The Preview class implement the SurfaceHolder.Callback interface in order to be notified when underlying surface is created, changed and destroyed. We'll use these callbacks to properly handle the Camera object.

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewSize(width, height);
        mCamera.setParameters(parameters);
                
        mCamera.startPreview();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();
            
        configure(mCamera);

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException exception) {
            closeCamera();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        closeCamera();
    }

As soon as the camera is created we call configure in order to set the parameters the camera will use to take a picture. Things like flash mode, effects, picture format, picture size, scene mode and so on. Since not all devices support all kind of features always ask which features are supported before setting them.

    private void configure(Camera camera) {
        Camera.Parameters params = camera.getParameters();

        // Configure image format. RGB_565 is the most common format.
        List<Integer> formats = params.getSupportedPictureFormats();
        if (formats.contains(PixelFormat.RGB_565))
            params.setPictureFormat(PixelFormat.RGB_565);
        else
            params.setPictureFormat(PixelFormat.JPEG);

        // Choose the biggest picture size supported by the hardware
        List<Size> sizes = params.getSupportedPictureSizes();
        Camera.Size size = sizes.get(sizes.size()-1);
        params.setPictureSize(size.width, size.height);

        List<String> flashModes = params.getSupportedFlashModes();
        if (flashModes.size() > 0)
            params.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);

        // Action mode take pictures of fast moving objects
        List<String> sceneModes = params.getSupportedSceneModes();
        if (sceneModes.contains(Camera.Parameters.SCENE_MODE_ACTION))
            params.setSceneMode(Camera.Parameters.SCENE_MODE_ACTION);
        else
            params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);

        // if you choose FOCUS_MODE_AUTO remember to call autoFocus() on
        // the Camera object before taking a picture 
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
        
        camera.setParameters(params);
    }

When the surface is destroyed we close the camera and free its resources:

    private void closeCamera() {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

The Jpeg callback is the last one called, this is where we restart the preview and save the file on disk.

    @Override
    public void onPictureTaken(byte[] jpeg, Camera camera) {
        // now that all the callbacks have been called it is safe to resume the preview
        mCamera.startPreview();
                
        saveFile(jpeg);
    }
}

Finally we implement the ShutterCallback and again PictureCallback to receive the uncompressed raw image data.

 
class RawCallback implements ShutterCallback, PictureCallback {

    @Override
    public void onShutter() {
        // notify the user, normally with a sound, that the picture has 
        // been taken
    }

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        // manipulate uncompressed image data
    }       
}

See Also:

Taking a Picture Using an Intent