summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/camera/CaptureModule.java156
-rw-r--r--src/com/android/camera/CaptureModuleUI.java9
-rw-r--r--src/com/android/camera/one/OneCamera.java61
-rw-r--r--src/com/android/camera/one/v2/OneCameraImpl.java395
4 files changed, 582 insertions, 39 deletions
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 0ade6b164..71eba6859 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -47,6 +47,8 @@ import com.android.camera.debug.Log.Tag;
import com.android.camera.hardware.HardwareSpec;
import com.android.camera.module.ModuleController;
import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCamera.AutoFocusMode;
+import com.android.camera.one.OneCamera.AutoFocusState;
import com.android.camera.one.OneCamera.CaptureReadyCallback;
import com.android.camera.one.OneCamera.Facing;
import com.android.camera.one.OneCamera.OpenCallback;
@@ -62,6 +64,8 @@ import com.android.camera.ui.PreviewStatusListener;
import com.android.camera.ui.TouchCoordinate;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.Size;
+import com.android.camera.util.SystemProperties;
+import com.android.camera.util.UsageStatistics;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
@@ -87,6 +91,7 @@ public class CaptureModule extends CameraModule
implements MediaSaver.QueueListener,
ModuleController,
OneCamera.PictureCallback,
+ OneCamera.FocusStateListener,
PreviewStatusListener.PreviewAreaChangedListener,
RemoteCameraModule,
SensorEventListener,
@@ -140,6 +145,29 @@ public class CaptureModule extends CameraModule
}
};
+ /**
+ * Show AF target in center of preview and start animation.
+ */
+ Runnable mShowAutoFocusTargetInCenterRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mUI.setAutoFocusTarget(((int) (mPreviewArea.left + mPreviewArea.right)) / 2,
+ ((int) (mPreviewArea.top + mPreviewArea.bottom)) / 2);
+ mUI.showAutoFocusInProgress();
+ }
+ };
+
+ /**
+ * Hide AF target UI element.
+ */
+ Runnable mHideAutoFocusTargetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // showAutoFocusSuccess() just hides the AF UI.
+ mUI.showAutoFocusSuccess();
+ }
+ };
+
private static final Tag TAG = new Tag("CaptureModule");
private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
/** Enable additional debug output. */
@@ -152,6 +180,12 @@ public class CaptureModule extends CameraModule
*/
private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
+ /** System Properties switch to enable debugging focus UI. */
+ private static final String PROP_FOCUS_DEBUG_UI_KEY = "persist.camera.focus_debug_ui";
+ private static final String PROP_FOCUS_DEBUG_UI_OFF = "0";
+ private static final boolean FOCUS_DEBUG_UI = !PROP_FOCUS_DEBUG_UI_OFF
+ .equals(SystemProperties.get(PROP_FOCUS_DEBUG_UI_KEY, PROP_FOCUS_DEBUG_UI_OFF));
+
private final Object mDimensionLock = new Object();
/**
* Lock for race conditions in the SurfaceTextureListener callbacks.
@@ -186,6 +220,16 @@ public class CaptureModule extends CameraModule
private ModuleState mState = ModuleState.IDLE;
/** Current orientation of the device. */
private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ /** Current zoom value. */
+ private float mZoomValue = 1f;
+
+ /** True if in AF tap-to-focus sequence. */
+ private boolean mTapToFocusInProgress = false;
+
+ /** Persistence of Tap to Focus target UI after scan complete. */
+ private static final int FOCUS_HOLD_UI_MILLIS = 500;
+ /** Persistence of Tap to Focus target UI timeout. */
+ private static final int FOCUS_HOLD_UI_TIMEOUT_MILLIS = 1500;
/** Accelerometer data. */
private final float[] mGData = new float[3];
@@ -214,19 +258,16 @@ public class CaptureModule extends CameraModule
/** Current display rotation in degrees. */
private int mDisplayRotation;
- /** Current width of the screen, in pixels. */
+ /** Current screen width in pixels. */
private int mScreenWidth;
- /** Current height of the screen, in pixels. */
+ /** Current screen height in pixels. */
private int mScreenHeight;
- /** Current preview width, in pixels. */
+ /** Current width of preview frames from camera. */
private int mPreviewBufferWidth;
- /** Current preview height, in pixels. */
+ /** Current height of preview frames from camera.. */
private int mPreviewBufferHeight;
-
- // /** Current preview area width. */
- // private float mFullPreviewWidth;
- // /** Current preview area height. */
- // private float mFullPreviewHeight;
+ /** Area used by preview. */
+ RectF mPreviewArea;
/** The current preview transformation matrix. */
private Matrix mPreviewTranformationMatrix = new Matrix();
@@ -325,6 +366,7 @@ public class CaptureModule extends CameraModule
@Override
public void onPreviewAreaChanged(RectF previewArea) {
+ mPreviewArea = previewArea;
// mUI.updatePreviewAreaRect(previewArea);
// mUI.positionProgressOverlay(previewArea);
}
@@ -421,6 +463,7 @@ public class CaptureModule extends CameraModule
public void onReadyForCapture() {
Log.d(TAG, "Ready for capture.");
onPreviewStarted();
+ mCamera.setFocusStateListener(CaptureModule.this);
}
});
}
@@ -578,8 +621,97 @@ public class CaptureModule extends CameraModule
return false;
}
+ /**
+ * Focus sequence starts for zone around tap location for single tap.
+ */
@Override
public void onSingleTapUp(View view, int x, int y) {
+ Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
+ // TODO: This should query actual capability.
+ if (mCameraFacing == Facing.FRONT) {
+ return;
+ }
+ triggerFocusAtScreenCoord(x, y);
+ }
+
+ // TODO: Consider refactoring FocusOverlayManager.
+ // Currently AF state transitions are controlled in OneCameraImpl.
+ // PhotoModule uses FocusOverlayManager which uses API1/portability
+ // logic and coordinates.
+
+ private void triggerFocusAtScreenCoord(int x, int y) {
+ mTapToFocusInProgress = true;
+ // Show UI immediately even though scan has not started yet.
+ mUI.setAutoFocusTarget(x, y);
+ mUI.showAutoFocusInProgress();
+ mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+ mMainHandler.postDelayed(mHideAutoFocusTargetRunnable, FOCUS_HOLD_UI_TIMEOUT_MILLIS);
+
+ // Normalize coordinates to [0,1] per CameraOne API.
+ float points[] = new float[2];
+ points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
+ points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
+
+ // Rotate coordinates to portrait orientation per CameraOne API.
+ Matrix rotationMatrix = new Matrix();
+ rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
+ rotationMatrix.mapPoints(points);
+ mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
+
+ // Log touch (screen coordinates).
+ if (mZoomValue == 1f) {
+ TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
+ y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
+ // TODO: Add to logging: duration, rotation.
+ UsageStatistics.instance().tapToFocus(touchCoordinate, null);
+ }
+ }
+
+ /**
+ * This AF status listener does two things:
+ * <ol>
+ * <li>Ends tap-to-focus period when mode goes from AUTO to CONTINUOUS_PICTURE.</li>
+ * <li>Updates AF UI if tap-to-focus is not in progress.</li>
+ * </ol>
+ */
+ public void onFocusStatusUpdate(final AutoFocusMode mode, final AutoFocusState state) {
+ Log.v(TAG, "AF status is mode:" + mode + " state:" + state);
+
+ if (FOCUS_DEBUG_UI) {
+ // TODO: Add debug circle radius+color UI to FocusOverlay.
+ // mMainHandler.post(...)
+ }
+
+ // After tap to focus SCAN completes, clear UI after FOCUS_HOLD_UI_MILLIS.
+ if (mTapToFocusInProgress && mode == AutoFocusMode.AUTO &&
+ (state == AutoFocusState.STOPPED_FOCUSED ||
+ state == AutoFocusState.STOPPED_UNFOCUSED)) {
+ mMainHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mTapToFocusInProgress = false;
+ mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+ mMainHandler.post(mHideAutoFocusTargetRunnable);
+ }
+ }, FOCUS_HOLD_UI_MILLIS);
+ }
+
+ // Use the OneCamera auto focus callbacks to show the UI, except for
+ // tap to focus where we show UI right away at touch, and then turn
+ // it off early at 0.5 sec, before the focus lock expires at 3 sec.
+ if (!mTapToFocusInProgress) {
+ switch (state) {
+ case SCANNING:
+ mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+ mMainHandler.post(mShowAutoFocusTargetInCenterRunnable);
+ break;
+ case STOPPED_FOCUSED:
+ case STOPPED_UNFOCUSED:
+ mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+ mMainHandler.post(mHideAutoFocusTargetRunnable);
+ break;
+ }
+ }
}
@Override
@@ -654,6 +786,7 @@ public class CaptureModule extends CameraModule
/***
* Update the preview transform based on the new dimensions.
+ * TODO: Make work with all: aspect ratios/resolutions x screens/cameras.
*/
private void updatePreviewTransform(int incomingWidth, int incomingHeight,
boolean forceUpdate) {
@@ -759,11 +892,9 @@ public class CaptureModule extends CameraModule
}
mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
+ // TODO: Take these quantities from mPreviewArea.
float previewWidth = effectiveWidth * scale;
float previewHeight = effectiveHeight * scale;
- // mFullPreviewWidth = previewWidth;
- // mFullPreviewHeight = previewHeight;
-
float previewCenterX = previewWidth / 2;
float previewCenterY = previewHeight / 2;
mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
@@ -833,6 +964,7 @@ public class CaptureModule extends CameraModule
private void closeCamera() {
if (mCamera != null) {
+ mCamera.setFocusStateListener(null);
mCamera.close(null);
mCamera = null;
}
diff --git a/src/com/android/camera/CaptureModuleUI.java b/src/com/android/camera/CaptureModuleUI.java
index e926a4ad5..b3ea99c08 100644
--- a/src/com/android/camera/CaptureModuleUI.java
+++ b/src/com/android/camera/CaptureModuleUI.java
@@ -174,13 +174,10 @@ public class CaptureModuleUI implements
mFocusUI.onFocusFailed();
}
- public void setAutoFocusTarget(int x, int y, boolean isAutoFocus) {
+ public void setAutoFocusTarget(int x, int y) {
+ // TODO: refactor.
+ boolean isAutoFocus = false;
mFocusUI.setFocusPosition(x, y, isAutoFocus);
- // Log manual tap to focus.
- // TODO: Log coordinates. Note: 16x9 mode will be tricky.
- if (isAutoFocus == false) {
- UsageStatistics.instance().tapToFocus(null, null);
- }
}
public void clearAutoFocusIndicator() {
diff --git a/src/com/android/camera/one/OneCamera.java b/src/com/android/camera/one/OneCamera.java
index a1d4c1146..d179c7283 100644
--- a/src/com/android/camera/one/OneCamera.java
+++ b/src/com/android/camera/one/OneCamera.java
@@ -36,6 +36,40 @@ public interface OneCamera {
}
/**
+ * Auto focus system status.
+ * <ul>
+ * <li>{@link #INACTIVE}</li>
+ * <li>{@link #SCANNING}</li>
+ * <li>{@link #STOPPED_FOCUSED}</li>
+ * <li>{@link #STOPPED_UNFOCUSED}</li>
+ * </ul>
+ */
+ public static enum AutoFocusState {
+ /** Indicates AF system is inactive for some reason (could be an error). */
+ INACTIVE,
+ /** Indicates scan in progress. */
+ SCANNING,
+ /** Indicates scan success (camera in focus). */
+ STOPPED_FOCUSED,
+ /** Indicates scan or other failure. */
+ STOPPED_UNFOCUSED
+ }
+
+ /**
+ * Auto focus system mode.
+ * <ul>
+ * <li>{@link #CONTINUOUS_PICTURE}</li>
+ * <li>{@link #AUTO}</li>
+ * </ul>
+ */
+ public static enum AutoFocusMode {
+ /** System is continuously focusing. */
+ CONTINUOUS_PICTURE,
+ /** System is running a triggered scan. */
+ AUTO
+ }
+
+ /**
* Classes implementing this interface will be called when the camera was
* opened or failed to open.
*/
@@ -129,16 +163,17 @@ public interface OneCamera {
/**
* Classes implementing this interface will be called when the state of the
- * focus changes.
+ * focus changes. Guaranteed not to stay stuck in scanning state past some
+ * reasonable timeout even if Camera API is stuck.
*/
public static interface FocusStateListener {
/**
- * Called when an auto-focus run ended
+ * Called when mode or state of auto focus system changes.
*
- * @param success whether auto-focus succeeded. If true, it means that
- * the image should not be sharp.
+ * @param mode Is manual AF trigger cycle active.
+ * @param state Current state: scanning, focused, not focused, inactive.
*/
- public void onAutoFocusDone(boolean success);
+ public void onFocusStatusUpdate(AutoFocusMode mode, AutoFocusState state);
}
/**
@@ -191,6 +226,22 @@ public interface OneCamera {
}
/**
+ * Triggers auto focus scan for default ROI.
+ */
+ public void triggerAutoFocus();
+
+ /**
+ * Meters and triggers auto focus scan with ROI around tap point.
+ * <p/>
+ * Normalized coordinates are referenced to portrait preview window
+ * with 0,0 top left and 1,1 bottom right. Rotation has no effect.
+ *
+ * @param nx normalized x coordinate.
+ * @param nx normalized y coordinate.
+ */
+ public void triggerFocusAndMeterAtPoint(float nx, float ny);
+
+ /**
* Call this to take a picture.
*
* @param params parameters for taking pictures.
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));
+ }
+ }
}