summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/one/v2/OneCameraImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/one/v2/OneCameraImpl.java')
-rw-r--r--src/com/android/camera/one/v2/OneCameraImpl.java395
1 files changed, 379 insertions, 16 deletions
diff --git a/src/com/android/camera/one/v2/OneCameraImpl.java b/src/com/android/camera/one/v2/OneCameraImpl.java
index 56e2a0d65..fb14c9f15 100644
--- a/src/com/android/camera/one/v2/OneCameraImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraImpl.java
@@ -17,6 +17,7 @@
package com.android.camera.one.v2;
import android.graphics.ImageFormat;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
@@ -24,12 +25,16 @@ import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
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.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
import android.view.OrientationEventListener;
import android.view.Surface;
@@ -45,6 +50,8 @@ import com.android.camera.one.OneCamera;
import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash;
import com.android.camera.session.CaptureSession;
import com.android.camera.util.Size;
+import com.android.camera.util.CameraUtil;
+import com.android.camera.util.SystemProperties;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -70,9 +77,96 @@ public class OneCameraImpl extends AbstractOneCamera {
}
private static final Tag TAG = new Tag("OneCameraImpl2");
+
+ /** System Properties switch to enable additional focus logging. */
+ private static final String PROP_FOCUS_DEBUG_KEY = "persist.camera.focus_debug_log";
+ private static final String PROP_FOCUS_DEBUG_OFF = "0";
+ private static final boolean FOCUS_DEBUG = !PROP_FOCUS_DEBUG_OFF
+ .equals(SystemProperties.get(PROP_FOCUS_DEBUG_KEY, PROP_FOCUS_DEBUG_OFF));
+
/** Default JPEG encoding quality. */
private static final Byte JPEG_QUALITY = 90;
+ /** Width and height of touch metering region as fraction of longest edge. */
+ private static final float METERING_REGION_EDGE = 0.1f;
+ /** Metering region weight between 0 and 1. */
+ private static final float METERING_REGION_WEIGHT = 0.25f;
+ /** Duration to hold after manual focus tap. */
+ private static final int FOCUS_HOLD_MILLIS = 3000;
+
+ /**
+ * CaptureRequest tags.
+ * <ul>
+ * <li>{@link #PRESHOT_TRIGGERED_AF}</li>
+ * <li>{@link #CAPTURE}</li>
+ * </ul>
+ */
+ public static enum RequestTag {
+ /** Request that is part of a pre shot trigger. */
+ PRESHOT_TRIGGERED_AF,
+ /** Capture request (purely for logging). */
+ CAPTURE
+ }
+
+ /** Current CONTROL_AF_MODE request to Camera2 API. */
+ private int mLastRequestedControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+ /** Last OneCamera.AutoFocusState reported. */
+ private AutoFocusState mLastResultAFState = AutoFocusState.INACTIVE;
+ /** Last OneCamera.AutoFocusMode reported. */
+ private AutoFocusMode mLastResultAFMode = AutoFocusMode.CONTINUOUS_PICTURE;
+ /** Flag to take a picture when in AUTO mode and the lens is stopped. */
+ private boolean mTakePictureWhenLensStoppedAndAuto = false;
+ /** Flag to take a picture when the lens is stopped. */
+ private boolean mTakePictureWhenLensIsStopped = false;
+ /** Takes a (delayed) picture with appropriate parameters. */
+ private Runnable mTakePictureRunnable;
+ /** Last time takePicture() was called in uptimeMillis. */
+ private long mTakePictureStartMillis;
+ /** Runnable that returns to CONTROL_AF_MODE = AF_CONTINUOUS_PICTURE. */
+ private Runnable mReturnToContinuousAFRunnable = new Runnable() {
+ @Override
+ public void run() {
+ repeatingPreviewWithReadyListener(null);
+ }
+ };
+
+ /** Current zoom value. 1.0 is no zoom. */
+ private float mZoomValue = 1f;
+ /** If partial results was OK, don't need to process total result. */
+ private boolean mAutoFocusStateListenerPartialOK = false;
+
+ /**
+ * Common listener for preview frame metadata.
+ */
+ private CameraCaptureSession.CaptureListener mAutoFocusStateListener = new
+ CameraCaptureSession.CaptureListener() {
+ // AF state information is sometimes available 1 frame before
+ // onCaptureCompleted(), so we take advantage of that.
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
+ CaptureResult partialResult) {
+
+ if (partialResult.get(CaptureResult.CONTROL_AF_STATE) != null) {
+ mAutoFocusStateListenerPartialOK = true;
+ autofocusStateChangeDispatcher(partialResult);
+ if (FOCUS_DEBUG) {
+ logExtraFocusInfo(partialResult);
+ }
+ } else {
+ mAutoFocusStateListenerPartialOK = false;
+ }
+ super.onCaptureProgressed(session, request, partialResult);
+ }
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ if (!mAutoFocusStateListenerPartialOK) {
+ autofocusStateChangeDispatcher(result);
+ }
+ super.onCaptureCompleted(session, request, result);
+ }
+ };
+
/** Thread on which the camera operations are running. */
private final HandlerThread mCameraThread;
/** Handler of the {@link #mCameraThread}. */
@@ -113,6 +207,7 @@ public class OneCameraImpl extends AbstractOneCamera {
// Since this is not an HDR+ session, we will just save the result.
capture.session.startEmpty();
byte[] imageBytes = acquireJpegBytesAndClose(reader);
+ // TODO: The savePicture call here seems to block UI thread.
savePicture(imageBytes, capture.parameters, capture.session);
capture.parameters.callback.onPictureTaken(capture.session);
}
@@ -140,21 +235,70 @@ public class OneCameraImpl extends AbstractOneCamera {
Log.d(TAG, "New Camera2 based OneCameraImpl created.");
}
+ /**
+ * Take picture, initiating an auto focus scan if needed.
+ */
@Override
- public void takePicture(PhotoCaptureParameters params, CaptureSession session) {
+ public void takePicture(final PhotoCaptureParameters params, final CaptureSession session) {
+ if (mTakePictureWhenLensStoppedAndAuto || mTakePictureWhenLensIsStopped) {
+ // Do not do anything when a picture is already in progress.
+ return;
+ }
+
+ mTakePictureRunnable = new Runnable() {
+ @Override
+ public void run() {
+ takePictureNow(params, session);
+ }
+ };
+ mTakePictureStartMillis = SystemClock.uptimeMillis();
+
+ if (mLastResultAFMode == AutoFocusMode.CONTINUOUS_PICTURE
+ && mLastResultAFState == AutoFocusState.STOPPED_UNFOCUSED) {
+ Log.v(TAG, "Unfocused: Triggering auto focus scan.");
+ // Trigger auto focus scan if in CONTINUOUS_PICTURE + unfocused.
+ mTakePictureWhenLensStoppedAndAuto = true;
+ repeatingPreviewWithAFTrigger(null, null, RequestTag.PRESHOT_TRIGGERED_AF);
+ } else if (mLastResultAFState == AutoFocusState.SCANNING) {
+ // Delay shot if scanning.
+ Log.v(TAG, "Waiting until scan is done before taking shot.");
+ mTakePictureWhenLensIsStopped = true;
+ } else {
+ takePictureNow(params, session);
+ }
+ }
+
+ /**
+ * Take picture immediately. Parameters passed through from takePicture().
+ */
+ public void takePictureNow(PhotoCaptureParameters params, CaptureSession session) {
+ long dt = SystemClock.uptimeMillis() - mTakePictureStartMillis;
+ Log.v(TAG, "Taking shot with extra AF delay of " + dt + " ms.");
// This will throw a RuntimeException, if parameters are not sane.
params.checkSanity();
try {
- // JPEG capture
+ // JPEG capture.
CaptureRequest.Builder builder = mDevice
.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ // TODO: Check that these control modes are correct for AWB, AE.
+ if (mLastRequestedControlAFMode == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
+ builder.set(CaptureRequest.CONTROL_AF_MODE,
+ CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ Log.v(TAG, "CaptureRequest with CONTROL_AF_MODE_CONTINUOUS_PICTURE.");
+ } else if (mLastRequestedControlAFMode == CameraMetadata.CONTROL_AF_MODE_AUTO) {
+ builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+ Log.v(TAG, "CaptureRequest with AUTO.");
+ }
+ builder.setTag(RequestTag.CAPTURE);
builder.set(CaptureRequest.JPEG_QUALITY, JPEG_QUALITY);
builder.set(CaptureRequest.JPEG_ORIENTATION, getJpegRotation(params.orientation));
builder.addTarget(mPreviewSurface);
builder.addTarget(mJpegImageReader.getSurface());
applyFlashMode(params.flashMode, builder);
CaptureRequest request = builder.build();
- mCaptureSession.capture(request, null, mCameraHandler);
+ mCaptureSession.capture(request, mAutoFocusStateListener, mCameraHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "Could not access camera for JPEG capture.");
params.callback.onPictureTakenFailed();
@@ -190,6 +334,11 @@ public class OneCameraImpl extends AbstractOneCamera {
Log.w(TAG, "Camera is already closed.");
return;
}
+ try {
+ mCaptureSession.abortCaptures();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Could not abort captures in progress.");
+ }
mIsClosed = true;
mCloseCallback = closeCallback;
mCameraThread.quitSafely();
@@ -305,7 +454,7 @@ public class OneCameraImpl extends AbstractOneCamera {
@Override
public void onConfigured(CameraCaptureSession session) {
mCaptureSession = session;
- startPreviewInternal(listener);
+ repeatingPreviewWithReadyListener(listener);
}
@Override
@@ -323,29 +472,203 @@ public class OneCameraImpl extends AbstractOneCamera {
}
/**
- * Configures and creates the request for starting the preview.
+ * Request preview capture stream with AF_MODE_CONTINUOUS_PICTURE.
*
- * @param listener called when request was build and sent, or if setting up
- * the request failed.
+ * @param readyListener called when request was build and sent, or if
+ * setting up the request failed.
*/
- private void startPreviewInternal(CaptureReadyCallback listener) {
- Log.v(TAG, "issuePreviewCaptureRequest.");
+ private void repeatingPreviewWithReadyListener(CaptureReadyCallback readyListener) {
try {
- CaptureRequest.Builder builder = mDevice
- .createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CaptureRequest.Builder builder = mDevice.
+ createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ builder.addTarget(mPreviewSurface);
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ mLastRequestedControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
builder.set(CaptureRequest.CONTROL_AF_MODE,
CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+ mCaptureSession.setRepeatingRequest(builder.build(), mAutoFocusStateListener,
+ mCameraHandler);
+ Log.v(TAG, "Sent preview request with AF_MODE_CONTINUOUS_PICTURE.");
+ if (readyListener != null) {
+ readyListener.onReadyForCapture();
+ }
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Could not access camera setting up preview.", ex);
+ if (readyListener != null) {
+ readyListener.onSetupFailed();
+ }
+ }
+ }
+
+ /**
+ * Request preview capture stream with auto focus cycle.
+ *
+ * @param focusRegions focus regions, for tap to focus/expose.
+ * @param meteringRegions metering regions, for tap to focus/expose.
+ */
+ private void repeatingPreviewWithAFTrigger(MeteringRectangle[] focusRegions,
+ MeteringRectangle[] meteringRegions, Object tag) {
+ try {
+ CaptureRequest.Builder builder;
+ builder = mDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(mPreviewSurface);
- mCaptureSession.setRepeatingRequest(builder.build(), null, mCameraHandler);
- listener.onReadyForCapture();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Could not access camera setting up preview.");
- listener.onSetupFailed();
+ builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ if (focusRegions != null) {
+ builder.set(CaptureRequest.CONTROL_AF_REGIONS, focusRegions);
+ }
+ if (meteringRegions != null) {
+ builder.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRegions);
+ }
+ builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+ mLastRequestedControlAFMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
+
+ // Step 1: Request single frame CONTROL_AF_TRIGGER_START.
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+ mCaptureSession.capture(builder.build(), mAutoFocusStateListener, mCameraHandler);
+
+ // Step 2: Request continuous frames CONTROL_AF_TRIGGER_IDLE.
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+ builder.setTag(tag);
+ mCaptureSession.setRepeatingRequest(builder.build(), mAutoFocusStateListener,
+ mCameraHandler);
+ resumeContinuousAFAfterDelay(FOCUS_HOLD_MILLIS);
+ } catch (CameraAccessException ex) {
+ Log.e(TAG, "Could not execute preview request.", ex);
+ }
+ }
+
+ /**
+ * Resume AF_MODE_CONTINUOUS_PICTURE after FOCUS_HOLD_MILLIS.
+ */
+ private void resumeContinuousAFAfterDelay(int millis) {
+ mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
+ mCameraHandler.postDelayed(mReturnToContinuousAFRunnable, millis);
+ }
+
+ /**
+ * This method takes appropriate action if camera2 AF state changes.
+ * <ol>
+ * <li>Reports changes in camera2 AF state to OneCamera.FocusStateListener.</li>
+ * <li>Take picture after AF scan.</li>
+ * <li>TODO: Take picture after AE_PRECAPTURE sequence for flash.</li>
+ * </ol>
+ */
+ private void autofocusStateChangeDispatcher(CaptureResult result) {
+ Integer nativeAFControlState = result.get(CaptureResult.CONTROL_AF_STATE);
+ Integer nativeAFControlMode = result.get(CaptureResult.CONTROL_AF_MODE);
+ Object tag = result.getRequest().getTag();
+
+ // Convert to OneCamera mode and state.
+ AutoFocusMode resultAFMode = modeFromCamera2Mode(nativeAFControlMode);
+ AutoFocusState resultAFState = stateFromCamera2State(nativeAFControlState);
+
+ boolean lensIsStopped = (resultAFState == AutoFocusState.STOPPED_FOCUSED ||
+ resultAFState == AutoFocusState.STOPPED_UNFOCUSED);
+ if (tag == RequestTag.PRESHOT_TRIGGERED_AF && lensIsStopped &&
+ mTakePictureWhenLensStoppedAndAuto) {
+ // Take the shot.
+ mCameraHandler.post(mTakePictureRunnable);
+ // Return to passive scanning.
+ mCameraHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ repeatingPreviewWithReadyListener(null);
+ }
+ });
+ mTakePictureWhenLensStoppedAndAuto = false;
+ }
+ if (mTakePictureWhenLensIsStopped && lensIsStopped) {
+ // Take the shot.
+ mCameraHandler.post(mTakePictureRunnable);
+ mTakePictureWhenLensIsStopped = false;
+ }
+
+ // Report state change when mode or state has changed.
+ if (resultAFState != mLastResultAFState || resultAFMode != mLastResultAFMode
+ && mFocusStateListener != null) {
+ mFocusStateListener.onFocusStatusUpdate(resultAFMode, resultAFState);
+ }
+ mLastResultAFState = resultAFState;
+ mLastResultAFMode = resultAFMode;
+ }
+
+ /**
+ * Convert reported camera2 AF state to OneCamera AutoFocusState.
+ */
+ private static AutoFocusState stateFromCamera2State(int state) {
+ switch (state) {
+ case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+ return AutoFocusState.SCANNING;
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+ return AutoFocusState.STOPPED_FOCUSED;
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+ case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ return AutoFocusState.STOPPED_UNFOCUSED;
+ default:
+ return AutoFocusState.INACTIVE;
}
}
/**
+ * Convert reported camera2 AF state to OneCamera AutoFocusMode.
+ */
+ private static AutoFocusMode modeFromCamera2Mode(int mode) {
+ if (mode == CaptureResult.CONTROL_AF_MODE_AUTO) {
+ return AutoFocusMode.AUTO;
+ } else {
+ // CONTROL_AF_MODE_CONTINUOUS_PICTURE is the other mode used.
+ return AutoFocusMode.CONTINUOUS_PICTURE;
+ }
+ }
+
+ @Override
+ public void triggerAutoFocus() {
+ Log.v(TAG, "triggerAutoFocus()");
+ repeatingPreviewWithAFTrigger(null, null, null);
+ }
+
+ @Override
+ public void triggerFocusAndMeterAtPoint(float nx, float ny) {
+ Log.v(TAG, "triggerFocusAndMeterAtPoint(" + nx + "," + ny + ")");
+ float points[] = new float[]{nx, ny};
+ // Make sure the points are in [0,1] range.
+ points[0] = CameraUtil.clamp(points[0], 0f, 1f);
+ points[1] = CameraUtil.clamp(points[1], 0f, 1f);
+
+ // Shrink points towards center if zoomed.
+ if (mZoomValue > 1f) {
+ Matrix zoomMatrix = new Matrix();
+ zoomMatrix.postScale(1f / mZoomValue, 1f / mZoomValue, 0.5f, 0.5f);
+ zoomMatrix.mapPoints(points);
+ }
+
+ // TODO: Make this work when preview aspect ratio != sensor aspect ratio.
+ Rect sensor = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ int edge = (int) (METERING_REGION_EDGE * Math.max(sensor.width(), sensor.height()));
+ // x0 and y0 in sensor coordinate system, rotated 90 degrees from portrait.
+ int x0 = (int) (sensor.width() * points[1]);
+ int y0 = (int) (sensor.height() * (1f - points[0]));
+ int x1 = x0 + edge;
+ int y1 = y0 + edge;
+
+ // Make sure regions are inside the sensor area.
+ x0 = CameraUtil.clamp(x0, 0, sensor.width() - 1);
+ x1 = CameraUtil.clamp(x1, 0, sensor.width() - 1);
+ y0 = CameraUtil.clamp(y0, 0, sensor.height() - 1);
+ y1 = CameraUtil.clamp(y1, 0, sensor.height() - 1);
+ int wt = (int) ((1 - METERING_REGION_WEIGHT) * (float) MeteringRectangle.METERING_WEIGHT_MIN
+ + METERING_REGION_WEIGHT * (float) MeteringRectangle.METERING_WEIGHT_MAX);
+
+ Log.v(TAG, "sensor 3A @ x0=" + x0 + " y0=" + y0 + " dx=" + (x1 - x0) + " dy=" + (y1 - y0));
+ MeteringRectangle[] regions = new MeteringRectangle[]{
+ new MeteringRectangle(x0, y0, x1 - x0, y1 - y0, wt)};
+ repeatingPreviewWithAFTrigger(regions, regions, null);
+ }
+
+ /**
* Calculate the aspect ratio of the full size capture on this device.
*
* @param characteristics the characteristics of the camera device.
@@ -419,4 +742,44 @@ public class OneCameraImpl extends AbstractOneCamera {
break;
}
}
+
+ /**
+ * Utility function: converts CaptureResult.CONTROL_AF_STATE* to String.
+ */
+ private static String camera2ControlAFStateDesc(int aFState) {
+ switch (aFState) {
+ case CaptureResult.CONTROL_AF_STATE_INACTIVE:
+ return "inactive";
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+ return "passive_scan";
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ return "passive_focused";
+ case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
+ return "active_scan";
+ case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+ return "focus_locked";
+ case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ return "not_focus_locked";
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+ return "passive_unfocused";
+ default:
+ return "unknown";
+ }
+ }
+
+ private void logExtraFocusInfo(CaptureResult result) {
+ Object tag = result.getRequest().getTag();
+ // Nexus 5 has a bug where CONTROL_AF_STATE is missing sometimes.
+ if (result.get(CaptureResult.CONTROL_AF_STATE) == null) {
+ //throw new IllegalStateException("CaptureResult missing CONTROL_AF_STATE.");
+ Log.e(TAG, "\n!!!! TotalCaptureResult missing CONTROL_AF_STATE. !!!!\n ");
+ return;
+ }
+ Log.v(TAG, "camera2 AF state: " + camera2ControlAFStateDesc(result.
+ get(CaptureResult.CONTROL_AF_STATE)) +
+ (tag == null ? "" : (" tag: " + tag)));
+ if (result.get(CaptureResult.LENS_FOCUS_DISTANCE) != null) {
+ Log.v(TAG, " lens @ " + result.get(CaptureResult.LENS_FOCUS_DISTANCE));
+ }
+ }
}