blob: 1c42629ce9cb17cfc3598735293e25cfa5518591 [file] [log] [blame]
/*
* Copyright (C) 2014 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 android.hardware.camera2.cts.helpers;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import com.android.ex.camera2.pos.AutoFocusStateMachine;
import com.android.ex.camera2.pos.AutoFocusStateMachine.AutoFocusStateListener;
/**
* A focuser utility class to assist camera to do auto focus.
* <p>
* This class need create repeating request and single request to do auto focus.
* The repeating request is used to get the auto focus states; the single
* request is used to trigger the auto focus. This class assumes the camera device
* supports auto-focus. Don't use this class if the camera device doesn't have focuser
* unit.
* </p>
*/
public class Camera2Focuser implements AutoFocusStateListener {
private static final String TAG = "Focuser";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private final AutoFocusStateMachine mAutoFocus = new AutoFocusStateMachine(this);
private final Handler mHandler;
private final AutoFocusListener mAutoFocusListener;
private final CameraDevice mCamera;
private final CameraCaptureSession mSession;
private final Surface mRequestSurface;
private final StaticMetadata mStaticInfo;
private int mAfRun = 0;
private MeteringRectangle[] mAfRegions;
private boolean mLocked = false;
private boolean mSuccess = false;
private CaptureRequest.Builder mRepeatingBuilder;
/**
* The callback interface to notify auto focus result.
*/
public interface AutoFocusListener {
/**
* This callback is called when auto focus completes and locked.
*
* @param success true if focus was successful, false if otherwise
*/
void onAutoFocusLocked(boolean success);
}
/**
* Construct a focuser object, with given capture requestSurface, listener
* and handler.
* <p>
* The focuser object will use camera and requestSurface to submit capture
* request and receive focus state changes. The {@link AutoFocusListener} is
* used to notify the auto focus callback.
* </p>
*
* @param camera The camera device associated with this focuser
* @param session The camera capture session associated with this focuser
* @param requestSurface The surface to issue the capture request with
* @param listener The auto focus listener to notify AF result
* @param staticInfo The CameraCharacteristics of the camera device
* @param handler The handler used to post auto focus callbacks
* @throws CameraAccessException
*/
public Camera2Focuser(CameraDevice camera, CameraCaptureSession session, Surface requestSurface,
AutoFocusListener listener, CameraCharacteristics staticInfo, Handler handler)
throws CameraAccessException {
if (camera == null) {
throw new IllegalArgumentException("camera must not be null");
}
if (session == null) {
throw new IllegalArgumentException("session must not be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler must not be null");
}
if (requestSurface == null) {
throw new IllegalArgumentException("requestSurface must not be null");
}
if (staticInfo == null) {
throw new IllegalArgumentException("staticInfo must not be null");
}
mCamera = camera;
mSession = session;
mRequestSurface = requestSurface;
mAutoFocusListener = listener;
mStaticInfo = new StaticMetadata(staticInfo,
StaticMetadata.CheckLevel.ASSERT, /*collector*/null);
mHandler = handler;
if (!mStaticInfo.hasFocuser()) {
throw new IllegalArgumentException("this camera doesn't have a focuser");
}
/**
* Begin by always being in passive auto focus.
*/
cancelAutoFocus();
}
@Override
public synchronized void onAutoFocusSuccess(CaptureResult result, boolean locked) {
mSuccess = true;
mLocked = locked;
if (locked) {
dispatchAutoFocusStatusLocked(/*success*/true);
}
}
@Override
public synchronized void onAutoFocusFail(CaptureResult result, boolean locked) {
mSuccess = false;
mLocked = locked;
if (locked) {
dispatchAutoFocusStatusLocked(/*success*/false);
}
}
@Override
public synchronized void onAutoFocusScan(CaptureResult result) {
mSuccess = false;
mLocked = false;
}
@Override
public synchronized void onAutoFocusInactive(CaptureResult result) {
mSuccess = false;
mLocked = false;
}
/**
* Start a active auto focus scan based on the given regions.
*
* <p>This is usually used for touch for focus, it can make the auto-focus converge based
* on some particular region aggressively. But it is usually slow as a full active scan
* is initiated. After the auto focus is converged, the {@link cancelAutoFocus} must be called
* to resume the continuous auto-focus.</p>
*
* @param afRegions The AF regions used by focuser auto focus, full active
* array size is used if afRegions is null.
* @throws CameraAccessException
*/
public synchronized void touchForAutoFocus(MeteringRectangle[] afRegions)
throws CameraAccessException {
startAutoFocusLocked(/*active*/true, afRegions);
}
/**
* Start auto focus scan.
* <p>
* Start an auto focus scan if it was not done yet. If AF passively focused,
* lock it. If AF is already locked, return. Otherwise, initiate a full
* active scan. This is suitable for still capture: focus should need to be
* accurate, but the AF latency also need to be as short as possible.
* </p>
*
* @param afRegions The AF regions used by focuser auto focus, full active
* array size is used if afRegions is null.
* @throws CameraAccessException
*/
public synchronized void startAutoFocus(MeteringRectangle[] afRegions)
throws CameraAccessException {
startAutoFocusLocked(/*forceActive*/false, afRegions);
}
/**
* Cancel ongoing auto focus, unlock the auto-focus if it was locked, and
* resume to passive continuous auto focus.
*
* @throws CameraAccessException
*/
public synchronized void cancelAutoFocus() throws CameraAccessException {
mSuccess = false;
mLocked = false;
// reset the AF regions:
setAfRegions(null);
// Create request builders, the af regions are automatically updated.
mRepeatingBuilder = createRequestBuilder();
CaptureRequest.Builder requestBuilder = createRequestBuilder();
mAutoFocus.setPassiveAutoFocus(/*picture*/true, mRepeatingBuilder);
mAutoFocus.unlockAutoFocus(mRepeatingBuilder, requestBuilder);
CaptureCallback listener = createCaptureListener();
mSession.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
mSession.capture(requestBuilder.build(), listener, mHandler);
}
/**
* Get current AF mode.
* @return current AF mode
* @throws IllegalStateException if there auto focus is not running.
*/
public synchronized int getCurrentAfMode() {
if (mRepeatingBuilder == null) {
throw new IllegalStateException("Auto focus is not running, unable to get AF mode");
}
return mRepeatingBuilder.get(CaptureRequest.CONTROL_AF_MODE);
}
private void startAutoFocusLocked(
boolean forceActive, MeteringRectangle[] afRegions) throws CameraAccessException {
setAfRegions(afRegions);
mAfRun++;
// Create request builders, the af regions are automatically updated.
mRepeatingBuilder = createRequestBuilder();
CaptureRequest.Builder requestBuilder = createRequestBuilder();
if (forceActive) {
startAutoFocusFullActiveLocked();
} else {
// Not forcing a full active scan. If AF passively focused, lock it. If AF is already
// locked, return. Otherwise, initiate a full active scan.
if (mSuccess && mLocked) {
dispatchAutoFocusStatusLocked(/*success*/true);
return;
} else if (mSuccess) {
mAutoFocus.lockAutoFocus(mRepeatingBuilder, requestBuilder);
CaptureCallback listener = createCaptureListener();
mSession.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
mSession.capture(requestBuilder.build(), listener, mHandler);
} else {
startAutoFocusFullActiveLocked();
}
}
}
private void startAutoFocusFullActiveLocked() throws CameraAccessException {
// Create request builders, the af regions are automatically updated.
mRepeatingBuilder = createRequestBuilder();
CaptureRequest.Builder requestBuilder = createRequestBuilder();
mAutoFocus.setActiveAutoFocus(mRepeatingBuilder, requestBuilder);
if (mRepeatingBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER)
!= CaptureRequest.CONTROL_AF_TRIGGER_IDLE) {
throw new AssertionError("Wrong trigger set in repeating request");
}
if (requestBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER)
!= CaptureRequest.CONTROL_AF_TRIGGER_START) {
throw new AssertionError("Wrong trigger set in queued request");
}
mAutoFocus.resetState();
CaptureCallback listener = createCaptureListener();
mSession.setRepeatingRequest(mRepeatingBuilder.build(), listener, mHandler);
mSession.capture(requestBuilder.build(), listener, mHandler);
}
private void dispatchAutoFocusStatusLocked(final boolean success) {
mHandler.post(new Runnable() {
@Override
public void run() {
mAutoFocusListener.onAutoFocusLocked(success);
}
});
}
/**
* Create request builder, set the af regions.
* @throws CameraAccessException
*/
private CaptureRequest.Builder createRequestBuilder() throws CameraAccessException {
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, mAfRegions);
requestBuilder.addTarget(mRequestSurface);
return requestBuilder;
}
/**
* Set AF regions, fall back to default region if afRegions is null.
*
* @param afRegions The AF regions to set
* @throws IllegalArgumentException if the region is malformed (length is 0).
*/
private void setAfRegions(MeteringRectangle[] afRegions) {
if (afRegions == null) {
setDefaultAfRegions();
return;
}
// Throw IAE if AF regions are malformed.
if (afRegions.length == 0) {
throw new IllegalArgumentException("afRegions is malformed, length: 0");
}
mAfRegions = afRegions;
}
/**
* Set default AF region to full active array size.
*/
private void setDefaultAfRegions() {
// Initialize AF regions with all zeros, meaning that it is up to camera device to device
// the regions used by AF.
mAfRegions = new MeteringRectangle[] {
new MeteringRectangle(0, 0, 0, 0, MeteringRectangle.METERING_WEIGHT_DONT_CARE)};
}
private CaptureCallback createCaptureListener() {
int thisAfRun;
synchronized (this) {
thisAfRun = mAfRun;
}
final int finalAfRun = thisAfRun;
return new CaptureCallback() {
private long mLatestFrameCount = -1;
@Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
CaptureResult result) {
// In case of a partial result, send to focuser if necessary
// 3A fields are present
if (result.get(CaptureResult.CONTROL_AF_STATE) != null &&
result.get(CaptureResult.CONTROL_AF_MODE) != null) {
if (VERBOSE) {
Log.v(TAG, "Focuser - got early AF state");
}
dispatchToFocuser(result);
}
}
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
dispatchToFocuser(result);
}
private void dispatchToFocuser(CaptureResult result) {
int afRun;
synchronized (Camera2Focuser.this) {
// In case of partial results, don't send AF update twice
long frameCount = result.getFrameNumber();
if (frameCount <= mLatestFrameCount) return;
mLatestFrameCount = frameCount;
afRun = mAfRun;
}
if (afRun != finalAfRun) {
if (VERBOSE) {
Log.w(TAG,
"onCaptureCompleted - Ignoring results from previous AF run "
+ finalAfRun);
}
return;
}
mAutoFocus.onCaptureCompleted(result);
}
};
}
}