From 6ad510cdd607fd07614381f6adb43b3a3c3f6963 Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Wed, 8 Jun 2016 13:19:34 +0900 Subject: [PATCH] Implementation on Camera2 API Change-Id: I57e10dca4b6b74460db1e1604c24676825742c24 --- .../android/cameraview/CameraViewImpl.java | 3 + .../android/cameraview/SurfaceInfo.java | 37 + .../google/android/cameraview/Camera1.java | 30 +- .../google/android/cameraview/Camera2.java | 778 ++++++++++++++++++ .../google/android/cameraview/CameraView.java | 8 +- 5 files changed, 834 insertions(+), 22 deletions(-) create mode 100644 library/src/main/base/com/google/android/cameraview/SurfaceInfo.java create mode 100644 library/src/main/camera2/com/google/android/cameraview/Camera2.java diff --git a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java index 03ed59de..9fd1e849 100644 --- a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java +++ b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java @@ -16,6 +16,7 @@ package com.google.android.cameraview; +import android.graphics.Matrix; import android.view.TextureView; import java.util.Set; @@ -66,6 +67,8 @@ interface Callback { void onPictureTaken(byte[] data); + void onTransformUpdated(Matrix matrix); + } } diff --git a/library/src/main/base/com/google/android/cameraview/SurfaceInfo.java b/library/src/main/base/com/google/android/cameraview/SurfaceInfo.java new file mode 100644 index 00000000..a14e60fe --- /dev/null +++ b/library/src/main/base/com/google/android/cameraview/SurfaceInfo.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.graphics.SurfaceTexture; + + +/** + * Stores information about the {@link SurfaceTexture} showing camera preview. + */ +class SurfaceInfo { + + SurfaceTexture surface; + int width; + int height; + + void configure(SurfaceTexture s, int w, int h) { + surface = s; + width = w; + height = h; + } + +} diff --git a/library/src/main/camera1/com/google/android/cameraview/Camera1.java b/library/src/main/camera1/com/google/android/cameraview/Camera1.java index 3902d0ff..02732635 100644 --- a/library/src/main/camera1/com/google/android/cameraview/Camera1.java +++ b/library/src/main/camera1/com/google/android/cameraview/Camera1.java @@ -49,7 +49,7 @@ class Camera1 extends CameraViewImpl { private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo(); - private final PreviewInfo mPreviewInfo = new PreviewInfo(); + private final SurfaceInfo mSurfaceInfo = new SurfaceInfo(); private final SizeMap mPreviewSizes = new SizeMap(); @@ -67,23 +67,11 @@ class Camera1 extends CameraViewImpl { private int mDisplayOrientation; - private static class PreviewInfo { - SurfaceTexture surface; - int width; - int height; - - void configure(SurfaceTexture s, int w, int h) { - surface = s; - width = w; - height = h; - } - } - private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { private void reconfigurePreview(SurfaceTexture surface, int width, int height) { - mPreviewInfo.configure(surface, width, height); + mSurfaceInfo.configure(surface, width, height); if (mCamera != null) { setUpPreview(); adjustCameraParameters(); @@ -124,7 +112,7 @@ TextureView.SurfaceTextureListener getSurfaceTextureListener() { void start() { chooseCamera(); openCamera(); - if (mPreviewInfo.surface != null) { + if (mSurfaceInfo.surface != null) { setUpPreview(); } mShowingPreview = true; @@ -142,7 +130,7 @@ void stop() { private void setUpPreview() { try { - mCamera.setPreviewTexture(mPreviewInfo.surface); + mCamera.setPreviewTexture(mSurfaceInfo.surface); } catch (IOException e) { throw new RuntimeException(e); } @@ -346,17 +334,17 @@ private void adjustCameraParameters() { @SuppressWarnings("SuspiciousNameCombination") private Size chooseOptimalSize(SortedSet sizes) { - if (mPreviewInfo.width == 0 || mPreviewInfo.height == 0) { // Not yet laid out + if (mSurfaceInfo.width == 0 || mSurfaceInfo.height == 0) { // Not yet laid out return sizes.first(); // Return the smallest size } int desiredWidth; int desiredHeight; if (mDisplayOrientation == 90 || mDisplayOrientation == 270) { - desiredWidth = mPreviewInfo.height; - desiredHeight = mPreviewInfo.width; + desiredWidth = mSurfaceInfo.height; + desiredHeight = mSurfaceInfo.width; } else { - desiredWidth = mPreviewInfo.width; - desiredHeight = mPreviewInfo.height; + desiredWidth = mSurfaceInfo.width; + desiredHeight = mSurfaceInfo.height; } Size result = null; for (Size size : sizes) { // Iterate from small to large diff --git a/library/src/main/camera2/com/google/android/cameraview/Camera2.java b/library/src/main/camera2/com/google/android/cameraview/Camera2.java new file mode 100644 index 00000000..6804dcda --- /dev/null +++ b/library/src/main/camera2/com/google/android/cameraview/Camera2.java @@ -0,0 +1,778 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.cameraview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.SurfaceTexture; +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.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.Image; +import android.media.ImageReader; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.Log; +import android.util.SparseIntArray; +import android.view.Surface; +import android.view.TextureView; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Set; +import java.util.SortedSet; + + +@SuppressWarnings("MissingPermission") +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +class Camera2 extends CameraViewImpl { + + private static final String TAG = "Camera2"; + + private static final SparseIntArray INTERNAL_FACINGS = new SparseIntArray(); + + static { + INTERNAL_FACINGS.put(Constants.FACING_BACK, CameraCharacteristics.LENS_FACING_BACK); + INTERNAL_FACINGS.put(Constants.FACING_FRONT, CameraCharacteristics.LENS_FACING_FRONT); + } + + private final CameraManager mCameraManager; + + private final TextureView.SurfaceTextureListener mSurfaceTextureListener + = new TextureView.SurfaceTextureListener() { + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + mSurfaceInfo.configure(surface, width, height); + configureTransform(); + startCaptureSession(); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + mSurfaceInfo.configure(surface, width, height); + configureTransform(); + startCaptureSession(); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + mSurfaceInfo.configure(null, 0, 0); + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } + + }; + + private final CameraDevice.StateCallback mCameraDeviceCallback + = new CameraDevice.StateCallback() { + + @Override + public void onOpened(@NonNull CameraDevice camera) { + mCamera = camera; + mCallback.onCameraOpened(); + startCaptureSession(); + } + + @Override + public void onClosed(@NonNull CameraDevice camera) { + mCallback.onCameraClosed(); + } + + @Override + public void onDisconnected(@NonNull CameraDevice camera) { + mCamera = null; + } + + @Override + public void onError(@NonNull CameraDevice camera, int error) { + Log.e(TAG, "onError: " + camera.getId() + " (" + error + ")"); + mCamera = null; + } + + }; + + private final CameraCaptureSession.StateCallback mSessionCallback + = new CameraCaptureSession.StateCallback() { + + @Override + public void onConfigured(@NonNull CameraCaptureSession session) { + if (mCamera == null) { + return; + } + mCaptureSession = session; + updateAutoFocus(); + updateFlash(); + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to start camera preview.", e); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession session) { + Log.e(TAG, "Failed to configure capture session."); + } + + @Override + public void onClosed(@NonNull CameraCaptureSession session) { + mCaptureSession = null; + } + + }; + + private PictureCaptureCallback mCaptureCallback = new PictureCaptureCallback() { + + @Override + public void onPrecaptureRequired() { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + setState(STATE_PRECAPTURE); + try { + mCaptureSession.capture(mPreviewRequestBuilder.build(), this, null); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, + CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to run precapture sequence.", e); + } + } + + @Override + public void onReady() { + captureStillPicture(); + } + + }; + + private final ImageReader.OnImageAvailableListener mOnImageAvailableListener + = new ImageReader.OnImageAvailableListener() { + + @Override + public void onImageAvailable(ImageReader reader) { + try (Image image = reader.acquireNextImage()) { + Image.Plane[] planes = image.getPlanes(); + if (planes.length > 0) { + ByteBuffer buffer = planes[0].getBuffer(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + mCallback.onPictureTaken(data); + } + } + } + + }; + + + private String mCameraId; + + private CameraCharacteristics mCameraCharacteristics; + + private CameraDevice mCamera; + + private CameraCaptureSession mCaptureSession; + + private CaptureRequest.Builder mPreviewRequestBuilder; + + private ImageReader mImageReader; + + private final SurfaceInfo mSurfaceInfo = new SurfaceInfo(); + + private final SizeMap mPreviewSizes = new SizeMap(); + + private final SizeMap mPictureSizes = new SizeMap(); + + private int mFacing; + + private AspectRatio mAspectRatio = Constants.DEFAULT_ASPECT_RATIO; + + private boolean mAutoFocus; + + private int mFlash; + + private int mDisplayOrientation; + + public Camera2(Callback callback, Context context) { + super(callback); + mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); + } + + @Override + TextureView.SurfaceTextureListener getSurfaceTextureListener() { + return mSurfaceTextureListener; + } + + @Override + void start() { + chooseCameraIdByFacing(); + collectCameraInfo(); + prepareImageReader(); + startOpeningCamera(); + } + + @Override + void stop() { + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + if (mCamera != null) { + mCamera.close(); + mCamera = null; + } + if (mImageReader != null) { + mImageReader.close(); + mImageReader = null; + } + } + + @Override + boolean isCameraOpened() { + return mCamera != null; + } + + @Override + void setFacing(int facing) { + if (mFacing == facing) { + return; + } + mFacing = facing; + if (isCameraOpened()) { + stop(); + start(); + } + } + + @Override + int getFacing() { + return mFacing; + } + + @Override + Set getSupportedAspectRatios() { + return mPreviewSizes.ratios(); + } + + @Override + void setAspectRatio(AspectRatio ratio) { + if (ratio == null || ratio.equals(mAspectRatio) || + !mPreviewSizes.ratios().contains(ratio)) { + // TODO: Better error handling + return; + } + mAspectRatio = ratio; + if (mCaptureSession != null) { + mCaptureSession.close(); + startCaptureSession(); + } + } + + @Override + AspectRatio getAspectRatio() { + return mAspectRatio; + } + + @Override + void setAutoFocus(boolean autoFocus) { + if (mAutoFocus == autoFocus) { + return; + } + mAutoFocus = autoFocus; + if (mPreviewRequestBuilder != null) { + updateAutoFocus(); + if (mCaptureSession != null) { + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mAutoFocus = !mAutoFocus; // Revert + } + } + } + } + + @Override + boolean getAutoFocus() { + return mAutoFocus; + } + + @Override + void setFlash(int flash) { + if (mFlash == flash) { + return; + } + int saved = mFlash; + mFlash = flash; + if (mPreviewRequestBuilder != null) { + updateFlash(); + if (mCaptureSession != null) { + try { + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), + mCaptureCallback, null); + } catch (CameraAccessException e) { + mFlash = saved; // Revert + } + } + } + } + + @Override + int getFlash() { + return mFlash; + } + + @Override + void takePicture() { + if (mAutoFocus) { + lockFocus(); + } else { + captureStillPicture(); + } + } + + @Override + void setDisplayOrientation(int displayOrientation) { + mDisplayOrientation = displayOrientation; + configureTransform(); + } + + /** + * Chooses a camera ID by the specified camera facing ({@link #mFacing}). + * + *

This rewrites {@link #mCameraId}, {@link #mCameraCharacteristics}, and optionally + * {@link #mFacing}.

+ */ + private void chooseCameraIdByFacing() { + try { + int internalFacing = INTERNAL_FACINGS.get(mFacing); + final String[] ids = mCameraManager.getCameraIdList(); + for (String id : ids) { + CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id); + Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING); + if (internal == null) { + throw new NullPointerException("Unexpected state: LENS_FACING null"); + } + if (internal == internalFacing) { + mCameraId = id; + mCameraCharacteristics = characteristics; + return; + } + } + // Not found + mCameraId = ids[0]; + mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); + Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING); + if (internal == null) { + throw new NullPointerException("Unexpected state: LENS_FACING null"); + } + for (int i = 0, count = INTERNAL_FACINGS.size(); i < count; i++) { + if (INTERNAL_FACINGS.valueAt(i) == internal) { + mFacing = INTERNAL_FACINGS.keyAt(i); + return; + } + } + // The operation can reach here when the only camera device is an external one. + // We treat it as facing back. + mFacing = Constants.FACING_BACK; + } catch (CameraAccessException e) { + throw new RuntimeException("Failed to get a list of camera devices", e); + } + } + + /** + * Collects some information from {@link #mCameraCharacteristics}. + * + *

This rewrites {@link #mPreviewSizes}, {@link #mPictureSizes}, and optionally, + * {@link #mAspectRatio}.

+ */ + private void collectCameraInfo() { + StreamConfigurationMap map = mCameraCharacteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + if (map == null) { + throw new IllegalStateException("Failed to get configuration map: " + mCameraId); + } + mPreviewSizes.clear(); + for (android.util.Size size : map.getOutputSizes(SurfaceTexture.class)) { + mPreviewSizes.add(new Size(size.getWidth(), size.getHeight())); + } + mPictureSizes.clear(); + for (android.util.Size size : map.getOutputSizes(ImageFormat.JPEG)) { + mPictureSizes.add(new Size(size.getWidth(), size.getHeight())); + } + if (!mPreviewSizes.ratios().contains(mAspectRatio)) { + mAspectRatio = mPreviewSizes.ratios().iterator().next(); + } + } + + private void prepareImageReader() { + Size largest = mPictureSizes.sizes(mAspectRatio).last(); + mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.JPEG, /* maxImages */ 2); + mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); + } + + /** + * Starts opening a camera device. + * + *

The result will be processed in {@link #mCameraDeviceCallback}.

+ */ + private void startOpeningCamera() { + try { + mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null); + } catch (CameraAccessException e) { + throw new RuntimeException("Failed to open camera: " + mCameraId, e); + } + } + + /** + * Starts a capture session for camera preview. + * + *

This rewrites {@link #mPreviewRequestBuilder}.

+ * + *

The result will be continuously processed in {@link #mSessionCallback}.

+ */ + private void startCaptureSession() { + if (!isCameraOpened() || mSurfaceInfo.surface == null) { + return; + } + Size previewSize = chooseOptimalSize(); + mSurfaceInfo.surface.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + Surface surface = new Surface(mSurfaceInfo.surface); + try { + mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + mPreviewRequestBuilder.addTarget(surface); + mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), + mSessionCallback, null); + } catch (CameraAccessException e) { + throw new RuntimeException("Failed to start camera session"); + } + } + + /** + * Chooses the optimal preview size based on {@link #mPreviewSizes} and {@link #mSurfaceInfo}. + * + * @return The picked size for camera preview. + */ + private Size chooseOptimalSize() { + int surfaceLonger, surfaceShorter; + if (mSurfaceInfo.width < mSurfaceInfo.height) { + surfaceLonger = mSurfaceInfo.height; + surfaceShorter = mSurfaceInfo.width; + } else { + surfaceLonger = mSurfaceInfo.width; + surfaceShorter = mSurfaceInfo.height; + } + SortedSet candidates = mPreviewSizes.sizes(mAspectRatio); + // Pick the smallest of those big enough. + for (Size size : candidates) { + if (size.getWidth() >= surfaceLonger && size.getHeight() >= surfaceShorter) { + return size; + } + } + // If no size is big enough, pick the largest one. + return candidates.last(); + } + + /** + * Configures the transform matrix for TextureView based on {@link #mDisplayOrientation} and + * {@link #mSurfaceInfo}. + */ + private void configureTransform() { + Matrix matrix = new Matrix(); + if (mDisplayOrientation % 180 == 90) { + // Rotate the camera preview when the screen is landscape. + matrix.setPolyToPoly( + new float[]{ + 0.f, 0.f, // top left + mSurfaceInfo.width, 0.f, // top right + 0.f, mSurfaceInfo.height, // bottom left + mSurfaceInfo.width, mSurfaceInfo.height, // bottom right + }, 0, + mDisplayOrientation == 90 ? + // Clockwise + new float[]{ + 0.f, mSurfaceInfo.height, // top left + 0.f, 0.f, // top right + mSurfaceInfo.width, mSurfaceInfo.height, // bottom left + mSurfaceInfo.width, 0.f, // bottom right + } + : // mDisplayOrientation == 270 + // Counter-clockwise + new float[]{ + mSurfaceInfo.width, 0.f, // top left + mSurfaceInfo.width, mSurfaceInfo.height, // top right + 0.f, 0.f, // bottom left + 0.f, mSurfaceInfo.height, // bottom right + }, 0, + 4); + } + mCallback.onTransformUpdated(matrix); + } + + /** + * Updates the internal state of auto-focus to {@link #mAutoFocus}. + */ + private void updateAutoFocus() { + if (mAutoFocus) { + int[] modes = mCameraCharacteristics.get( + CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); + // Auto focus is not supported + if (modes == null || modes.length == 0 || + (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) { + mAutoFocus = false; + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF); + } else { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + } + } else { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_OFF); + } + } + + /** + * Updates the internal state of flash to {@link #mFlash}. + */ + private void updateFlash() { + switch (mFlash) { + case Constants.FLASH_OFF: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_ON: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_TORCH: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_TORCH); + break; + case Constants.FLASH_AUTO: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_RED_EYE: + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE); + mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + } + } + + /** + * Locks the focus as the first step for a still image capture. + */ + private void lockFocus() { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_START); + try { + mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING); + mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to lock focus.", e); + } + } + + /** + * Captures a still picture. + */ + private void captureStillPicture() { + try { + CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest( + CameraDevice.TEMPLATE_STILL_CAPTURE); + captureRequestBuilder.addTarget(mImageReader.getSurface()); + captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, + mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE)); + switch (mFlash) { + case Constants.FLASH_OFF: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_OFF); + break; + case Constants.FLASH_ON: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); + break; + case Constants.FLASH_TORCH: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON); + captureRequestBuilder.set(CaptureRequest.FLASH_MODE, + CaptureRequest.FLASH_MODE_TORCH); + break; + case Constants.FLASH_AUTO: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + case Constants.FLASH_RED_EYE: + captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, + CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); + break; + } + // Calculate JPEG orientation. + @SuppressWarnings("ConstantConditions") + int sensorOrientation = mCameraCharacteristics.get( + CameraCharacteristics.SENSOR_ORIENTATION); + captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, + (sensorOrientation + + mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) + + 360) % 360); + // Stop preview and capture a still picture. + mCaptureSession.stopRepeating(); + mCaptureSession.capture(captureRequestBuilder.build(), + new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + unlockFocus(); + } + }, null); + } catch (CameraAccessException e) { + Log.e(TAG, "Cannot capture a still picture.", e); + } + } + + /** + * Unlocks the auto-focus and restart camera preview. This is supposed to be called after + * capturing a still picture. + */ + private void unlockFocus() { + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); + try { + mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null); + updateAutoFocus(); + updateFlash(); + mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, + CaptureRequest.CONTROL_AF_TRIGGER_IDLE); + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, + null); + mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to restart camera preview.", e); + } + } + + /** + * A {@link CameraCaptureSession.CaptureCallback} for capturing a still picture. + */ + private static abstract class PictureCaptureCallback + extends CameraCaptureSession.CaptureCallback { + + public static final int STATE_PREVIEW = 0; + public static final int STATE_LOCKING = 1; + public static final int STATE_LOCKED = 2; + public static final int STATE_PRECAPTURE = 3; + public static final int STATE_WAITING = 4; + public static final int STATE_CAPTURING = 5; + + private int mState; + + public void setState(int state) { + mState = state; + } + + @Override + public void onCaptureProgressed(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull CaptureResult partialResult) { + process(partialResult); + } + + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, + @NonNull CaptureRequest request, + @NonNull TotalCaptureResult result) { + process(result); + } + + private void process(@NonNull CaptureResult result) { + switch (mState) { + case STATE_LOCKING: { + Integer af = result.get(CaptureResult.CONTROL_AF_STATE); + if (af == null) { + break; + } + if (af == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || + af == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); + if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + setState(STATE_CAPTURING); + onReady(); + } else { + setState(STATE_LOCKED); + onPrecaptureRequired(); + } + } + break; + } + case STATE_PRECAPTURE: { + Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); + if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || + ae == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { + setState(STATE_WAITING); + } + break; + } + case STATE_WAITING: { + Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); + if (ae == null || ae != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + setState(STATE_CAPTURING); + onReady(); + } + break; + } + } + } + + /** + * Called when it is ready to take a still picture. + */ + public abstract void onReady(); + + /** + * Called when it is necessary to run the precapture sequence. + */ + public abstract void onPrecaptureRequired(); + + } + +} diff --git a/library/src/main/java/com/google/android/cameraview/CameraView.java b/library/src/main/java/com/google/android/cameraview/CameraView.java index 29cef689..6cdebf29 100644 --- a/library/src/main/java/com/google/android/cameraview/CameraView.java +++ b/library/src/main/java/com/google/android/cameraview/CameraView.java @@ -19,6 +19,7 @@ import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Matrix; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -96,7 +97,7 @@ public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { if (Build.VERSION.SDK_INT < 21) { mImpl = new Camera1(mCallbacks); } else { - mImpl = new Camera1(mCallbacks); // TODO: Implement Camera2 and replace this + mImpl = new Camera2(mCallbacks, context); } // View content inflate(context, R.layout.camera_view, this); @@ -416,6 +417,11 @@ public void onPictureTaken(byte[] data) { } } + @Override + public void onTransformUpdated(Matrix matrix) { + mTextureView.setTransform(matrix); + } + public void reserveRequestLayoutOnOpen() { mRequestLayoutOnOpen = true; }