Add Snapshot2PictureRecorder

pull/580/head
Mattia Iavarone 6 years ago
parent 123d5456b6
commit c405f4cd1a
  1. 51
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 34
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java
  3. 14
      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.WorkerHandler;
import com.otaliastudios.cameraview.picture.Full2PictureRecorder;
import com.otaliastudios.cameraview.picture.Snapshot2PictureRecorder;
import com.otaliastudios.cameraview.picture.SnapshotGlPictureRecorder;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.size.AspectRatio;
@ -81,7 +82,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
private CameraCharacteristics mCameraCharacteristics;
private CameraCaptureSession mSession;
private CaptureRequest.Builder mRepeatingRequestBuilder;
private CaptureRequest mRepeatingRequest;
private CameraCaptureSession.CaptureCallback mRepeatingRequestCallback;
private CaptureResult mLastRepeatingResult;
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) {
if (!checkStarted || getPreviewState() == STATE_STARTED) {
try {
mRepeatingRequest = mRepeatingRequestBuilder.build();
CaptureRequest repeatingRequest = mRepeatingRequestBuilder.build();
final AtomicBoolean firstFrame = new AtomicBoolean(false);
mRepeatingRequestCallback = new CameraCaptureSession.CaptureCallback() {
@Override
@ -245,13 +245,16 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (mPictureRecorder instanceof Full2PictureRecorder) {
((Full2PictureRecorder) mPictureRecorder).onCaptureCompleted(result);
}
if (mPictureRecorder instanceof Snapshot2PictureRecorder) {
((Snapshot2PictureRecorder) mPictureRecorder).onCaptureCompleted(result);
}
if (mMeter != null && mMeter.isMetering()) {
mMeter.onCapture(result);
}
}
};
mSession.setRepeatingRequest(mRepeatingRequest, mRepeatingRequestCallback, null);
mSession.setRepeatingRequest(repeatingRequest, mRepeatingRequestCallback, null);
} catch (CameraAccessException e) {
throw new CameraException(e, errorReason);
}
@ -543,6 +546,10 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mVideoRecorder = null;
}
mPictureRecorder = null;
if (mMeter != null) {
mMeter.resetMetering();
mMeter = null;
}
if (hasFrameProcessors()) {
getFrameManager().release();
}
@ -557,8 +564,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
throw createCameraException(e);
}
removeRepeatingRequestBuilderSurfaces();
mRepeatingRequest = null;
mMeter = null;
LOG.i("onStopPreview:", "Returning.");
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.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0.
if (doMetering) {
LOG.i("onTakePictureSnapshot:", "doMetering is true. Delaying.");
mDelayedPictureStub = stub;
mDelayedPictureRatio = outputRatio;
startMetering(null, null);
} else {
LOG.i("onTakePictureSnapshot:", "doMetering is false. Performing.");
if (!(mPreview instanceof GlCameraPreview)) {
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,
outputRatio,
getOverlay());
mSession,
mRepeatingRequestCallback,
mRepeatingRequestBuilder);
mPictureRecorder.take();
}
}
@ -636,11 +645,12 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
protected void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering) {
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getPictureSize(Reference.OUTPUT);
if (doMetering) {
LOG.i("onTakePicture:", "doMetering is true. Delaying.");
mDelayedPictureStub = stub;
startMetering(null, null);
} else {
LOG.i("onTakePicture:", "doMetering is false. Performing.");
try {
CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
applyAllParameters(builder);
@ -771,7 +781,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
* the {@link #createRepeatingRequestBuilder(int)} method.
*/
private void maybeRestorePreviewTemplateAfterVideo() {
int template = (int) mRepeatingRequest.getTag();
int template = (int) mRepeatingRequestBuilder.build().getTag();
if (template != CameraDevice.TEMPLATE_PREVIEW) {
try {
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
// reflected (for example, torch stays active) unless we do as follows.
// It's just a way to wake up the AE routine.
builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
// TODO this works but seems to cause other issues.
// builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
// CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
return true;
}
}
@ -1221,6 +1232,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (point != null) {
mCallback.dispatchOnFocusEnd(mMeteringGesture, success, point);
} else {
LOG.w("onMeteringEnd - restoring the picture capturing. isSnapshot:", mDelayedPictureStub.isSnapshot);
if (mDelayedPictureStub.isSnapshot) {
onTakePictureSnapshot(mDelayedPictureStub, mDelayedPictureRatio, false);
} 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.
* We should apply them, and also go back to default focus.
@ -1250,8 +1251,10 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
*/
@Override
public void onMeteringReset(@Nullable PointF point) {
applyDefaultFocus(mRepeatingRequestBuilder);
applyRepeatingRequestBuilder(); // only if preview started already
if (getEngineState() == STATE_STARTED) {
applyDefaultFocus(mRepeatingRequestBuilder);
applyRepeatingRequestBuilder(); // only if preview started already
}
}
//endregion

@ -70,15 +70,6 @@ public class Meter {
*
*/
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();
@ -315,7 +306,6 @@ public class Meter {
* a reset call, or if {@link #startMetering(CaptureResult, PointF)} was never called.
* @return true if metering
*/
@SuppressWarnings("WeakerAccess")
public boolean isMetering() {
return mIsMetering;
}
@ -361,20 +351,18 @@ public class Meter {
@SuppressWarnings("WeakerAccess")
public void resetMetering() {
mEngine.mHandler.remove(mResetRunnable);
if (mCallback.canResetMetering(mPoint)) {
LOG.i("Resetting the meter parameters.");
MeteringRectangle whole = null;
if (mPoint != null) {
// If we have a point, we must reset the metering areas.
Rect wholeRect = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (wholeRect == null) wholeRect = new Rect();
whole = new MeteringRectangle(wholeRect, MeteringRectangle.METERING_WEIGHT_DONT_CARE);
}
mAutoFocus.resetMetering(mCharacteristics, mBuilder, whole);
mAutoWhiteBalance.resetMetering(mCharacteristics, mBuilder, whole);
mAutoExposure.resetMetering(mCharacteristics, mBuilder, whole);
mCallback.onMeteringReset(mPoint);
LOG.i("Resetting the meter parameters.");
MeteringRectangle whole = null;
if (mPoint != null) {
// If we have a point, we must reset the metering areas.
Rect wholeRect = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (wholeRect == null) wholeRect = new Rect();
whole = new MeteringRectangle(wholeRect, MeteringRectangle.METERING_WEIGHT_DONT_CARE);
}
mAutoFocus.resetMetering(mCharacteristics, mBuilder, whole);
mAutoWhiteBalance.resetMetering(mCharacteristics, mBuilder, whole);
mAutoExposure.resetMetering(mCharacteristics, mBuilder, whole);
mCallback.onMeteringReset(mPoint);
}
private Runnable mResetRunnable = new Runnable() {

@ -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_IMAGE = 2;
private static final int REQUEST_TAG = CameraDevice.TEMPLATE_STILL_CAPTURE;
private CameraCaptureSession mSession;
private CameraCaptureSession.CaptureCallback mCallback;
private ImageReader mPictureReader;
private CaptureRequest.Builder mPictureBuilder;
private boolean mStopPreviewBeforeCapture;
private int mState = STATE_IDLE;
private int mSequenceId;
public Full2PictureRecorder(@NonNull PictureResult.Stub stub,
@Nullable PictureResultListener listener,
@ -67,7 +66,6 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
public void take() {
try {
mState = STATE_WAITING_CAPTURE;
mPictureBuilder.setTag(REQUEST_TAG);
mPictureBuilder.addTarget(mPictureReader.getSurface());
mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation);
if (mStopPreviewBeforeCapture) {
@ -79,7 +77,7 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
mSession.stopRepeating();
mSession.abortCaptures();
}
mSession.capture(mPictureBuilder.build(), mCallback, null);
mSequenceId = mSession.capture(mPictureBuilder.build(), mCallback, null);
} catch (CameraAccessException e) {
mResult = null;
mError = e;
@ -88,14 +86,16 @@ public class Full2PictureRecorder extends PictureRecorder implements ImageReader
}
public void onCaptureStarted(@NonNull CaptureRequest request) {
if (request.getTag() == (Integer) REQUEST_TAG) {
dispatchOnShutter(false);
if (mState == STATE_WAITING_CAPTURE) {
if (request.getTag() == mPictureBuilder.build().getTag()) {
dispatchOnShutter(false);
}
}
}
public void onCaptureCompleted(@NonNull TotalCaptureResult result) {
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.
LOG.i("onCaptureCompleted:", "Got result, moving to 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:cameraPlaySounds="true"
app:cameraGrid="off"
app:cameraFlash="auto"
app:cameraFlash="on"
app:cameraAudio="on"
app:cameraFacing="back"
app:cameraGestureTap="autoFocus"
@ -29,6 +29,7 @@
app:cameraGestureScrollHorizontal="filterControl1"
app:cameraGestureScrollVertical="exposureCorrection"
app:cameraMode="picture"
app:cameraPictureSnapshotMetering="true"
app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker">
<!-- Watermark -->

Loading…
Cancel
Save