-
-
Save aalices/1f88844762150b36aa4b3790fdc56b91 to your computer and use it in GitHub Desktop.
CameraScanner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.google.android.cameraview; | |
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.graphics.ImageFormat; | |
import android.graphics.Rect; | |
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.CamcorderProfile; | |
import android.media.Image; | |
import android.media.ImageReader; | |
import android.media.MediaRecorder; | |
import android.support.annotation.NonNull; | |
import android.util.Log; | |
import android.util.SparseIntArray; | |
import android.view.Surface; | |
import java.io.File; | |
import java.io.IOException; | |
import java.nio.ByteBuffer; | |
import java.util.Arrays; | |
import java.util.Set; | |
import java.util.SortedSet; | |
@SuppressWarnings("MissingPermission") | |
@TargetApi(21) | |
class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener { | |
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); | |
} | |
/** | |
* Max preview width that is guaranteed by Camera2 API | |
*/ | |
private static final int MAX_PREVIEW_WIDTH = 1920; | |
/** | |
* Max preview height that is guaranteed by Camera2 API | |
*/ | |
private static final int MAX_PREVIEW_HEIGHT = 1080; | |
private final CameraManager mCameraManager; | |
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; | |
try { | |
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), | |
mCaptureCallback, null); | |
} catch (CameraAccessException e) { | |
Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e); | |
} catch (IllegalStateException 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) { | |
if (mCaptureSession != null && mCaptureSession.equals(session)) { | |
mCaptureSession = null; | |
} | |
} | |
}; | |
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); | |
if (image.getFormat() == ImageFormat.JPEG) { | |
// some callback | |
} else { | |
// some callback | |
} | |
image.close(); | |
} | |
} | |
} | |
}; | |
private String mCameraId; | |
private CameraCharacteristics mCameraCharacteristics; | |
CameraDevice mCamera; | |
CameraCaptureSession mCaptureSession; | |
CaptureRequest.Builder mPreviewRequestBuilder; | |
private ImageReader mStillImageReader; | |
private ImageReader mScanImageReader; | |
private int mImageFormat; | |
private MediaRecorder mMediaRecorder; | |
private String mVideoPath; | |
private boolean mIsRecording; | |
private final SizeMap mPreviewSizes = new SizeMap(); | |
private final SizeMap mPictureSizes = new SizeMap(); | |
private int mFacing; | |
private AspectRatio mAspectRatio = Constants.DEFAULT_ASPECT_RATIO; | |
private AspectRatio mInitialRatio; | |
private boolean mAutoFocus; | |
private int mFlash; | |
private int mDisplayOrientation; | |
Camera2(Callback callback, PreviewImpl preview, Context context) { | |
super(callback, preview); | |
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); | |
mImageFormat = mIsScanning ? ImageFormat.YUV_420_888 : ImageFormat.JPEG; | |
mPreview.setCallback(new PreviewImpl.Callback() { | |
@Override | |
public void onSurfaceChanged() { | |
startCaptureSession(); | |
} | |
@Override | |
public void onSurfaceDestroyed() { | |
stop(); | |
} | |
}); | |
} | |
@Override | |
boolean start() { | |
if (!chooseCameraIdByFacing()) { | |
mAspectRatio = mInitialRatio; | |
return false; | |
} | |
collectCameraInfo(); | |
setAspectRatio(mInitialRatio); | |
mInitialRatio = null; | |
mStillImageReader = prepareImageReader(mStillImageReader, ImageFormat.JPEG); | |
mScanImageReader = prepareImageReader(mScanImageReader, ImageFormat.YUV_420_888); | |
startOpeningCamera(); | |
return true; | |
} | |
@Override | |
void stop() { | |
if (mCaptureSession != null) { | |
mCaptureSession.close(); | |
mCaptureSession = null; | |
} | |
if (mCamera != null) { | |
mCamera.close(); | |
mCamera = null; | |
} | |
if (mStillImageReader != null) { | |
mStillImageReader.close(); | |
mStillImageReader = null; | |
} | |
if (mScanImageReader != null) { | |
mScanImageReader.close(); | |
mScanImageReader = 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<AspectRatio> getSupportedAspectRatios() { | |
return mPreviewSizes.ratios(); | |
} | |
@Override | |
boolean setAspectRatio(AspectRatio ratio) { | |
if (ratio != null && mPreviewSizes.isEmpty()) { | |
mInitialRatio = ratio; | |
return false; | |
} | |
if (ratio == null || ratio.equals(mAspectRatio) || | |
!mPreviewSizes.ratios().contains(ratio)) { | |
// TODO: Better error handling | |
return false; | |
} | |
mAspectRatio = ratio; | |
prepareImageReader(mStillImageReader, ImageFormat.JPEG); | |
prepareImageReader(mScanImageReader, ImageFormat.YUV_420_888); | |
if (mCaptureSession != null) { | |
mCaptureSession.close(); | |
mCaptureSession = null; | |
startCaptureSession(); | |
} | |
return true; | |
} | |
@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 setScanning(boolean isScanning) { | |
if (mIsScanning == isScanning) { | |
return; | |
} | |
mIsScanning = isScanning; | |
if (!mIsScanning) { | |
mImageFormat = ImageFormat.JPEG; | |
} else { | |
mImageFormat = ImageFormat.YUV_420_888; | |
} | |
if (mCaptureSession != null) { | |
mCaptureSession.close(); | |
mCaptureSession = null; | |
} | |
startCaptureSession(); | |
} | |
@Override | |
boolean getScanning() { | |
return mIsScanning; | |
} | |
@Override | |
void setDisplayOrientation(int displayOrientation) { | |
mDisplayOrientation = displayOrientation; | |
mPreview.setDisplayOrientation(mDisplayOrientation); | |
} | |
/** | |
* <p>Chooses a camera ID by the specified camera facing ({@link #mFacing}).</p> | |
* <p>This rewrites {@link #mCameraId}, {@link #mCameraCharacteristics}, and optionally | |
* {@link #mFacing}.</p> | |
*/ | |
private boolean chooseCameraIdByFacing() { | |
try { | |
int internalFacing = INTERNAL_FACINGS.get(mFacing); | |
final String[] ids = mCameraManager.getCameraIdList(); | |
if (ids.length == 0) { // No camera | |
throw new RuntimeException("No camera available."); | |
} | |
for (String id : ids) { | |
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id); | |
Integer level = characteristics.get( | |
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); | |
if (level == null || | |
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { | |
continue; | |
} | |
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 true; | |
} | |
} | |
// Not found | |
mCameraId = ids[0]; | |
mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); | |
Integer level = mCameraCharacteristics.get( | |
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); | |
if (level == null || | |
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { | |
return false; | |
} | |
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 true; | |
} | |
} | |
// The operation can reach here when the only camera device is an external one. | |
// We treat it as facing back. | |
mFacing = Constants.FACING_BACK; | |
return true; | |
} catch (CameraAccessException e) { | |
throw new RuntimeException("Failed to get a list of camera devices", e); | |
} | |
} | |
/** | |
* <p>Collects some information from {@link #mCameraCharacteristics}.</p> | |
* <p>This rewrites {@link #mPreviewSizes}, {@link #mPictureSizes}, and optionally, | |
* {@link #mAspectRatio}.</p> | |
*/ | |
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(mPreview.getOutputClass())) { | |
int width = size.getWidth(); | |
int height = size.getHeight(); | |
if (width <= MAX_PREVIEW_WIDTH && height <= MAX_PREVIEW_HEIGHT) { | |
mPreviewSizes.add(new Size(width, height)); | |
} | |
} | |
mPictureSizes.clear(); | |
collectPictureSizes(mPictureSizes, map); | |
for (AspectRatio ratio : mPreviewSizes.ratios()) { | |
if (!mPictureSizes.ratios().contains(ratio)) { | |
mPreviewSizes.remove(ratio); | |
} | |
} | |
if (!mPreviewSizes.ratios().contains(mAspectRatio)) { | |
mAspectRatio = mPreviewSizes.ratios().iterator().next(); | |
} | |
} | |
protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) { | |
for (android.util.Size size : map.getOutputSizes(mImageFormat)) { | |
mPictureSizes.add(new Size(size.getWidth(), size.getHeight())); | |
} | |
} | |
private ImageReader prepareImageReader(ImageReader reader, int imageFormat) { | |
if (reader != null) { | |
reader.close(); | |
} | |
Size largest = mPictureSizes.sizes(mAspectRatio).last(); | |
reader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), | |
imageFormat, 1); | |
reader.setOnImageAvailableListener(mOnImageAvailableListener, null); | |
return reader; | |
} | |
/** | |
* <p>Starts opening a camera device.</p> | |
* <p>The result will be processed in {@link #mCameraDeviceCallback}.</p> | |
*/ | |
private void startOpeningCamera() { | |
try { | |
mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null); | |
} catch (CameraAccessException e) { | |
throw new RuntimeException("Failed to open camera: " + mCameraId, e); | |
} | |
} | |
/** | |
* <p>Starts a capture session for camera preview.</p> | |
* <p>This rewrites {@link #mPreviewRequestBuilder}.</p> | |
* <p>The result will be continuously processed in {@link #mSessionCallback}.</p> | |
*/ | |
void startCaptureSession() { | |
if (!isCameraOpened() || !mPreview.isReady() || mStillImageReader == null || mScanImageReader == null) { | |
return; | |
} | |
Size previewSize = chooseOptimalSize(); | |
mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight()); | |
Surface surface = mPreview.getSurface(); | |
try { | |
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
mPreviewRequestBuilder.addTarget(surface); | |
if (mIsScanning) { | |
mPreviewRequestBuilder.addTarget(mScanImageReader.getSurface()); | |
} | |
mCamera.createCaptureSession(Arrays.asList(surface, mStillImageReader.getSurface(), | |
mScanImageReader.getSurface()), mSessionCallback, null); | |
} catch (CameraAccessException e) { | |
throw new RuntimeException("Failed to start camera session"); | |
} | |
} | |
/** | |
* Chooses the optimal preview size based on {@link #mPreviewSizes} and the surface size. | |
* | |
* @return The picked size for camera preview. | |
*/ | |
private Size chooseOptimalSize() { | |
int surfaceLonger, surfaceShorter; | |
final int surfaceWidth = mPreview.getWidth(); | |
final int surfaceHeight = mPreview.getHeight(); | |
if (surfaceWidth < surfaceHeight) { | |
surfaceLonger = surfaceHeight; | |
surfaceShorter = surfaceWidth; | |
} else { | |
surfaceLonger = surfaceWidth; | |
surfaceShorter = surfaceHeight; | |
} | |
SortedSet<Size> 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(); | |
} | |
/** | |
* Updates the internal state of auto-focus to {@link #mAutoFocus}. | |
*/ | |
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}. | |
*/ | |
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. | |
*/ | |
void captureStillPicture() { | |
try { | |
CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest( | |
CameraDevice.TEMPLATE_STILL_CAPTURE); | |
if (mIsScanning) { | |
mImageFormat = ImageFormat.JPEG; | |
captureRequestBuilder.removeTarget(mScanImageReader.getSurface()); | |
} | |
captureRequestBuilder.addTarget(mStillImageReader.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; | |
} | |
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOutputRotation()); | |
// 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); | |
} | |
} | |
private int getOutputRotation() { | |
@SuppressWarnings("ConstantConditions") | |
int sensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); | |
return (sensorOrientation + | |
mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) + | |
360) % 360; | |
} | |
/** | |
* Unlocks the auto-focus and restart camera preview. This is supposed to be called after | |
* capturing a still picture. | |
*/ | |
void unlockFocus() { | |
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, | |
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); | |
try { | |
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null); | |
updateAutoFocus(); | |
updateFlash(); | |
if (mIsScanning) { | |
mImageFormat = ImageFormat.YUV_420_888; | |
startCaptureSession(); | |
} else { | |
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 { | |
static final int STATE_PREVIEW = 0; | |
static final int STATE_LOCKING = 1; | |
static final int STATE_LOCKED = 2; | |
static final int STATE_PRECAPTURE = 3; | |
static final int STATE_WAITING = 4; | |
static final int STATE_CAPTURING = 5; | |
private int mState; | |
PictureCaptureCallback() { | |
} | |
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 || | |
ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { | |
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(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment