Camera2

As we know that Camera has been deprecated on older version and Android API 21 introduce new version i.e. Camera2. So here we will try to understand and work with Camera2.

Since from API level 21, the old Camera class was deprecated and a brand-new Camera2 class was born. In this post, I want to use the least code to create Android camera preview apps with Camera and Camera2 respectively and compare the usage difference between the two sets of APIs.

Compared with old Camera API, new Camera2(android.hardware.camera2) is much complex as it involves all asynchronous calls. Android API 21 introduced this new Camera2 API. So, if you are working on API less than 21, use the old Camera API(android.hardware.camera) and for all above API 21 use this API as the old one is deprecated.

5 main CallBack of Camera2 are:

Callback What for Type
SurfaceTextureListner Prepaing TextureView interface
CameraDevice.StateCallBack() Checking CameraDevice state open or close abstract class
CameraCaptureSession.StateCallBack() Preparing CameraCaptureSession abstract class
CameraCaptureSession.CaptureCallback() Capturing image data from CameraCaptureSession abstract class
ImageReader.OnImageAvailableListner Receiving image data from CameraCaptureSession through ImageReader interface

We will discuss all one by one.


TextureView.SurfaceTextureListener

This callback is actually used since Camera1 API. TextureView is the view which renders captured camera image data. TextureView is prepared at View creation, and this callback gives us a notification when we are ready to prepare for the camera device initialization.

private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        openCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    }
};


CameraDevice.StateCallback()

This is used to check a camera device state. It is required to open a camera.

private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {
        cameraDevice = camera;
        startCamera();
    }

    @Override
    public void onDisconnected(CameraDevice camera) {
    }

    @Override
    public void onError(CameraDevice camera, int error) {
    }
};


CameraCaptureSession.StateCallback()

A callback for configuring capture sessions from a camera. This is needed to check if the camera session is configured and ready to show a preview.

new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(CameraCaptureSession session) {
        try {
            session.capture(capturebuilder.build(), previewSSession, handler);
        } catch (Exception e) {
        }
    }

    @Override
    public void onConfigureFailed(CameraCaptureSession session) {
    }
}


CameraCaptureSession.CaptureCallback()

This callback manages captured sessions. Whenever a focus or a still picture is requested from a user, CameraCaptureSession returns callbacks through this callback.

final CameraCaptureSession.CaptureCallback previewSSession = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) {
        super.onCaptureStarted(session, request, timestamp, frameNumber);
    }

    @Override
    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        startCamera();
    }
};


ImageReader.OnImageAvailableListener

This callback returns an image when CameraCaptureSession completes capture. You need to set this against ImageReader before capturing a still picture.

new ImageReader.OnImageAvailableListener {
    @Override
    public void onImageAvailable(ImageReader imageReader) {
    }
}


Now Let's create an CaptureImageActivity.java to capture image as:

package com.theitbulls.cameraex;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.TextureView;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.theitbulls.cameraex.camera2.Camera2Image;
import com.theitbulls.cameraex.camera2.OnImageCapture;

import java.io.File;

public class CaptureImageActivity extends AppCompatActivity {
    private TextureView textureView;
    private Camera2Image camera2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.camera_activity);

        textureView = findViewById(R.id.textureView);
        camera2 = new Camera2Image(this, textureView);
    }

    public void capture(View view) {
        final ProgressDialog progressBar = new ProgressDialog(this);
        progressBar.setMessage("Capturing please wait...");
        progressBar.show();
        camera2.captureImage(new OnImageCapture() {
            @Override
            public void save(File fileLocation) {
                progressBar.dismiss();

                if(fileLocation == null) {
                    Toast.makeText(CaptureImageActivity.this, "File saved error", Toast.LENGTH_SHORT).show();
                    return;
                }
                Toast.makeText(CaptureImageActivity.this, "File saved at: " + fileLocation.getAbsolutePath(), Toast.LENGTH_SHORT).show();
            }
        });
    }


    @Override
    protected void onPause() {
        super.onPause();
        if (camera2.cameraDevice != null) {
            camera2.cameraDevice.close();
        }
    }

}


Now create Camera2Image.java class where all Camer2 related class are together and you will get a method captureImage() to capture image as:

