New angle classes, fix snapshot rotation

pull/494/head
Mattia Iavarone 6 years ago
parent f844040a29
commit f92c5b1e37
  1. 19
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java
  2. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  3. 81
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  4. 54
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  5. 123
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  6. 125
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/offset/Angles.java
  7. 28
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/offset/Axis.java
  8. 28
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/offset/Reference.java
  9. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot1PictureRecorder.java
  10. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  11. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  12. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/TextureCameraPreview.java
  13. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  14. 2
      demo/src/main/res/layout/activity_camera.xml

@ -177,23 +177,4 @@ public class MockCameraEngine extends CameraEngine {
protected boolean collectCameraInfo(@NonNull Facing facing) {
return true;
}
/*
@Override
public void setFacing(@NonNull Facing facing) {
mFacing = facing;
}
@Override
public void setMode(@NonNull Mode mode) {
mMode = mode;
}
@Override
public void setAudio(@NonNull Audio audio) {
mAudio = audio;
}
*/
}

@ -37,6 +37,7 @@ import com.otaliastudios.cameraview.controls.Engine;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.engine.Camera2Engine;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.markers.MarkerLayout;
import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.CameraEngine;
@ -342,7 +343,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Size previewSize = mCameraEngine.getPreviewStreamSize(CameraEngine.REF_VIEW);
Size previewSize = mCameraEngine.getPreviewStreamSize(Reference.VIEW);
if (previewSize == null) {
LOG.w("onMeasure:", "surface is not ready. Calling default behavior.");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@ -1549,12 +1550,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Get the preview size and crop according to the current view size.
// It's better to do calculations in the REF_VIEW reference, and then flip if needed.
Size preview = mCameraEngine.getUncroppedSnapshotSize(CameraEngine.REF_VIEW);
Size preview = mCameraEngine.getUncroppedSnapshotSize(Reference.VIEW);
if (preview == null) return null; // Should never happen.
AspectRatio viewRatio = AspectRatio.of(getWidth(), getHeight());
Rect crop = CropHelper.computeCrop(preview, viewRatio);
Size cropSize = new Size(crop.width(), crop.height());
if (mCameraEngine.flip(CameraEngine.REF_VIEW, CameraEngine.REF_OUTPUT)) {
if (mCameraEngine.getAngles().flip(Reference.VIEW, Reference.OUTPUT)) {
return cropSize.flip();
} else {
return cropSize;
@ -1573,7 +1574,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Nullable
public Size getPictureSize() {
return mCameraEngine.getPictureSize(CameraEngine.REF_OUTPUT);
return mCameraEngine.getPictureSize(Reference.OUTPUT);
}
@ -1588,7 +1589,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
*/
@Nullable
public Size getVideoSize() {
return mCameraEngine.getVideoSize(CameraEngine.REF_OUTPUT);
return mCameraEngine.getVideoSize(Reference.OUTPUT);
}

@ -3,7 +3,6 @@ package com.otaliastudios.cameraview.engine;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@ -15,7 +14,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import android.util.Log;
import android.view.SurfaceHolder;
import com.google.android.gms.tasks.Task;
@ -24,6 +22,8 @@ import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.controls.Engine;
import com.otaliastudios.cameraview.engine.offset.Axis;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.VideoResult;
@ -35,7 +35,6 @@ import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.internal.utils.CropHelper;
import com.otaliastudios.cameraview.internal.utils.Op;
import com.otaliastudios.cameraview.picture.Full1PictureRecorder;
import com.otaliastudios.cameraview.picture.Snapshot1PictureRecorder;
import com.otaliastudios.cameraview.picture.SnapshotGlPictureRecorder;
@ -124,7 +123,7 @@ public class Camera1Engine extends CameraEngine implements
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == internalFacing) {
mSensorOffset = cameraInfo.orientation;
setSensorOffset(facing, cameraInfo.orientation);
mCameraId = i;
return true;
}
@ -151,10 +150,10 @@ public class Camera1Engine extends CameraEngine implements
// Set parameters that might have been set before the camera was opened.
LOG.i("onStartEngine:", "Applying default parameters.");
Camera.Parameters params = mCamera.getParameters();
mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW));
mCameraOptions = new CameraOptions(params, getAngles().flip(Reference.SENSOR, Reference.VIEW));
applyAllParameters(params);
mCamera.setParameters(params);
mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW)); // <- not allowed during preview
mCamera.setDisplayOrientation(getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE)); // <- not allowed during preview
LOG.i("onStartEngine:", "Ended");
return Tasks.forResult(null);
}
@ -188,7 +187,7 @@ public class Camera1Engine extends CameraEngine implements
LOG.i("onStartPreview", "Dispatching onCameraPreviewStreamSizeChanged.");
mCallback.onCameraPreviewStreamSizeChanged();
Size previewSize = getPreviewStreamSize(REF_VIEW);
Size previewSize = getPreviewStreamSize(Reference.VIEW);
if (previewSize == null) {
throw new IllegalStateException("previewStreamSize should not be null at this point.");
}
@ -299,8 +298,8 @@ public class Camera1Engine extends CameraEngine implements
@WorkerThread
@Override
protected void onTakePicture(@NonNull PictureResult.Stub stub) {
stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
stub.size = getPictureSize(REF_OUTPUT);
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getPictureSize(Reference.OUTPUT);
mPictureRecorder = new Full1PictureRecorder(stub, Camera1Engine.this, mCamera);
mPictureRecorder.take();
}
@ -308,9 +307,9 @@ public class Camera1Engine extends CameraEngine implements
@WorkerThread
@Override
protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio) {
stub.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
stub.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0.
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
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.
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio);
@ -326,8 +325,8 @@ public class Camera1Engine extends CameraEngine implements
@Override
protected void onTakeVideo(@NonNull VideoResult.Stub stub) {
stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
stub.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getAngles().flip(Reference.SENSOR, Reference.OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
// Unlock the camera and start recording.
try {
mCamera.unlock();
@ -337,9 +336,7 @@ public class Camera1Engine extends CameraEngine implements
onVideoResult(null, e);
return;
}
if (!(mVideoRecorder instanceof Full1VideoRecorder)) {
mVideoRecorder = new Full1VideoRecorder(Camera1Engine.this, mCamera, mCameraId);
}
mVideoRecorder.start(stub);
}
@ -355,56 +352,19 @@ public class Camera1Engine extends CameraEngine implements
}
GlCameraPreview glPreview = (GlCameraPreview) mPreview;
// Size and rotation turned out to be extremely tricky. In case of Snapshot1PictureRecorder
// we use the preview size in REF_OUTPUT (cropped) and offset(REF_SENSOR, REF_OUTPUT) as rotation.
// These values mean that we expect input to be in the REF_SENSOR system.
// Here everything seems different. We would expect a difference because the two snapshot
// recorders have different mechanics (the picture one uses a SurfaceTexture with setBufferSize,
// the video one here uses the MediaCodec input surface which we can't control).
// The strangest thing is the fact that the correct angle seems to be the same for FRONT and
// BACK sensor, which means that our sensor correction actually screws things up. For this reason
// facing value is temporarily set to BACK.
Facing realFacing = mFacing;
mFacing = Facing.BACK;
// These are the angles that make it work on a Nexus5X, compared to the offset() results.
// For instance, SV means offset(REF_SENSOR, REF_VIEW). The rest should be clear.
// CONFIG | WANTED | SV | VS | VO | OV | SO | OS |
// ------------|--------|--------|--------|--------|--------|--------|--------|
// Vertical | 0 | 270 | 90 | 0 | 0 | 270 | 90 |
// Left | 270 | 270 | 90 | 270 | 90 | 180 | 180 |
// Right | 90 | 270 | 90 | 90 | 270 | 0 | 0 |
// Upside down | 180 | 270 | 90 | 180 | 180 | 90 | 270 |
// The VO is the only correct value. Things change when using FRONT camera, in which case,
// no value is actually correct, and the needed values are the same of BACK!
// CONFIG | WANTED | SV | VS | VO | OV | SO | OS |
// ------------|--------|--------|--------|--------|--------|--------|--------|
// Vertical | 0 | 90 | 270 | 180 | 180 | 270 | 90 |
// Left | 270 | 90 | 270 | 270 | 90 | 0 | 0 |
// Right | 90 | 90 | 270 | 90 | 270 | 180 | 180 |
// Upside down | 180 | 90 | 270 | 0 | 0 | 90 | 270 |
// Based on this we will use VO for everything. See if we get issues about distortion
// and maybe we can improve. The reason why this happen is beyond my understanding.
Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT);
// Output size is easy:
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT);
if (outputSize == null) {
throw new IllegalStateException("outputSize should not be null.");
}
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height());
stub.size = outputSize;
stub.rotation = offset(REF_VIEW, REF_OUTPUT);
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
// Reset facing and start.
mFacing = realFacing;
if (!(mVideoRecorder instanceof SnapshotVideoRecorder)) {
// Start.
mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview);
}
mVideoRecorder.start(stub);
}
@ -691,7 +651,7 @@ public class Camera1Engine extends CameraEngine implements
public void onPreviewFrame(@NonNull byte[] data, Camera camera) {
Frame frame = getFrameManager().getFrame(data,
System.currentTimeMillis(),
offset(REF_SENSOR, REF_OUTPUT),
getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR),
mPreviewStreamSize,
PREVIEW_FORMAT);
mCallback.dispatchFrame(frame);
@ -718,8 +678,9 @@ public class Camera1Engine extends CameraEngine implements
if (getEngineState() < STATE_STARTED) return;
if (!mCameraOptions.isAutoFocusSupported()) return;
final PointF p = new PointF(point.x, point.y); // copy.
int offset = getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE);
List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y,
viewWidthF, viewHeightF, offset(REF_SENSOR, REF_VIEW));
viewWidthF, viewHeightF, offset);
List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1);
// At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().

