Add Snapshot2PictureRecorder

pull/580/head
Mattia Iavarone 6 years ago
parent 123d5456b6
commit c405f4cd1a
  1. 47
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java
  3. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java
  4. 104
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot2PictureRecorder.java
  5. 3
      demo/src/main/res/layout/activity_camera.xml

@ -52,6 +52,7 @@ import com.otaliastudios.cameraview.internal.utils.CropHelper;
import com.otaliastudios.cameraview.internal.utils.ImageHelper; import com.otaliastudios.cameraview.internal.utils.ImageHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import com.otaliastudios.cameraview.picture.Full2PictureRecorder; import com.otaliastudios.cameraview.picture.Full2PictureRecorder;
import com.otaliastudios.cameraview.picture.Snapshot2PictureRecorder;
import com.otaliastudios.cameraview.picture.SnapshotGlPictureRecorder; import com.otaliastudios.cameraview.picture.SnapshotGlPictureRecorder;
import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.AspectRatio;
@ -81,7 +82,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
private CameraCharacteristics mCameraCharacteristics; private CameraCharacteristics mCameraCharacteristics;
private CameraCaptureSession mSession; private CameraCaptureSession mSession;
private CaptureRequest.Builder mRepeatingRequestBuilder; private CaptureRequest.Builder mRepeatingRequestBuilder;
private CaptureRequest mRepeatingRequest;
private CameraCaptureSession.CaptureCallback mRepeatingRequestCallback; private CameraCaptureSession.CaptureCallback mRepeatingRequestCallback;
private CaptureResult mLastRepeatingResult; private CaptureResult mLastRepeatingResult;
private final Camera2Mapper mMapper = Camera2Mapper.get(); private final Camera2Mapper mMapper = Camera2Mapper.get();
@ -216,7 +216,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
private void applyRepeatingRequestBuilder(boolean checkStarted, int errorReason, @Nullable final Runnable onFirstFrame) { private void applyRepeatingRequestBuilder(boolean checkStarted, int errorReason, @Nullable final Runnable onFirstFrame) {
if (!checkStarted || getPreviewState() == STATE_STARTED) { if (!checkStarted || getPreviewState() == STATE_STARTED) {
try { try {
mRepeatingRequest = mRepeatingRequestBuilder.build(); CaptureRequest repeatingRequest = mRepeatingRequestBuilder.build();
final AtomicBoolean firstFrame = new AtomicBoolean(false); final AtomicBoolean firstFrame = new AtomicBoolean(false);
mRepeatingRequestCallback = new CameraCaptureSession.CaptureCallback() { mRepeatingRequestCallback = new CameraCaptureSession.CaptureCallback() {
@Override @Override
@ -245,13 +245,16 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (mPictureRecorder instanceof Full2PictureRecorder) { if (mPictureRecorder instanceof Full2PictureRecorder) {
((Full2PictureRecorder) mPictureRecorder).onCaptureCompleted(result); ((Full2PictureRecorder) mPictureRecorder).onCaptureCompleted(result);
} }
if (mPictureRecorder instanceof Snapshot2PictureRecorder) {
((Snapshot2PictureRecorder) mPictureRecorder).onCaptureCompleted(result);
}
if (mMeter != null && mMeter.isMetering()) { if (mMeter != null && mMeter.isMetering()) {
mMeter.onCapture(result); mMeter.onCapture(result);
} }
} }
}; };
mSession.setRepeatingRequest(mRepeatingRequest, mRepeatingRequestCallback, null); mSession.setRepeatingRequest(repeatingRequest, mRepeatingRequestCallback, null);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
throw new CameraException(e, errorReason); throw new CameraException(e, errorReason);
} }
@ -543,6 +546,10 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mVideoRecorder = null; mVideoRecorder = null;
} }
mPictureRecorder = null; mPictureRecorder = null;
if (mMeter != null) {
mMeter.resetMetering();
mMeter = null;
}
if (hasFrameProcessors()) { if (hasFrameProcessors()) {
getFrameManager().release(); getFrameManager().release();
} }
@ -557,8 +564,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
throw createCameraException(e); throw createCameraException(e);
} }
removeRepeatingRequestBuilderSurfaces(); removeRepeatingRequestBuilderSurfaces();
mRepeatingRequest = null;
mMeter = null;
LOG.i("onStopPreview:", "Returning."); LOG.i("onStopPreview:", "Returning.");
return Tasks.forResult(null); return Tasks.forResult(null);
} }
@ -617,17 +622,21 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
stub.size = getUncroppedSnapshotSize(Reference.OUTPUT); // Not the real size: it will be cropped to match the view ratio stub.size = getUncroppedSnapshotSize(Reference.OUTPUT); // Not the real size: it will be cropped to match the view ratio
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0. stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0.
if (doMetering) { if (doMetering) {
LOG.i("onTakePictureSnapshot:", "doMetering is true. Delaying.");
mDelayedPictureStub = stub; mDelayedPictureStub = stub;
mDelayedPictureRatio = outputRatio; mDelayedPictureRatio = outputRatio;
startMetering(null, null); startMetering(null, null);
} else { } else {
LOG.i("onTakePictureSnapshot:", "doMetering is false. Performing.");
if (!(mPreview instanceof GlCameraPreview)) { if (!(mPreview instanceof GlCameraPreview)) {
throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE"); throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE");
} }
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, mPictureRecorder = new Snapshot2PictureRecorder(stub, this,
(GlCameraPreview) mPreview, (GlCameraPreview) mPreview,
outputRatio, outputRatio,
getOverlay()); mSession,
mRepeatingRequestCallback,
mRepeatingRequestBuilder);
mPictureRecorder.take(); mPictureRecorder.take();
} }
} }
@ -636,11 +645,12 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
protected void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering) { protected void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering) {
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getPictureSize(Reference.OUTPUT); stub.size = getPictureSize(Reference.OUTPUT);
if (doMetering) { if (doMetering) {
LOG.i("onTakePicture:", "doMetering is true. Delaying.");
mDelayedPictureStub = stub; mDelayedPictureStub = stub;
startMetering(null, null); startMetering(null, null);
} else { } else {
LOG.i("onTakePicture:", "doMetering is false. Performing.");
try { try {
CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
applyAllParameters(builder); applyAllParameters(builder);
@ -771,7 +781,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
* the {@link #createRepeatingRequestBuilder(int)} method. * the {@link #createRepeatingRequestBuilder(int)} method.
*/ */
private void maybeRestorePreviewTemplateAfterVideo() { private void maybeRestorePreviewTemplateAfterVideo() {
int template = (int) mRepeatingRequest.getTag(); int template = (int) mRepeatingRequestBuilder.build().getTag();
if (template != CameraDevice.TEMPLATE_PREVIEW) { if (template != CameraDevice.TEMPLATE_PREVIEW) {
try { try {
createRepeatingRequestBuilder(CameraDevice.TEMPLATE_PREVIEW); createRepeatingRequestBuilder(CameraDevice.TEMPLATE_PREVIEW);
@ -902,8 +912,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// On some devices, switching from TORCH/OFF to AUTO/ON is not immediately // On some devices, switching from TORCH/OFF to AUTO/ON is not immediately
// reflected (for example, torch stays active) unless we do as follows. // reflected (for example, torch stays active) unless we do as follows.
// It's just a way to wake up the AE routine. // It's just a way to wake up the AE routine.
builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, // TODO this works but seems to cause other issues.
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); // builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
// CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
return true; return true;
} }
} }
@ -1221,6 +1232,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (point != null) { if (point != null) {
mCallback.dispatchOnFocusEnd(mMeteringGesture, success, point); mCallback.dispatchOnFocusEnd(mMeteringGesture, success, point);
} else { } else {
LOG.w("onMeteringEnd - restoring the picture capturing. isSnapshot:", mDelayedPictureStub.isSnapshot);
if (mDelayedPictureStub.isSnapshot) { if (mDelayedPictureStub.isSnapshot) {
onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false); onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false);
} else { } else {
@ -1231,17 +1243,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
} }
} }
/**
* When metering is reset, we're not sure that the engine is still alive.
* We should check this here.
* @param point point
* @return true if metering can be reset
*/
@Override
public boolean canResetMetering(@Nullable PointF point) {
return getEngineState() == STATE_STARTED;
}
/** /**
* Called by {@link Meter} after resetting the metering parameters. * Called by {@link Meter} after resetting the metering parameters.
* We should apply them, and also go back to default focus. * We should apply them, and also go back to default focus.
@ -1250,9 +1251,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
*/ */
@Override @Override
public void onMeteringReset(@Nullable PointF point) { public void onMeteringReset(@Nullable PointF point) {
if (getEngineState() == STATE_STARTED) {
applyDefaultFocus(mRepeatingRequestBuilder); applyDefaultFocus(mRepeatingRequestBuilder);
applyRepeatingRequestBuilder(); // only if preview started already applyRepeatingRequestBuilder(); // only if preview started already
} }
}
//endregion //endregion
} }

@ -70,15 +70,6 @@ public class Meter {
* *
*/ */
void onMeteringReset(@Nullable PointF point); void onMeteringReset(@Nullable PointF point);
/**
* Whether metering can be reset. Since it happens at a future time, this should
* return true if the engine is still in a legit state for this operation.
* @param point point
* @return true if can reset
*/
// TODO is this useful? engine could do its checks onMeteringReset()
boolean canResetMetering(@Nullable PointF point);
} }
private static final String TAG = Meter.class.getSimpleName(); private static final String TAG = Meter.class.getSimpleName();
@ -315,7 +306,6 @@ public class Meter {
* a reset call, or if {@link #startMetering(CaptureResult, PointF)} was never called. * a reset call, or if {@link #startMetering(CaptureResult, PointF)} was never called.
* @return true if metering * @return true if metering
*/ */
@SuppressWarnings("WeakerAccess")
public boolean isMetering() { public boolean isMetering() {
return mIsMetering; return mIsMetering;
} }
@ -361,7 +351,6 @@ public class Meter {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public void resetMetering() { public void resetMetering() {
mEngine.mHandler.remove(mResetRunnable); mEngine.mHandler.remove(mResetRunnable);
if (mCallback.canResetMetering(mPoint)) {
LOG.i("Resetting the meter parameters."); LOG.i("Resetting the meter parameters.");
MeteringRectangle whole = null; MeteringRectangle whole = null;
if (mPoint != null) { if (mPoint != null) {
@ -375,7 +364,6 @@ public class Meter {
mAutoExposure.resetMetering(mCharacteristics, mBuilder, whole); mAutoExposure.resetMetering(mCharacteristics, mBuilder, whole);
mCallback.onMeteringReset(mPoint); mCallback.onMeteringReset(mPoint);
} }
}
private Runnable mResetRunnable = new Runnable() { private Runnable mResetRunnable = new Runnable() {
@Override @Override

@ -38,14 +38,13 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
private static final int STATE_WAITING_CAPTURE = 1; private static final int STATE_WAITING_CAPTURE = 1;
private static final int STATE_WAITING_IMAGE = 2; private static final int STATE_WAITING_IMAGE = 2;
private static final int REQUEST_TAG = CameraDevice.TEMPLATE_STILL_CAPTURE;
private CameraCaptureSession mSession; private CameraCaptureSession mSession;
private CameraCaptureSession.CaptureCallback mCallback; private CameraCaptureSession.CaptureCallback mCallback;
private ImageReader mPictureReader; private ImageReader mPictureReader;
private CaptureRequest.Builder mPictureBuilder; private CaptureRequest.Builder mPictureBuilder;
private boolean mStopPreviewBeforeCapture; private boolean mStopPreviewBeforeCapture;
private int mState = STATE_IDLE; private int mState = STATE_IDLE;
private int mSequenceId;
public Full2PictureRecorder(@NonNull PictureResult.Stub stub, public Full2PictureRecorder(@NonNull PictureResult.Stub stub,
@Nullable PictureResultListener listener, @Nullable PictureResultListener listener,
@ -67,7 +66,6 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
public void take() { public void take() {
try { try {
mState = STATE_WAITING_CAPTURE; mState = STATE_WAITING_CAPTURE;
mPictureBuilder.setTag(REQUEST_TAG);
mPictureBuilder.addTarget(mPictureReader.getSurface()); mPictureBuilder.addTarget(mPictureReader.getSurface());
mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation); mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation);
if (mStopPreviewBeforeCapture) { if (mStopPreviewBeforeCapture) {
@ -79,7 +77,7 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
mSession.stopRepeating(); mSession.stopRepeating();
mSession.abortCaptures(); mSession.abortCaptures();
} }
mSession.capture(mPictureBuilder.build(), mCallback, null); mSequenceId = mSession.capture(mPictureBuilder.build(), mCallback, null);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
mResult = null; mResult = null;
mError = e; mError = e;
@ -88,14 +86,16 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
} }
public void onCaptureStarted(@NonNull CaptureRequest request) { public void onCaptureStarted(@NonNull CaptureRequest request) {
if (request.getTag() == (Integer) REQUEST_TAG) { if (mState == STATE_WAITING_CAPTURE) {
if (request.getTag() == mPictureBuilder.build().getTag()) {
dispatchOnShutter(false); dispatchOnShutter(false);
} }
} }
}
public void onCaptureCompleted(@NonNull TotalCaptureResult result) { public void onCaptureCompleted(@NonNull TotalCaptureResult result) {
if (mState == STATE_WAITING_CAPTURE) { if (mState == STATE_WAITING_CAPTURE) {
if (result.getRequest().getTag() == (Integer) REQUEST_TAG) { if (result.getSequenceId() == mSequenceId) {
// This has no real use for now other than logging. // This has no real use for now other than logging.
LOG.i("onCaptureCompleted:", "Got result, moving to STATE_WAITING_IMAGE"); LOG.i("onCaptureCompleted:", "Got result, moving to STATE_WAITING_IMAGE");
mState = STATE_WAITING_IMAGE; mState = STATE_WAITING_IMAGE;

@ -0,0 +1,104 @@
package com.otaliastudios.cameraview.picture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera2Engine;
import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.overlay.Overlay;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
private final static String TAG = Snapshot2PictureRecorder.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
private final static int STATE_IDLE = 0;
private final static int STATE_WAITING_CAPTURE = 1;
private final static int STATE_WAITING_IMAGE = 2;
private final Camera2Engine mEngine;
private final CameraCaptureSession mSession;
private final CameraCaptureSession.CaptureCallback mCallback;
private final CaptureRequest.Builder mBuilder;
private int mState = STATE_IDLE;
private Integer mOldCaptureIntent;
private int mSequenceId;
public Snapshot2PictureRecorder(@NonNull PictureResult.Stub stub,
@NonNull Camera2Engine engine,
@NonNull GlCameraPreview preview,
@NonNull AspectRatio outputRatio,
@NonNull CameraCaptureSession session,
@NonNull CameraCaptureSession.CaptureCallback callback,
@NonNull CaptureRequest.Builder builder) {
super(stub, engine, preview, outputRatio, engine.getOverlay());
mEngine = engine;
mSession = session;
mCallback = callback;
mBuilder = builder;
}
@Override
public void take() {
if (mEngine.getPictureSnapshotMetering()) {
try {
LOG.i("take:", "Engine does metering, adding our CONTROL_CAPTURE_INTENT.");
mState = STATE_WAITING_CAPTURE;
mOldCaptureIntent = mBuilder.get(CaptureRequest.CONTROL_CAPTURE_INTENT);
mBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
mSequenceId = mSession.setRepeatingRequest(mBuilder.build(), mCallback, null);
} catch (CameraAccessException e) {
LOG.w("take:", "Camera error while applying our CONTROL_CAPTURE_INTENT", e);
mResult = null;
mError = e;
dispatchResult();
}
} else {
super.take();
}
}
public void onCaptureCompleted(@NonNull TotalCaptureResult result) {
LOG.i("onCaptureCompleted:",
"aeState:", result.get(CaptureResult.CONTROL_AE_STATE),
"flashState:", result.get(CaptureResult.FLASH_STATE));
if (mState == STATE_WAITING_CAPTURE) {
if (result.getSequenceId() == mSequenceId) {
// This is the request we launched and we're waiting for the right frame.
LOG.w("onCaptureCompleted:",
"aeState:", result.get(CaptureResult.CONTROL_AE_STATE),
"flashState:", result.get(CaptureResult.FLASH_STATE));
mState = STATE_WAITING_IMAGE;
LOG.i("onCaptureCompleted:", "Got first result! Calling the GL recorder.");
super.take();
}
}
}
@Override
protected void dispatchResult() {
if (mState == STATE_WAITING_IMAGE) {
// Revert our changes.
LOG.i("dispatchResult:", "Reverting the capture intent changes.");
try {
mBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, mOldCaptureIntent);
mSession.setRepeatingRequest(mBuilder.build(), mCallback, null);
} catch (CameraAccessException ignore) {}
}
mState = STATE_IDLE;
super.dispatchResult();
}
}

@ -20,7 +20,7 @@
app:cameraPreview="glSurface" app:cameraPreview="glSurface"
app:cameraPlaySounds="true" app:cameraPlaySounds="true"
app:cameraGrid="off" app:cameraGrid="off"
app:cameraFlash="auto" app:cameraFlash="on"
app:cameraAudio="on" app:cameraAudio="on"
app:cameraFacing="back" app:cameraFacing="back"
app:cameraGestureTap="autoFocus" app:cameraGestureTap="autoFocus"
@ -29,6 +29,7 @@
app:cameraGestureScrollHorizontal="filterControl1" app:cameraGestureScrollHorizontal="filterControl1"
app:cameraGestureScrollVertical="exposureCorrection" app:cameraGestureScrollVertical="exposureCorrection"
app:cameraMode="picture" app:cameraMode="picture"
app:cameraPictureSnapshotMetering="true"
app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker"> app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker">
<!-- Watermark --> <!-- Watermark -->

Loading…
Cancel
Save