Camera2Image.java
package com.theitbulls.cameraex.camera2;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import com.tbruyelle.rxpermissions2.RxPermissions;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Camera2Image extends CameraDevice.StateCallback implements TextureView.SurfaceTextureListener {
    protected AppCompatActivity mContext;
    protected TextureView textureView;

    private Size previewsize;

    public CameraDevice cameraDevice;
    private CaptureRequest.Builder previewBuilder;
    private CameraCaptureSession previewSession;

    public static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    public Camera2Image(AppCompatActivity mContext, TextureView textureView) {
        this.mContext = mContext;
        this.textureView = textureView;

        this.textureView.setSurfaceTextureListener(this);
    }

    public void openCamera() {
        CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        try {
            String camerId = manager.getCameraIdList()[0];

            CameraCharacteristics characteristics = manager.getCameraCharacteristics(camerId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            previewsize = map.getOutputSizes(SurfaceTexture.class)[0];

            if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                new RxPermissions(mContext).request(Manifest.permission.CAMERA);
                return;
            }
            manager.openCamera(camerId, this, null);
        } catch (Exception e) {
            Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
        }
    }

    public void startCamera() {
        if (cameraDevice == null || !textureView.isAvailable() || previewsize == null) {
            return;
        }
        SurfaceTexture texture = textureView.getSurfaceTexture();
        if (texture == null) {
            return;
        }
        texture.setDefaultBufferSize(previewsize.getWidth(), previewsize.getHeight());
        Surface surface = new Surface(texture);
        try {
            previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (Exception e) {
            Toast.makeText(mContext, "Preview Builder Error:" + e.toString(), Toast.LENGTH_SHORT).show();
        }

        previewBuilder.addTarget(surface);
        try {
            cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    previewSession = session;
                    onChangedPreview();
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
                }
            }, null);
        } catch (Exception e) {
        }
    }

    private void onChangedPreview() {
        if (cameraDevice == null) {
            return;
        }
        previewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
        HandlerThread thread = new HandlerThread("On Change Preview");
        thread.start();

        Handler handler = new Handler(thread.getLooper());
        try {
            previewSession.setRepeatingRequest(previewBuilder.build(), null, handler);
        } catch (Exception e) {
        }
    }

    public void captureImage(OnImageCapture onCapture) {
        if (cameraDevice == null) {
            return;
        }
        CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        try {
            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraDevice.getId());

            Size[] jpegSizes = null;
            if (characteristics != null) {
                jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
            }
            int width = 640, height = 480;
            if (jpegSizes != null && jpegSizes.length > 0) {
                width = jpegSizes[0].getWidth();
                height = jpegSizes[0].getHeight();
            }

            ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
            List<Surface> outputSurfaces = new ArrayList<>(2);
            outputSurfaces.add(reader.getSurface());
            outputSurfaces.add(new Surface(textureView.getSurfaceTexture()));

            final CaptureRequest.Builder capturebuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            capturebuilder.addTarget(reader.getSurface());
            capturebuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

            int rotation = mContext.getWindowManager().getDefaultDisplay().getRotation();
            capturebuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));

            HandlerThread handlerThread = new HandlerThread("CaptureImage");
            handlerThread.start();

            final Handler handler = new Handler(handlerThread.getLooper());
            reader.setOnImageAvailableListener(new ImageSaver(reader, onCapture), handler);

            final CameraCaptureSession.CaptureCallback previewSSession = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) {
                    super.onCaptureStarted(session, request, timestamp, frameNumber);
                }

                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                    startCamera();
                }
            };
            cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    try {
                        session.capture(capturebuilder.build(), previewSSession, handler);
                    } catch (Exception e) {
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
                }
            }, handler);
        } catch (Exception e) {
        }
    }

    public class ImageSaver implements ImageReader.OnImageAvailableListener {
        private ImageReader reader;
        private OnImageCapture capture;

        public ImageSaver(ImageReader reader, OnImageCapture capture) {
            this.reader = reader;
            this.capture = capture;
        }

        @Override
        public void onImageAvailable(ImageReader imageReader) {
            File fileLocation = ImageUtils.saveImage(imageReader);
            capture.save(fileLocation);
        }
    }


    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        openCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }

    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        this.cameraDevice = cameraDevice;
        startCamera();
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {

    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int i) {

    }
}


Download: Camera2Ex.zip