I am trying to implement touch based focus on Android using cameraApi2. It work sort of but it doesn't seem to work always for me. My implementation is as follows:
First I create Camera PreviewSession with the following steps:
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
Then I call createCaptureSession and in that I call
mCaptureSession.setRepeatingRequest( mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
I have implement OnTouch Listener to capture the touched area of the screen and generate AF Region
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, meteringRectangleArr);
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
In the captureCallback, I do following steps in onCaptureCompleted
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
mCaptureSession.capture(mPreviewRequestBuilder.build(), null, mBackgroundHandler);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler);
Now upon touch the Camera focus works sometimes and sometimes the preview is completely blur.
Open the Camera app and tap the Settings icon. Tap the switch next to Tracking auto-focus to turn it off.
Here is an implementation of a handler class (based on this) which implements OnTouchListener
. You can set it to the TextureView
to detect the touched point and make the desired focus.
CameraFocusOnTouchHandler.java
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class CameraFocusOnTouchHandler implements View.OnTouchListener {
private static final String TAG = "FocusOnTouchHandler";
private CameraCharacteristics mCameraCharacteristics;
private CaptureRequest.Builder mPreviewRequestBuilder;
private CameraCaptureSession mCaptureSession;
private Handler mBackgroundHandler;
private boolean mManualFocusEngaged = false;
public CameraFocusOnTouchHandler(
CameraCharacteristics cameraCharacteristics,
CaptureRequest.Builder previewRequestBuilder,
CameraCaptureSession captureSession,
Handler backgroundHandler
) {
mCameraCharacteristics = cameraCharacteristics;
mPreviewRequestBuilder = previewRequestBuilder;
mCaptureSession = captureSession;
mBackgroundHandler = backgroundHandler;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//Override in your touch-enabled view (this can be different than the view you use for displaying the cam preview)
final int actionMasked = motionEvent.getActionMasked();
if (actionMasked != MotionEvent.ACTION_DOWN) {
return false;
}
if (mManualFocusEngaged) {
Log.d(TAG, "Manual focus already engaged");
return true;
}
final Rect sensorArraySize = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
//TODO: here I just flip x,y, but this needs to correspond with the sensor orientation (via SENSOR_ORIENTATION)
final int y = (int) ((motionEvent.getX() / (float) view.getWidth()) * (float) sensorArraySize.height());
final int x = (int) ((motionEvent.getY() / (float) view.getHeight()) * (float) sensorArraySize.width());
final int halfTouchWidth = 50; //(int)motionEvent.getTouchMajor(); //TODO: this doesn't represent actual touch size in pixel. Values range in [3, 10]...
final int halfTouchHeight = 50; //(int)motionEvent.getTouchMinor();
MeteringRectangle focusAreaTouch = new MeteringRectangle(Math.max(x - halfTouchWidth, 0),
Math.max(y - halfTouchHeight, 0),
halfTouchWidth * 2,
halfTouchHeight * 2,
MeteringRectangle.METERING_WEIGHT_MAX - 1);
CameraCaptureSession.CaptureCallback captureCallbackHandler = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
mManualFocusEngaged = false;
if (request.getTag() == "FOCUS_TAG") {
//the focus trigger is complete - resume repeating (preview surface will get frames), clear AF trigger
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, null);
try {
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
super.onCaptureFailed(session, request, failure);
Log.e(TAG, "Manual AF failure: " + failure);
mManualFocusEngaged = false;
}
};
//first stop the existing repeating request
try {
mCaptureSession.stopRepeating();
} catch (CameraAccessException e) {
e.printStackTrace();
}
//cancel any existing AF trigger (repeated touches, etc.)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
try {
mCaptureSession.capture(mPreviewRequestBuilder.build(), captureCallbackHandler, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//Now add a new AF trigger with focus region
if (isMeteringAreaAFSupported()) {
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{focusAreaTouch});
}
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
mPreviewRequestBuilder.setTag("FOCUS_TAG"); //we'll capture this later for resuming the preview
//then we ask for a single request (not repeating!)
try {
mCaptureSession.capture(mPreviewRequestBuilder.build(), captureCallbackHandler, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
mManualFocusEngaged = true;
return true;
}
private boolean isMeteringAreaAFSupported() {
Integer value = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
if (value != null) {
return value >= 1;
} else {
return false;
}
}
}
When you sure that the camera is opened and mCaptureSession
is successfully careated, you can set the touch handler to the TextureView
as following:
mTextureView.setOnTouchListener(new CameraFocusOnTouchHandler(mCameraCharacteristics, mPreviewRequestBuilder, mCaptureSession, mBackgroundHandler));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With