@ -44,6 +44,8 @@ import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Hdr;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.controls.WhiteBalance;
import com.otaliastudios.cameraview.engine.offset.Axis;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameManager;
import com.otaliastudios.cameraview.gesture.Gesture;
@ -299,7 +301,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) {
mCameraId = cameraId;
mSensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0);
int sensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0);
setSensorOffset(facing, sensorOffset);
return true;
}
} catch (CameraAccessException ignore) {
@ -330,7 +333,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
try {
LOG.i("createCamera:", "Applying default parameters.");
mCameraCharacteristics = mManager.getCameraCharacteristics(mCameraId);
mCameraOptions = new CameraOptions(mManager, mCameraId, flip(REF_SENSOR, REF_VIEW));
boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW);
mCameraOptions = new CameraOptions(mManager, mCameraId, flip);
createRepeatingRequestBuilder(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
task.trySetException(createCameraException(e));
@ -489,12 +493,12 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
LOG.i("onStartPreview", "Dispatching onCameraPreviewStreamSizeChanged.");
mCallback.onCameraPreviewStreamSizeChanged();
Size previewSizeForView = getPreviewStreamSize(REF_VIEW);
Size previewSizeForView = getPreviewStreamSize(Reference.VIEW);
if (previewSizeForView == null) {
throw new IllegalStateException("previewStreamSize should not be null at this point.");
}
mPreview.setStreamSize(previewSizeForView.getWidth(), previewSizeForView.getHeight());
mPreview.setDrawRotation(getDisplayOffset());
mPreview.setDrawRotation(getAngles().offset(Reference.BASE, Reference.VIEW, Axis.ABSOLUTE));
if (hasFrameProcessors()) {
getFrameManager().setUp(ImageFormat.getBitsPerPixel(FRAME_PROCESSING_FORMAT), mFrameProcessingSize);
}
@ -613,9 +617,9 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@WorkerThread
@Override
protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio) {
stub.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
stub.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0.
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
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.
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
if (mPreview instanceof GlCameraPreview) {
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio);
} else {
@ -626,8 +630,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override
protected void onTakePicture(@NonNull PictureResult.Stub stub) {
stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
stub.size = getPictureSize(REF_OUTPUT);
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getPictureSize(Reference.OUTPUT);
try {
CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
applyAllParameters(builder);
@ -661,8 +665,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override
protected void onTakeVideo(@NonNull VideoResult.Stub stub) {
LOG.i("onTakeVideo", "called.");
stub.rotation = offset(REF_SENSOR, REF_OUTPUT);
stub.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR);
stub.size = getAngles().flip(Reference.SENSOR, Reference.OUTPUT) ? mCaptureSize.flip() : mCaptureSize;
if (!Full2VideoRecorder.SUPPORTS_PERSISTENT_SURFACE) {
// On API 21 and 22, we must restart the session at each time.
// Save the pending data and restart the session.
@ -675,9 +679,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
}
private void doTakeVideo(@NonNull final VideoResult.Stub stub) {
if (!(mVideoRecorder instanceof Full2VideoRecorder)) {
mVideoRecorder = new Full2VideoRecorder(this, mCameraId, mFullVideoPersistentSurface);
}
Full2VideoRecorder recorder = (Full2VideoRecorder) mVideoRecorder;
try {
createRepeatingRequestBuilder(CameraDevice.TEMPLATE_RECORD);
@ -697,10 +699,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
}
}
/**
* See {@link CameraEngine#onTakeVideoSnapshot(VideoResult.Stub, AspectRatio)}
* to read about the size and rotation computation.
*/
@WorkerThread
@Override
protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull AspectRatio viewAspectRatio) {
@ -708,23 +706,20 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview.");
}
GlCameraPreview glPreview = (GlCameraPreview) mPreview;
Facing realFacing = mFacing;
mFacing = Facing.BACK;
Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT);
// Output size is easy:
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT);
if (outputSize == null) {
throw new IllegalStateException("outputSize should not be null.");
}
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio);
outputSize = new Size(outputCrop.width(), outputCrop.height());
stub.size = outputSize;
stub.rotation = offset(REF_VIEW, REF_OUTPUT);
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
// Reset facing and start.
mFacing = realFacing;
if (!(mVideoRecorder instanceof SnapshotVideoRecorder)) {
// Start.
mVideoRecorder = new SnapshotVideoRecorder(this, glPreview);
}
mVideoRecorder.start(stub);
}
@ -1052,7 +1047,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (getEngineState() == STATE_STARTED) {
Frame frame = getFrameManager().getFrame(data,
System.currentTimeMillis(),
offset(REF_SENSOR, REF_OUTPUT),
getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR),
mFrameProcessingSize,
FRAME_PROCESSING_FORMAT);
mCallback.dispatchFrame(frame);
@ -1101,12 +1096,11 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// This is a good Q/A. https://stackoverflow.com/a/33181620/4288782
// At first, the point is relative to the View system and does not account our own cropping.
// Will keep updating these two below.
//noinspection UnnecessaryLocalVariable
PointF referencePoint = new PointF(point.x, point.y);
Size referenceSize /* = previewSurfaceSize */;
// 1. Account for cropping.
Size previewStreamSize = getPreviewStreamSize(REF_VIEW);
Size previewStreamSize = getPreviewStreamSize(Reference.VIEW);
Size previewSurfaceSize = mPreview.getSurfaceSize();
if (previewStreamSize == null) throw new IllegalStateException("getPreviewStreamSize should not be null at this point.");
AspectRatio previewStreamAspectRatio = AspectRatio.of(previewStreamSize);
@ -1133,7 +1127,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// 3. Rotate to the stream coordinate system.
// Not elegant, but the sin/cos way was failing.
int angle = offset(REF_SENSOR, REF_VIEW);
int angle = getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE);
boolean flip = angle % 180 != 0;
float tempX = referencePoint.x; float tempY = referencePoint.y;
if (angle == 0) {

@ -1,15 +1,12 @@
package com.otaliastudios.cameraview.engine;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.PointF;
import android.hardware.camera2.CameraCharacteristics;
import android.location.Location;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.google.android.gms.tasks.Continuation;
import com.google.android.gms.tasks.OnCompleteListener;
@ -22,6 +19,8 @@ import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.engine.offset.Angles;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameManager;
import com.otaliastudios.cameraview.internal.utils.Op;
@ -147,10 +146,6 @@ public abstract class CameraEngine implements
public static final int STATE_STARTING = CameraEngineStep.STATE_STARTING;
public static final int STATE_STARTED = CameraEngineStep.STATE_STARTED;
public static final int REF_SENSOR = 0;
public static final int REF_VIEW = 1;
public static final int REF_OUTPUT = 2;
// Need to be protected
@SuppressWarnings("WeakerAccess") protected WorkerHandler mHandler;
@SuppressWarnings("WeakerAccess") protected final Callback mCallback;
@ -161,7 +156,6 @@ public abstract class CameraEngine implements
@SuppressWarnings("WeakerAccess") protected VideoRecorder mVideoRecorder;
@SuppressWarnings("WeakerAccess") protected Size mCaptureSize;
@SuppressWarnings("WeakerAccess") protected Size mPreviewStreamSize;
@SuppressWarnings("WeakerAccess") protected Facing mFacing;
@SuppressWarnings("WeakerAccess") protected Flash mFlash;
@SuppressWarnings("WeakerAccess") protected WhiteBalance mWhiteBalance;
@SuppressWarnings("WeakerAccess") protected VideoCodec mVideoCodec;
@ -170,14 +164,15 @@ public abstract class CameraEngine implements
@SuppressWarnings("WeakerAccess") protected float mZoomValue;
@SuppressWarnings("WeakerAccess") protected float mExposureCorrectionValue;
@SuppressWarnings("WeakerAccess") protected boolean mPlaySounds;
@SuppressWarnings("WeakerAccess") protected int mSensorOffset;
// Can be private
@VisibleForTesting Handler mCrashHandler;
private final FrameManager mFrameManager;
private final Angles mAngles;
@Nullable private SizeSelector mPreviewStreamSizeSelector;
private SizeSelector mPictureSizeSelector;
private SizeSelector mVideoSizeSelector;
private Facing mFacing;
private Mode mMode;
private Audio mAudio;
private long mVideoMaxSize;
@ -188,8 +183,6 @@ public abstract class CameraEngine implements
private long mAutoFocusResetDelayMillis;
private int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
private int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
private int mDisplayOffset;
private int mDeviceOrientation;
// Steps
private final CameraEngineStep.Callback mStepCallback = new CameraEngineStep.Callback() {
@ -219,6 +212,7 @@ public abstract class CameraEngine implements
mHandler = WorkerHandler.get("CameraViewEngine");
mHandler.getThread().setUncaughtExceptionHandler(new CrashExceptionHandler());
mFrameManager = instantiateFrameManager();
mAngles = new Angles();
}
public void setPreview(@NonNull CameraPreview cameraPreview) {
@ -573,7 +567,7 @@ public abstract class CameraEngine implements
*/
@Override
public final void onSurfaceAvailable() {
LOG.i("onSurfaceAvailable:", "Size is", getPreviewSurfaceSize(REF_VIEW));
LOG.i("onSurfaceAvailable:", "Size is", getPreviewSurfaceSize(Reference.VIEW));
mHandler.run(new Runnable() {
@Override
public void run() {
@ -590,7 +584,7 @@ public abstract class CameraEngine implements
@Override
public final void onSurfaceChanged() {
LOG.i("onSurfaceChanged:", "Size is", getPreviewSurfaceSize(REF_VIEW), "Posting.");
LOG.i("onSurfaceChanged:", "Size is", getPreviewSurfaceSize(Reference.VIEW), "Posting.");
mHandler.run(new Runnable() {
@Override
public void run() {
@ -786,19 +780,24 @@ public abstract class CameraEngine implements
//region Final setters and getters
// This is called before start() and never again.
public final void setDisplayOffset(int displayOffset) {
mDisplayOffset = displayOffset;
@SuppressWarnings("WeakerAccess")
public final Angles getAngles() {
return mAngles;
}
@SuppressWarnings("WeakerAccess")
public final int getDisplayOffset() {
return mDisplayOffset;
protected final void setSensorOffset(@NonNull Facing facing, int sensorOffset) {
mAngles.setSensorOffset(facing, sensorOffset);
}
// This is called before start() and never again.
public final void setDisplayOffset(int displayOffset) {
mAngles.setDisplayOffset(displayOffset);
}
// This can be called multiple times.
public final void setDeviceOrientation(int deviceOrientation) {
mDeviceOrientation = deviceOrientation;
mAngles.setDeviceOrientation(deviceOrientation);
}
public final void setPreviewStreamSizeSelector(@Nullable SizeSelector selector) {
@ -1019,8 +1018,8 @@ public abstract class CameraEngine implements
* Camera is about to be opened. Implementors should look into available cameras
* and see if anyone matches the given {@link Facing value}.
*
* If so, implementors should set {@link #mSensorOffset} and any other information
* (like camera ID) needed to start teh engine.
* If so, implementors should set {@link #setSensorOffset(Facing, int)} and any other information
* (like camera ID) needed to start the engine.
*
* @param facing the facing value
* @return true if we have one
@ -1087,7 +1086,7 @@ public abstract class CameraEngine implements
}
/**
* The snapshot size is the {@link #getPreviewStreamSize(int)}, but cropped based on the
* The snapshot size is the {@link #getPreviewStreamSize(Reference)}, but cropped based on the
* view/surface aspect ratio.
* @param stub a picture stub
* @param viewAspectRatio the view aspect ratio
@ -1104,9 +1103,6 @@ public abstract class CameraEngine implements
stub.isSnapshot = true;
stub.facing = mFacing;
// Leave the other parameters to subclasses.
LOG.v("takePictureSnapshot", "Rotations", "SV", offset(REF_SENSOR, REF_VIEW), "VS", offset(REF_VIEW, REF_SENSOR));
LOG.v("takePictureSnapshot", "Rotations", "SO", offset(REF_SENSOR, REF_OUTPUT), "OS", offset(REF_OUTPUT, REF_SENSOR));
LOG.v("takePictureSnapshot", "Rotations", "VO", offset(REF_VIEW, REF_OUTPUT), "OV", offset(REF_OUTPUT, REF_VIEW));
onTakePictureSnapshot(stub, viewAspectRatio);
}
});
@ -1228,73 +1224,31 @@ public abstract class CameraEngine implements
//endregion
//region Orientation utils
private int computeSensorToViewOffset() {
if (mFacing == Facing.FRONT) {
return (360 - ((mSensorOffset + mDisplayOffset) % 360)) % 360;
} else {
return (mSensorOffset - mDisplayOffset + 360) % 360;
}
}
private int computeSensorToOutputOffset() {
if (mFacing == Facing.FRONT) {
return (mSensorOffset - mDeviceOrientation + 360) % 360;
} else {
return (mSensorOffset + mDeviceOrientation) % 360;
}
}
// o(S, V) - o(S, O)
// displayOffset - deviceOrientation
// Returns the offset between two reference systems.
@SuppressWarnings("WeakerAccess")
public final int offset(int fromReference, int toReference) {
if (fromReference == toReference) return 0;
// We only know how to compute offsets with respect to REF_SENSOR.
// That's why we separate the two cases.
if (fromReference == REF_SENSOR) {
return toReference == REF_VIEW ?
computeSensorToViewOffset() :
computeSensorToOutputOffset();
}
// Maybe the sensor is the other.
if (toReference == REF_SENSOR) {
return (-offset(toReference, fromReference) + 360) % 360;
}
// None of them is the sensor. Use a difference.
return (offset(REF_SENSOR, toReference) - offset(REF_SENSOR, fromReference) + 360) % 360;
}
public final boolean flip(int reference1, int reference2) {
return offset(reference1, reference2) % 180 != 0;
}
//region Size utilities
@Nullable
public final Size getPictureSize(@SuppressWarnings("SameParameterValue") int reference) {
public final Size getPictureSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) {
if (mCaptureSize == null || mMode == Mode.VIDEO) return null;
return flip(REF_SENSOR, reference) ? mCaptureSize.flip() : mCaptureSize;
return getAngles().flip(Reference.SENSOR, reference) ? mCaptureSize.flip() : mCaptureSize;
}
@Nullable
public final Size getVideoSize(@SuppressWarnings("SameParameterValue") int reference) {
public final Size getVideoSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) {
if (mCaptureSize == null || mMode == Mode.PICTURE) return null;
return flip(REF_SENSOR, reference) ? mCaptureSize.flip() : mCaptureSize;
return getAngles().flip(Reference.SENSOR, reference) ? mCaptureSize.flip() : mCaptureSize;
}
@Nullable
public final Size getPreviewStreamSize(int reference) {
public final Size getPreviewStreamSize(@NonNull Reference reference) {
if (mPreviewStreamSize == null) return null;
return flip(REF_SENSOR, reference) ? mPreviewStreamSize.flip() : mPreviewStreamSize;
return getAngles().flip(Reference.SENSOR, reference) ? mPreviewStreamSize.flip() : mPreviewStreamSize;
}
@SuppressWarnings("SameParameterValue")
@Nullable
private Size getPreviewSurfaceSize(int reference) {
private Size getPreviewSurfaceSize(@NonNull Reference reference) {
if (mPreview == null) return null;
return flip(REF_VIEW, reference) ? mPreview.getSurfaceSize().flip() : mPreview.getSurfaceSize();
return getAngles().flip(Reference.VIEW, reference) ? mPreview.getSurfaceSize().flip() : mPreview.getSurfaceSize();
}
/**
@ -1318,10 +1272,10 @@ public abstract class CameraEngine implements
* apply, despite the capturing mechanism being different.
*/
@Nullable
public final Size getUncroppedSnapshotSize(int reference) {
public final Size getUncroppedSnapshotSize(@NonNull Reference reference) {
Size baseSize = getPreviewStreamSize(reference);
if (baseSize == null) return null;
boolean flip = flip(reference, REF_VIEW);
boolean flip = getAngles().flip(reference, Reference.VIEW);
int maxWidth = flip ? mSnapshotMaxHeight : mSnapshotMaxWidth;
int maxHeight = flip ? mSnapshotMaxWidth : mSnapshotMaxHeight;
float baseRatio = AspectRatio.of(baseSize).toFloat();
@ -1339,11 +1293,6 @@ public abstract class CameraEngine implements
}
}
//endregion
//region Size utils
/**
* This is called either on cameraView.start(), or when the underlying surface changes.
* It is possible that in the first call the preview surface has not already computed its
@ -1358,10 +1307,10 @@ public abstract class CameraEngine implements
}
@SuppressWarnings("WeakerAccess")
protected final Size computeCaptureSize(Mode mode) {
protected final Size computeCaptureSize(@NonNull Mode mode) {
// We want to pass stuff into the REF_VIEW reference, not the sensor one.
// This is already managed by CameraOptions, so we just flip again at the end.
boolean flip = flip(REF_SENSOR, REF_VIEW);
boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW);
SizeSelector selector;
Collection<Size> sizes;
if (mode == Mode.PICTURE) {
@ -1397,7 +1346,7 @@ public abstract class CameraEngine implements
@NonNull List<Size> previewSizes = getPreviewStreamAvailableSizes();
// These sizes come in REF_SENSOR. Since there is an external selector involved,
// we must convert all of them to REF_VIEW, then flip back when returning.
boolean flip = flip(REF_SENSOR, REF_VIEW);
boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW);
List<Size> sizes = new ArrayList<>(previewSizes.size());
for (Size size : previewSizes) {
sizes.add(flip ? size.flip() : size);
@ -1405,7 +1354,7 @@ public abstract class CameraEngine implements
// Create our own default selector, which will be used if the external mPreviewStreamSizeSelector
// is null, or if it fails in finding a size.
Size targetMinSize = getPreviewSurfaceSize(REF_VIEW);
Size targetMinSize = getPreviewSurfaceSize(Reference.VIEW);
if (targetMinSize == null) throw new IllegalStateException("targetMinSize should not be null here.");
AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight());
if (flip) targetRatio = targetRatio.flip();

@ -0,0 +1,125 @@
package com.otaliastudios.cameraview.engine.offset;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.controls.Facing;
public class Angles {
private Facing mSensorFacing;
private int mSensorOffset = 0;
private int mDisplayOffset = 0;
private int mDeviceOrientation = 0;
/**
* We want to keep everything in the {@link Axis#ABSOLUTE} reference,
* so a front facing sensor offset must be inverted.
*
* @param sensorFacing sensor facing value
* @param sensorOffset sensor offset
*/
public void setSensorOffset(@NonNull Facing sensorFacing, int sensorOffset) {
sanitizeInput(sensorOffset);
mSensorFacing = sensorFacing;
mSensorOffset = sensorOffset;
if (mSensorFacing == Facing.FRONT) {
mSensorOffset = sanitizeOutput(360 - mSensorOffset);
}
}
/**
* Sets the display offset.
* @param displayOffset the display offset
*/
public void setDisplayOffset(int displayOffset) {
sanitizeInput(displayOffset);
mDisplayOffset = displayOffset;
}
/**
* Sets the device orientation.
* @param deviceOrientation the device orientation
*/
public void setDeviceOrientation(int deviceOrientation) {
sanitizeInput(deviceOrientation);
mDeviceOrientation = deviceOrientation;
}
/**
* Returns the offset between two reference systems, computed along the given axis.
* @param from the source reference system
* @param to the destination reference system
* @param axis the axis
* @return the offset
*/
public int offset(@NonNull Reference from, @NonNull Reference to, @NonNull Axis axis) {
int offset = absoluteOffset(from, to);
if (axis == Axis.RELATIVE_TO_SENSOR) {
if (mSensorFacing == Facing.FRONT) {
offset = sanitizeOutput(360 - offset);
}
}
return offset;
}
/**
* Old results:
* offset(REF_SENSOR, REF_OUTPUT, Facing.BACK) = mSensorOffset + mDeviceOrientation
* offset(REF_SENSOR, REF_OUTPUT, Facing.FRONT) = -mSensorOffset - mDeviceOrientation
* offset(REF_SENSOR, REF_VIEW, Facing.BACK) = mSensorOffset - mDisplayOffset
* offset(REF_SENSOR, REF_VIEW, Facing.FRONT) = mSensorOffset - mDisplayOffset
* offset(REF_OUTPUT, REF_VIEW, Facing.BACK) = (mSensorOffset - mDisplayOffset) - (mSensorOffset + mDeviceOrientation) = -mDisplayOffset - mDeviceOrientation
* offset(REF_OUTPUT, REF_VIEW, Facing.FRONT) = (mSensorOffset - mDisplayOffset) - (-mSensorOffset - mDeviceOrientation) = 2*mSensorOffset + mDisplayOffset + mDeviceOrientation
*/
/**
* New results:
* offset(REF_SENSOR, REF_OUTPUT) = mDeviceOrientation + mSensorOffset
* -> REF_SENSOR, REF_OUTPUT is typically wanted in the RELATIVE_TO_SENSOR axis.
*
* offset(REF_SENSOR, REF_VIEW) = -mDisplayOffset + mSensorOffset
* -> REF_SENSOR, REF_VIEW is typically wanted in the ABSOLUTE axis.
*/
private int absoluteOffset(@NonNull Reference from, @NonNull Reference to) {
if (from == to) {
return 0;
} else if (to == Reference.BASE) {
return 360 - absoluteOffset(to, from);
} else if (from == Reference.BASE) {
switch (to) {
case VIEW: return sanitizeOutput(360 - mDisplayOffset);
case OUTPUT: return sanitizeOutput(mDeviceOrientation);
case SENSOR: return sanitizeOutput(360 - mSensorOffset);
default: throw new RuntimeException("Unknown reference: " + to);
}
} else {
return sanitizeOutput(
absoluteOffset(Reference.BASE, to)
- absoluteOffset(Reference.BASE, from));
}
}
/**
* Whether the two references systems are flipped.
* @param from source
* @param to destination
* @return true if flipped
*/
public boolean flip(@NonNull Reference from, @NonNull Reference to) {
return offset(from, to, Axis.ABSOLUTE) % 180 != 0;
}
private void sanitizeInput(int value) {
if (value != 0
&& value != 90
&& value != 180
&& value != 270) {
throw new IllegalStateException("This value is not sanitized: " + value);
}
}
private int sanitizeOutput(int value) {
return (value + 360) % 360;
}
}

@ -0,0 +1,28 @@
package com.otaliastudios.cameraview.engine.offset;
/**
* The axis around which offsets are computed. We have two possibilities:
* - an axis going out of the device screen towards the user's face
* - an axis going out of the device screen towards the back
*
* We are mostly interested in the first one, but some APIs will require
* angles in the sensor reference, in which case, for front cameras, we are
* required to use {@link #RELATIVE_TO_SENSOR}.
*/
public enum Axis {
/**
* This rotation axis is the one going out of the device screen
* towards the user's face.
*/
ABSOLUTE,
/**
* This rotation axis takes into account the current
* {@link com.otaliastudios.cameraview.controls.Facing} value.
*
* - for {@link com.otaliastudios.cameraview.controls.Facing#BACK}, this equals {@link #ABSOLUTE}
* - for {@link com.otaliastudios.cameraview.controls.Facing#FRONT}, this is inverted
*/
RELATIVE_TO_SENSOR
}

@ -0,0 +1,28 @@
package com.otaliastudios.cameraview.engine.offset;
public enum Reference {
/**
* The base reference system has its 'north' aligned with the device natural
* orientation.
*/
BASE,
/**
* This reference system has its 'north' aligned with the camera sensor.
*/
SENSOR,
/**
* This reference system has its 'north' aligned with the View hierarchy.
* This can be different than {@link #BASE} if the activity is allowed to rotate
* (or forced into a non natural position).
*/
VIEW,
/**
* This reference system has its 'north' aligned with the output picture/video.
* This means that it takes into account the device orientation.
*/
OUTPUT
}

@ -9,6 +9,7 @@ import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.internal.utils.CropHelper;
import com.otaliastudios.cameraview.internal.utils.RotationHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
@ -57,7 +58,7 @@ public class Snapshot1PictureRecorder extends PictureRecorder {
// Adding EXIF to a byte array, unfortunately, is hard.
final int sensorToOutput = mResult.rotation;
final Size outputSize = mResult.size;
final Size previewStreamSize = mEngine1.getPreviewStreamSize(CameraEngine.REF_SENSOR);
final Size previewStreamSize = mEngine1.getPreviewStreamSize(Reference.SENSOR);
if (previewStreamSize == null) {
throw new IllegalStateException("Preview stream size should never be null here.");
}

@ -4,7 +4,6 @@ import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.Matrix;
@ -13,14 +12,13 @@ import android.os.Build;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.internal.egl.EglCore;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.internal.egl.EglWindowSurface;
import com.otaliastudios.cameraview.internal.utils.CropHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import com.otaliastudios.cameraview.preview.CameraPreview;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback;
import com.otaliastudios.cameraview.preview.RendererThread;
@ -107,7 +105,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
// Apply scale and crop:
// NOTE: scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR.
boolean flip = mEngine.flip(CameraEngine.REF_VIEW, CameraEngine.REF_SENSOR);
boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR);
float realScaleX = flip ? scaleY : scaleX;
float realScaleY = flip ? scaleX : scaleY;
float scaleTranslX = (1F - realScaleX) / 2F;

@ -190,7 +190,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
// See TextureCameraPreview.setDrawRotation() for info.
if (mDrawRotation != 0) {
Matrix.translateM(mTransformMatrix, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(mTransformMatrix, 0, -mDrawRotation, 0, 0, 1);
Matrix.rotateM(mTransformMatrix, 0, mDrawRotation, 0, 0, 1);
Matrix.translateM(mTransformMatrix, 0, -0.5F, -0.5F, 0);
}

@ -140,7 +140,7 @@ public class TextureCameraPreview extends CameraPreview<TextureView, SurfaceText
float scaleX = (float) mOutputSurfaceHeight / mOutputSurfaceWidth;
matrix.postScale(scaleX, 1F / scaleX, outputCenterX, outputCenterY);
}
matrix.postRotate((float) -drawRotation, outputCenterX, outputCenterY);
matrix.postRotate((float) drawRotation, outputCenterX, outputCenterY);
getView().setTransform(matrix);
task.setResult(null);
}

@ -4,11 +4,11 @@ import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.os.Build;
import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback;
import com.otaliastudios.cameraview.preview.RendererThread;
@ -58,7 +58,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
@Override
protected void onStart() {
mPreview.addRendererFrameCallback(this);
mFlipped = mEngine.flip(CameraEngine.REF_SENSOR, CameraEngine.REF_VIEW);
mFlipped = mEngine.getAngles().flip(Reference.SENSOR, Reference.VIEW);
mDesiredState = STATE_RECORDING;
}
@ -128,8 +128,11 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
TextureMediaEncoder.TextureFrame textureFrame = textureEncoder.acquireFrame();
textureFrame.timestamp = surfaceTexture.getTimestamp();
surfaceTexture.getTransformMatrix(textureFrame.transform);
if (mEncoderEngine != null) {
// can happen on teardown
mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, textureFrame);
}
}
if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) {
LOG.i("Stopping the encoder engine.");

@ -17,7 +17,7 @@
android:layout_marginBottom="88dp"
android:keepScreenOn="true"
app:cameraExperimental="true"
app:cameraEngine="camera2"
app:cameraEngine="camera1"
app:cameraPreview="glSurface"
app:cameraPlaySounds="true"
app:cameraGrid="off"

Loading…
Cancel
Save