--- /dev/null
+package org.opencv.android;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.ViewGroup.LayoutParams;
+
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+import org.opencv.imgproc.Imgproc;
+
+/**
+ * This class is an implementation of the Bridge View between OpenCV and Java Camera.
+ * This class relays on the functionality available in base class and only implements
+ * required functions:
+ * connectCamera - opens Java camera and sets the PreviewCallback to be delivered.
+ * disconnectCamera - closes the camera and stops preview.
+ * When frame is delivered via callback from Camera - it processed via OpenCV to be
+ * converted to RGBA32 and then passed to the external callback for modifications if required.
+ */
+
+@TargetApi(21)
+public class JavaCamera2View extends CameraBridgeViewBase {
+
+ private static final String LOGTAG = "JavaCamera2View";
+
+ private ImageReader mImageReader;
+ private int mPreviewFormat = ImageFormat.YUV_420_888;
+
+ private CameraDevice mCameraDevice;
+ private CameraCaptureSession mCaptureSession;
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+ private String mCameraID;
+ private android.util.Size mPreviewSize = new android.util.Size(-1, -1);
+
+ private HandlerThread mBackgroundThread;
+ private Handler mBackgroundHandler;
+
+ public JavaCamera2View(Context context, int cameraId) {
+ super(context, cameraId);
+ }
+
+ public JavaCamera2View(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void startBackgroundThread() {
+ Log.i(LOGTAG, "startBackgroundThread");
+ stopBackgroundThread();
+ mBackgroundThread = new HandlerThread("OpenCVCameraBackground");
+ mBackgroundThread.start();
+ mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+ }
+
+ private void stopBackgroundThread() {
+ Log.i(LOGTAG, "stopBackgroundThread");
+ if (mBackgroundThread == null)
+ return;
+ mBackgroundThread.quitSafely();
+ try {
+ mBackgroundThread.join();
+ mBackgroundThread = null;
+ mBackgroundHandler = null;
+ } catch (InterruptedException e) {
+ Log.e(LOGTAG, "stopBackgroundThread", e);
+ }
+ }
+
+ protected boolean initializeCamera() {
+ Log.i(LOGTAG, "initializeCamera");
+ CameraManager manager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
+ try {
+ String camList[] = manager.getCameraIdList();
+ if (camList.length == 0) {
+ Log.e(LOGTAG, "Error: camera isn't detected.");
+ return false;
+ }
+ if (mCameraIndex == CameraBridgeViewBase.CAMERA_ID_ANY) {
+ mCameraID = camList[0];
+ } else {
+ for (String cameraID : camList) {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraID);
+ if ((mCameraIndex == CameraBridgeViewBase.CAMERA_ID_BACK &&
+ characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) ||
+ (mCameraIndex == CameraBridgeViewBase.CAMERA_ID_FRONT &&
+ characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
+ ) {
+ mCameraID = cameraID;
+ break;
+ }
+ }
+ }
+ if (mCameraID != null) {
+ Log.i(LOGTAG, "Opening camera: " + mCameraID);
+ manager.openCamera(mCameraID, mStateCallback, mBackgroundHandler);
+ }
+ return true;
+ } catch (CameraAccessException e) {
+ Log.e(LOGTAG, "OpenCamera - Camera Access Exception", e);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOGTAG, "OpenCamera - Illegal Argument Exception", e);
+ } catch (SecurityException e) {
+ Log.e(LOGTAG, "OpenCamera - Security Exception", e);
+ }
+ return false;
+ }
+
+ private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
+
+ @Override
+ public void onOpened(CameraDevice cameraDevice) {
+ mCameraDevice = cameraDevice;
+ createCameraPreviewSession();
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice cameraDevice) {
+ cameraDevice.close();
+ mCameraDevice = null;
+ }
+
+ @Override
+ public void onError(CameraDevice cameraDevice, int error) {
+ cameraDevice.close();
+ mCameraDevice = null;
+ }
+
+ };
+
+ private void createCameraPreviewSession() {
+ final int w = mPreviewSize.getWidth(), h = mPreviewSize.getHeight();
+ Log.i(LOGTAG, "createCameraPreviewSession(" + w + "x" + h + ")");
+ if (w < 0 || h < 0)
+ return;
+ try {
+ if (null == mCameraDevice) {
+ Log.e(LOGTAG, "createCameraPreviewSession: camera isn't opened");
+ return;
+ }
+ if (null != mCaptureSession) {
+ Log.e(LOGTAG, "createCameraPreviewSession: mCaptureSession is already started");
+ return;
+ }
+
+ mImageReader = ImageReader.newInstance(w, h, mPreviewFormat, 2);
+ mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image image = reader.acquireLatestImage();
+ if (image == null)
+ return;
+
+ // sanity checks - 3 planes
+ Image.Plane[] planes = image.getPlanes();
+ assert (planes.length == 3);
+ assert (image.getFormat() == mPreviewFormat);
+
+ // see also https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888
+ // Y plane (0) non-interleaved => stride == 1; U/V plane interleaved => stride == 2
+ assert (planes[0].getPixelStride() == 1);
+ assert (planes[1].getPixelStride() == 2);
+ assert (planes[2].getPixelStride() == 2);
+
+ ByteBuffer y_plane = planes[0].getBuffer();
+ ByteBuffer uv_plane = planes[1].getBuffer();
+ Mat y_mat = new Mat(h, w, CvType.CV_8UC1, y_plane);
+ Mat uv_mat = new Mat(h / 2, w / 2, CvType.CV_8UC2, uv_plane);
+ JavaCamera2Frame tempFrame = new JavaCamera2Frame(y_mat, uv_mat, w, h);
+ deliverAndDrawFrame(tempFrame);
+ tempFrame.release();
+ image.close();
+ }
+ }, mBackgroundHandler);
+ Surface surface = mImageReader.getSurface();
+
+ mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mPreviewRequestBuilder.addTarget(surface);
+
+ mCameraDevice.createCaptureSession(Arrays.asList(surface),
+ new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ Log.i(LOGTAG, "createCaptureSession::onConfigured");
+ if (null == mCameraDevice) {
+ return; // camera is already closed
+ }
+ mCaptureSession = cameraCaptureSession;
+ try {
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+
+ mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler);
+ Log.i(LOGTAG, "CameraPreviewSession has been started");
+ } catch (Exception e) {
+ Log.e(LOGTAG, "createCaptureSession failed", e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
+ Log.e(LOGTAG, "createCameraPreviewSession failed");
+ }
+ },
+ null
+ );
+ } catch (CameraAccessException e) {
+ Log.e(LOGTAG, "createCameraPreviewSession", e);
+ }
+ }
+
+ @Override
+ protected void disconnectCamera() {
+ Log.i(LOGTAG, "closeCamera");
+ try {
+ CameraDevice c = mCameraDevice;
+ mCameraDevice = null;
+ if (null != mCaptureSession) {
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ if (null != c) {
+ c.close();
+ }
+ if (null != mImageReader) {
+ mImageReader.close();
+ mImageReader = null;
+ }
+ } finally {
+ stopBackgroundThread();
+ }
+ }
+
+ boolean calcPreviewSize(final int width, final int height) {
+ Log.i(LOGTAG, "calcPreviewSize: " + width + "x" + height);
+ if (mCameraID == null) {
+ Log.e(LOGTAG, "Camera isn't initialized!");
+ return false;
+ }
+ CameraManager manager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
+ try {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
+ StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ int bestWidth = 0, bestHeight = 0;
+ float aspect = (float) width / height;
+ android.util.Size[] sizes = map.getOutputSizes(ImageReader.class);
+ bestWidth = sizes[0].getWidth();
+ bestHeight = sizes[0].getHeight();
+ for (android.util.Size sz : sizes) {
+ int w = sz.getWidth(), h = sz.getHeight();
+ Log.d(LOGTAG, "trying size: " + w + "x" + h);
+ if (width >= w && height >= h && bestWidth <= w && bestHeight <= h
+ && Math.abs(aspect - (float) w / h) < 0.2) {
+ bestWidth = w;
+ bestHeight = h;
+ }
+ }
+ Log.i(LOGTAG, "best size: " + bestWidth + "x" + bestHeight);
+ assert(!(bestWidth == 0 || bestHeight == 0));
+ if (mPreviewSize.getWidth() == bestWidth && mPreviewSize.getHeight() == bestHeight)
+ return false;
+ else {
+ mPreviewSize = new android.util.Size(bestWidth, bestHeight);
+ return true;
+ }
+ } catch (CameraAccessException e) {
+ Log.e(LOGTAG, "calcPreviewSize - Camera Access Exception", e);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOGTAG, "calcPreviewSize - Illegal Argument Exception", e);
+ } catch (SecurityException e) {
+ Log.e(LOGTAG, "calcPreviewSize - Security Exception", e);
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean connectCamera(int width, int height) {
+ Log.i(LOGTAG, "setCameraPreviewSize(" + width + "x" + height + ")");
+ startBackgroundThread();
+ initializeCamera();
+ try {
+ boolean needReconfig = calcPreviewSize(width, height);
+ mFrameWidth = mPreviewSize.getWidth();
+ mFrameHeight = mPreviewSize.getHeight();
+
+ if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT))
+ mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);
+ else
+ mScale = 0;
+
+ AllocateCache();
+
+ if (needReconfig) {
+ if (null != mCaptureSession) {
+ Log.d(LOGTAG, "closing existing previewSession");
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ createCameraPreviewSession();
+ }
+ } catch (RuntimeException e) {
+ throw new RuntimeException("Interrupted while setCameraPreviewSize.", e);
+ }
+ return true;
+ }
+
+ private class JavaCamera2Frame implements CvCameraViewFrame {
+ @Override
+ public Mat gray() {
+ return mYuvFrameData.submat(0, mHeight, 0, mWidth);
+ }
+
+ @Override
+ public Mat rgba() {
+ if (mPreviewFormat == ImageFormat.NV21)
+ Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGBA_NV21, 4);
+ else if (mPreviewFormat == ImageFormat.YV12)
+ Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGB_I420, 4); // COLOR_YUV2RGBA_YV12 produces inverted colors
+ else if (mPreviewFormat == ImageFormat.YUV_420_888) {
+ assert (mUVFrameData != null);
+ Imgproc.cvtColorTwoPlane(mYuvFrameData, mUVFrameData, mRgba, Imgproc.COLOR_YUV2RGBA_NV21);
+ } else
+ throw new IllegalArgumentException("Preview Format can be NV21 or YV12");
+
+ return mRgba;
+ }
+
+ public JavaCamera2Frame(Mat Yuv420sp, int width, int height) {
+ super();
+ mWidth = width;
+ mHeight = height;
+ mYuvFrameData = Yuv420sp;
+ mUVFrameData = null;
+ mRgba = new Mat();
+ }
+
+ public JavaCamera2Frame(Mat Y, Mat UV, int width, int height) {
+ super();
+ mWidth = width;
+ mHeight = height;
+ mYuvFrameData = Y;
+ mUVFrameData = UV;
+ mRgba = new Mat();
+ }
+
+ public void release() {
+ mRgba.release();
+ }
+
+ private Mat mYuvFrameData;
+ private Mat mUVFrameData;
+ private Mat mRgba;
+ private int mWidth;
+ private int mHeight;
+ };
+}