Camera2Basic: Enhancement to JPEG capture.
Incorporate autofocus and precapture trigger sequence.
Change-Id: I6ce92f81c9186a320a9d2d48f56210e8c7d043c6
diff --git a/media/Camera2Basic/Camera2BasicSample/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java b/media/Camera2Basic/Camera2BasicSample/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java
index ef7596c..19ec587 100644
--- a/media/Camera2Basic/Camera2BasicSample/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java
+++ b/media/Camera2Basic/Camera2BasicSample/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java
@@ -35,6 +35,7 @@
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
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;
@@ -77,9 +78,34 @@
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
+ /**
+ * Tag for the {@link Log}.
+ */
private static final String TAG = "Camera2BasicFragment";
/**
+ * Camera state: Showing camera preview.
+ */
+ private static final int STATE_PREVIEW = 0;
+
+ /**
+ * Camera state: Waiting for the focus to be locked.
+ */
+ private static final int STATE_WAITING_LOCK = 1;
+ /**
+ * Camera state: Waiting for the exposure to be precapture state.
+ */
+ private static final int STATE_WAITING_PRECAPTURE = 2;
+ /**
+ * Camera state: Waiting for the exposure state to be something other than precapture.
+ */
+ private static final int STATE_WAITING_NON_PRECAPTURE = 3;
+ /**
+ * Camera state: Picture was taken.
+ */
+ private static final int STATE_PICTURE_TAKEN = 4;
+
+ /**
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {@link TextureView}.
*/
@@ -198,6 +224,83 @@
};
/**
+ * {@link CaptureRequest.Builder} for the camera preview
+ */
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+
+ /**
+ * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
+ */
+ private CaptureRequest mPreviewRequest;
+
+ /**
+ * The current state of camera state for taking pictures.
+ *
+ * @see #mCaptureListener
+ */
+ private int mState = STATE_PREVIEW;
+
+ /**
+ * A {@link CameraCaptureSession.CaptureListener} that handles events related to JPEG capture.
+ */
+ private CameraCaptureSession.CaptureListener mCaptureListener
+ = new CameraCaptureSession.CaptureListener() {
+
+ private void process(CaptureResult result) {
+ switch (mState) {
+ case STATE_PREVIEW: {
+ // We have nothing to do when the camera preview is working normally.
+ break;
+ }
+ case STATE_WAITING_LOCK: {
+ int afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
+ int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+ mState = STATE_WAITING_NON_PRECAPTURE;
+ captureStillPicture();
+ } else {
+ runPrecaptureSequence();
+ }
+ }
+ break;
+ }
+ case STATE_WAITING_PRECAPTURE: {
+ int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (CaptureResult.CONTROL_AE_STATE_PRECAPTURE == aeState) {
+ mState = STATE_WAITING_NON_PRECAPTURE;
+ } else if (CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED == aeState) {
+ mState = STATE_WAITING_NON_PRECAPTURE;
+ }
+ break;
+ }
+ case STATE_WAITING_NON_PRECAPTURE: {
+ int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (CaptureResult.CONTROL_AE_STATE_PRECAPTURE != aeState) {
+ mState = STATE_PICTURE_TAKEN;
+ captureStillPicture();
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult) {
+ process(partialResult);
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ process(result);
+ }
+
+ };
+
+ /**
* Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
* width and height are at least as large as the respective requested values, and whose aspect
* ratio matches with the specified value.
@@ -408,9 +511,9 @@
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
- final CaptureRequest.Builder requestBuilder
+ mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- requestBuilder.addTarget(surface);
+ mPreviewRequestBuilder.addTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
@@ -421,12 +524,17 @@
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
- // Camera preview can be run in a background thread.
- setUpCaptureRequestBuilder(requestBuilder);
+ // Auto focus should be continuous for camera preview.
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ // Flash is automatically enabled when necessary.
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// Finally, we start displaying the camera preview.
- mCaptureSession.setRepeatingRequest(requestBuilder.build(), null,
- mBackgroundHandler);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+ mCaptureSession.setRepeatingRequest(mPreviewRequest,
+ mCaptureListener, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
@@ -447,16 +555,6 @@
}
/**
- * Fills in parameters of the {@link CaptureRequest.Builder}.
- *
- * @param builder The builder for a {@link CaptureRequest}
- */
- private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
- // In this sample, we just let the camera device pick the automatic settings.
- builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
- }
-
- /**
* Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
* This method should be called after the camera preview size is determined in
* setUpCameraOutputs and also the size of `mTextureView` is fixed.
@@ -488,9 +586,52 @@
}
/**
- * Takes a picture.
+ * Initiate a still image capture.
*/
private void takePicture() {
+ lockFocus();
+ }
+
+ /**
+ * Lock the focus as the first step for a still image capture.
+ */
+ private void lockFocus() {
+ try {
+ // This is how to tell the camera to lock focus.
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_START);
+ // Tell #mCaptureListener to wait for the lock.
+ mState = STATE_WAITING_LOCK;
+ mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureListener,
+ mBackgroundHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Run the precapture sequence for capturing a still image. This method should be called when we
+ * get a response in {@link #mCaptureListener} from {@link #lockFocus()}.
+ */
+ private void runPrecaptureSequence() {
+ try {
+ // This is how to tell the camera to trigger.
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ // Tell #mCaptureListener to wait for the precapture sequence to be set.
+ mState = STATE_WAITING_PRECAPTURE;
+ mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureListener,
+ mBackgroundHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Capture a still picture. This method should be called when we get a response in
+ * {@link #mCaptureListener} from both {@link #lockFocus()}.
+ */
+ private void captureStillPicture() {
try {
final Activity activity = getActivity();
if (null == activity || null == mCameraDevice) {
@@ -500,7 +641,12 @@
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
- setUpCaptureRequestBuilder(captureBuilder);
+
+ // Use the same AE and AF modes as the preview.
+ captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// Orientation
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
@@ -508,14 +654,38 @@
CameraCaptureSession.CaptureListener captureListener
= new CameraCaptureSession.CaptureListener() {
+
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
Toast.makeText(getActivity(), "Saved: " + mFile, Toast.LENGTH_SHORT).show();
+ unlockFocus();
}
};
- mCaptureSession.capture(captureBuilder.build(), captureListener, /*handler*/null);
+ mCaptureSession.stopRepeating();
+ mCaptureSession.capture(captureBuilder.build(), captureListener, null);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Unlock the focus. This method should be called when still image capture sequence is finished.
+ */
+ private void unlockFocus() {
+ try {
+ // Reset the autofucos trigger
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureListener,
+ mBackgroundHandler);
+ // After this, the camera will go back to the normal state of preview.
+ mState = STATE_PREVIEW;
+ mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureListener,
+ mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}