Refactor picture package

pull/482/head
Mattia Iavarone 6 years ago
parent 3815d317bd
commit f52fae8652
  1. 28
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraUtils.java
  2. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  3. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  4. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java
  5. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglCore.java
  6. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglWindowSurface.java
  7. 38
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/ExifHelper.java
  8. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java
  9. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/FullPictureRecorder.java
  10. 49
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/PictureRecorder.java
  11. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotPictureRecorder.java

@ -11,6 +11,7 @@ import android.os.Handler;
import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.engine.Mapper; import com.otaliastudios.cameraview.engine.Mapper;
import com.otaliastudios.cameraview.internal.utils.ExifHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler; import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -258,7 +259,7 @@ public class CameraUtils {
stream = new ByteArrayInputStream(source); stream = new ByteArrayInputStream(source);
ExifInterface exif = new ExifInterface(stream); ExifInterface exif = new ExifInterface(stream);
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
orientation = readExifOrientation(exifOrientation); orientation = ExifHelper.readExifOrientation(exifOrientation);
flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL ||
exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL ||
exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE || exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE ||
@ -315,31 +316,6 @@ public class CameraUtils {
return bitmap; return bitmap;
} }
private static int readExifOrientation(int exifOrientation) {
int orientation;
switch (exifOrientation) {
case ExifInterface.ORIENTATION_NORMAL:
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
orientation = 0; break;
case ExifInterface.ORIENTATION_ROTATE_180:
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
orientation = 180; break;
case ExifInterface.ORIENTATION_ROTATE_90:
case ExifInterface.ORIENTATION_TRANSPOSE:
orientation = 90; break;
case ExifInterface.ORIENTATION_ROTATE_270:
case ExifInterface.ORIENTATION_TRANSVERSE:
orientation = 270; break;
default: orientation = 0;
}
return orientation;
}
private static int computeSampleSize(int width, int height, int maxWidth, int maxHeight) { private static int computeSampleSize(int width, int height, int maxWidth, int maxHeight) {
// https://developer.android.com/topic/performance/graphics/load-bitmap.html // https://developer.android.com/topic/performance/graphics/load-bitmap.html
int inSampleSize = 1; int inSampleSize = 1;

@ -211,7 +211,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight(), wasFlipped); mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight(), wasFlipped);
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
mPreviewFormat = params.getPreviewFormat(); mPreviewStreamFormat = params.getPreviewFormat();
params.setPreviewSize(mPreviewStreamSize.getWidth(), mPreviewStreamSize.getHeight()); // <- not allowed during preview params.setPreviewSize(mPreviewStreamSize.getWidth(), mPreviewStreamSize.getHeight()); // <- not allowed during preview
if (mMode == Mode.PICTURE) { if (mMode == Mode.PICTURE) {
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed
@ -227,7 +227,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize); mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewStreamFormat), mPreviewStreamSize);
LOG.i(log, "Starting preview with startPreview()."); LOG.i(log, "Starting preview with startPreview().");
try { try {
@ -240,7 +240,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
} }
private void stopPreview() { private void stopPreview() {
mPreviewFormat = 0; mPreviewStreamFormat = 0;
mFrameManager.release(); mFrameManager.release();
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
try { try {
@ -634,7 +634,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
LOG.v("Rotations", "SO", offset(REF_SENSOR, REF_OUTPUT), "OS", offset(REF_OUTPUT, REF_SENSOR)); LOG.v("Rotations", "SO", offset(REF_SENSOR, REF_OUTPUT), "OS", offset(REF_OUTPUT, REF_SENSOR));
LOG.v("Rotations", "VO", offset(REF_VIEW, REF_OUTPUT), "OV", offset(REF_OUTPUT, REF_VIEW)); LOG.v("Rotations", "VO", offset(REF_VIEW, REF_OUTPUT), "OV", offset(REF_OUTPUT, REF_VIEW));
mPictureRecorder = new SnapshotPictureRecorder(stub, Camera1Engine.this, mCamera, outputRatio); mPictureRecorder = new SnapshotPictureRecorder(stub, Camera1Engine.this, mPreview, mCamera, outputRatio);
mPictureRecorder.take(); mPictureRecorder.take();
} }
}); });
@ -646,7 +646,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
System.currentTimeMillis(), System.currentTimeMillis(),
offset(REF_SENSOR, REF_OUTPUT), offset(REF_SENSOR, REF_OUTPUT),
mPreviewStreamSize, mPreviewStreamSize,
mPreviewFormat); mPreviewStreamFormat);
mCallback.dispatchFrame(frame); mCallback.dispatchFrame(frame);
} }

@ -75,6 +75,7 @@ public abstract class CameraEngine implements
public static final int REF_OUTPUT = 2; public static final int REF_OUTPUT = 2;
protected final Callback mCallback; protected final Callback mCallback;
protected final FrameManager mFrameManager;
protected CameraPreview mPreview; protected CameraPreview mPreview;
protected WorkerHandler mHandler; protected WorkerHandler mHandler;
@VisibleForTesting Handler mCrashHandler; @VisibleForTesting Handler mCrashHandler;
@ -101,7 +102,6 @@ public abstract class CameraEngine implements
protected int mCameraId; protected int mCameraId;
protected CameraOptions mCameraOptions; protected CameraOptions mCameraOptions;
protected Mapper mMapper; protected Mapper mMapper;
protected FrameManager mFrameManager;
protected PictureRecorder mPictureRecorder; protected PictureRecorder mPictureRecorder;
protected VideoRecorder mVideoRecorder; protected VideoRecorder mVideoRecorder;
protected long mVideoMaxSize; protected long mVideoMaxSize;
@ -110,7 +110,7 @@ public abstract class CameraEngine implements
protected int mAudioBitRate; protected int mAudioBitRate;
protected Size mCaptureSize; protected Size mCaptureSize;
protected Size mPreviewStreamSize; protected Size mPreviewStreamSize;
protected int mPreviewFormat; protected int mPreviewStreamFormat;
protected long mAutoFocusResetDelayMillis; protected long mAutoFocusResetDelayMillis;
protected int mSensorOffset; protected int mSensorOffset;
@ -416,6 +416,15 @@ public abstract class CameraEngine implements
//region final getters //region final getters
@NonNull
public final FrameManager getFrameManager() {
return mFrameManager;
}
public final int getPreviewStreamFormat() {
return mPreviewStreamFormat;
}
@Nullable @Nullable
public final CameraOptions getCameraOptions() { public final CameraOptions getCameraOptions() {
return mCameraOptions; return mCameraOptions;

@ -40,7 +40,7 @@ import java.nio.ByteOrder;
* There can be multiple surfaces associated with a single context. * There can be multiple surfaces associated with a single context.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class EglBaseSurface extends EglElement { public class EglBaseSurface extends EglElement {
protected static final String TAG = EglBaseSurface.class.getSimpleName(); protected static final String TAG = EglBaseSurface.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG); private final static CameraLogger LOG = CameraLogger.create(TAG);

@ -36,7 +36,7 @@ import android.view.Surface;
* The EGLContext must only be attached to one thread at a time. This class is not thread-safe. * The EGLContext must only be attached to one thread at a time. This class is not thread-safe.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
final class EglCore { public final class EglCore {
private static final String TAG = EglCore.class.getSimpleName(); private static final String TAG = EglCore.class.getSimpleName();
/** /**

@ -27,7 +27,7 @@ import android.view.Surface;
* It's good practice to explicitly release() the surface, preferably from a "finally" block. * It's good practice to explicitly release() the surface, preferably from a "finally" block.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class EglWindowSurface extends EglBaseSurface { public class EglWindowSurface extends EglBaseSurface {
private Surface mSurface; private Surface mSurface;
private boolean mReleaseSurface; private boolean mReleaseSurface;

@ -0,0 +1,38 @@
package com.otaliastudios.cameraview.internal.utils;
import androidx.exifinterface.media.ExifInterface;
/**
* Super basic exif utilities.
*/
public class ExifHelper {
/**
* Maps an {@link ExifInterface} orientation value
* to the actual degrees.
*/
public static int readExifOrientation(int exifOrientation) {
int orientation;
switch (exifOrientation) {
case ExifInterface.ORIENTATION_NORMAL:
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
orientation = 0; break;
case ExifInterface.ORIENTATION_ROTATE_180:
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
orientation = 180; break;
case ExifInterface.ORIENTATION_ROTATE_90:
case ExifInterface.ORIENTATION_TRANSPOSE:
orientation = 90; break;
case ExifInterface.ORIENTATION_ROTATE_270:
case ExifInterface.ORIENTATION_TRANSVERSE:
orientation = 270; break;
default: orientation = 0;
}
return orientation;
}
}

@ -10,7 +10,7 @@ import androidx.annotation.NonNull;
*/ */
@SuppressWarnings("DeprecatedIsStillUsed") @SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated @Deprecated
class RotationHelper { public class RotationHelper {
/** /**
* Rotates the given yuv image into another yuv array, by the given angle. * Rotates the given yuv image into another yuv array, by the given angle.

@ -3,8 +3,8 @@ package com.otaliastudios.cameraview.picture;
import android.hardware.Camera; import android.hardware.Camera;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraUtils;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.internal.utils.ExifHelper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -19,6 +19,7 @@ import java.io.IOException;
public class FullPictureRecorder extends PictureRecorder { public class FullPictureRecorder extends PictureRecorder {
private static final String TAG = FullPictureRecorder.class.getSimpleName(); private static final String TAG = FullPictureRecorder.class.getSimpleName();
@SuppressWarnings("unused")
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private Camera mCamera; private Camera mCamera;
@ -34,10 +35,8 @@ public class FullPictureRecorder extends PictureRecorder {
mCamera.setParameters(params); mCamera.setParameters(params);
} }
// Camera2 constructor here...
@Override @Override
void take() { public void take() {
mCamera.takePicture( mCamera.takePicture(
new Camera.ShutterCallback() { new Camera.ShutterCallback() {
@Override @Override
@ -54,7 +53,7 @@ public class FullPictureRecorder extends PictureRecorder {
try { try {
ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data)); ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data));
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
exifRotation = CameraUtils.readExifOrientation(exifOrientation); exifRotation = ExifHelper.readExifOrientation(exifOrientation);
} catch (IOException e) { } catch (IOException e) {
exifRotation = 0; exifRotation = 0;
} }

@ -4,6 +4,7 @@ import com.otaliastudios.cameraview.PictureResult;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
/** /**
* Interface for picture capturing. * Interface for picture capturing.
@ -12,21 +13,58 @@ import androidx.annotation.Nullable;
*/ */
public abstract class PictureRecorder { public abstract class PictureRecorder {
/* tests */ PictureResult.Stub mResult; /**
/* tests */ PictureResultListener mListener; * Listens for picture recorder events.
*/
public interface PictureResultListener {
/**
* The shutter was activated.
* @param didPlaySound whether a sound was played
*/
void onPictureShutter(boolean didPlaySound);
/**
* Picture was taken or there was some error, if
* the result is null.
* @param result the result or null if there was some error.
*/
void onPictureResult(@Nullable PictureResult.Stub result);
}
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) PictureResult.Stub mResult;
@VisibleForTesting PictureResultListener mListener;
PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener) { /**
* Creates a new picture recorder.
* @param stub a picture stub
* @param listener a listener
*/
@SuppressWarnings("WeakerAccess")
public PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener) {
mResult = stub; mResult = stub;
mListener = listener; mListener = listener;
} }
/**
* Takes a picture.
*/
public abstract void take(); public abstract void take();
/**
* Subclasses can call this to notify that the shutter was activated,
* and whether it did play some sound or not.
* @param didPlaySound whether it played sounds
*/
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected void dispatchOnShutter(boolean didPlaySound) { protected void dispatchOnShutter(boolean didPlaySound) {
if (mListener != null) mListener.onPictureShutter(didPlaySound); if (mListener != null) mListener.onPictureShutter(didPlaySound);
} }
/**
* Subclasses can call this to notify that the result was obtained,
* either with some error (null result) or with the actual stub, filled.
*/
protected void dispatchResult() { protected void dispatchResult() {
if (mListener != null) { if (mListener != null) {
mListener.onPictureResult(mResult); mListener.onPictureResult(mResult);
@ -34,9 +72,4 @@ public abstract class PictureRecorder {
mResult = null; mResult = null;
} }
} }
public interface PictureResultListener {
void onPictureShutter(boolean didPlaySound);
void onPictureResult(@Nullable PictureResult.Stub result);
}
} }

@ -42,22 +42,27 @@ public class SnapshotPictureRecorder extends PictureRecorder {
private static final String TAG = SnapshotPictureRecorder.class.getSimpleName(); private static final String TAG = SnapshotPictureRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private Camera1Engine mController; private Camera1Engine mEngine1;
private Camera mCamera; private Camera mCamera;
private CameraPreview mPreview; private CameraPreview mPreview;
private AspectRatio mOutputRatio; private AspectRatio mOutputRatio;
private Size mSensorPreviewSize;
private int mFormat; private int mFormat;
public SnapshotPictureRecorder(@NonNull PictureResult.Stub stub, @NonNull Camera1Engine controller, /**
@NonNull Camera camera, @NonNull AspectRatio outputRatio) { * Camera1 constructor.
super(stub, controller); */
mController = controller; public SnapshotPictureRecorder(
mPreview = controller.mPreview; @NonNull PictureResult.Stub stub,
@NonNull Camera1Engine engine,
@NonNull CameraPreview preview,
@NonNull Camera camera,
@NonNull AspectRatio outputRatio) {
super(stub, engine);
mEngine1 = engine;
mPreview = preview;
mCamera = camera; mCamera = camera;
mOutputRatio = outputRatio; mOutputRatio = outputRatio;
mFormat = mController.mPreviewFormat; mFormat = engine.getPreviewStreamFormat();
mSensorPreviewSize = mController.mPreviewStreamSize;
} }
@Override @Override
@ -125,7 +130,7 @@ public class SnapshotPictureRecorder extends PictureRecorder {
// Apply scale and crop: // Apply scale and crop:
// NOTE: scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR. // NOTE: scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR.
boolean flip = mController.flip(CameraEngine.REF_VIEW, CameraEngine.REF_SENSOR); boolean flip = mEngine1.flip(CameraEngine.REF_VIEW, CameraEngine.REF_SENSOR);
float realScaleX = flip ? scaleY : scaleX; float realScaleX = flip ? scaleY : scaleX;
float realScaleY = flip ? scaleX : scaleY; float realScaleY = flip ? scaleX : scaleY;
float scaleTranslX = (1F - realScaleX) / 2F; float scaleTranslX = (1F - realScaleX) / 2F;
@ -185,12 +190,17 @@ public class SnapshotPictureRecorder extends PictureRecorder {
// Adding EXIF to a byte array, unfortunately, is hard. // Adding EXIF to a byte array, unfortunately, is hard.
final int sensorToOutput = mResult.rotation; final int sensorToOutput = mResult.rotation;
final Size outputSize = mResult.size; final Size outputSize = mResult.size;
final Size previewStreamSize = mEngine1.getPreviewStreamSize(CameraEngine.REF_SENSOR);
if (previewStreamSize == null) {
throw new IllegalStateException("Preview stream size should never be null here.");
}
WorkerHandler.run(new Runnable() { WorkerHandler.run(new Runnable() {
@Override @Override
public void run() { public void run() {
// Rotate the picture, because no one will write EXIF data, // Rotate the picture, because no one will write EXIF data,
// then crop if needed. In both cases, transform yuv to jpeg. // then crop if needed. In both cases, transform yuv to jpeg.
byte[] data = RotationHelper.rotate(yuv, mSensorPreviewSize, sensorToOutput); //noinspection deprecation
byte[] data = RotationHelper.rotate(yuv, previewStreamSize, sensorToOutput);
YuvImage yuv = new YuvImage(data, mFormat, outputSize.getWidth(), outputSize.getHeight(), null); YuvImage yuv = new YuvImage(data, mFormat, outputSize.getWidth(), outputSize.getHeight(), null);
ByteArrayOutputStream stream = new ByteArrayOutputStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream();
@ -208,19 +218,18 @@ public class SnapshotPictureRecorder extends PictureRecorder {
// It seems that the buffers are already cleared here, so we need to allocate again. // It seems that the buffers are already cleared here, so we need to allocate again.
camera.setPreviewCallbackWithBuffer(null); // Release anything left camera.setPreviewCallbackWithBuffer(null); // Release anything left
camera.setPreviewCallbackWithBuffer(mController); // Add ourselves camera.setPreviewCallbackWithBuffer(mEngine1); // Add ourselves
mController.mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewStreamSize); mEngine1.getFrameManager().allocateBuffers(ImageFormat.getBitsPerPixel(mFormat), previewStreamSize);
} }
}); });
} }
@Override @Override
protected void dispatchResult() { protected void dispatchResult() {
mController = null; mEngine1 = null;
mCamera = null; mCamera = null;
mOutputRatio = null; mOutputRatio = null;
mFormat = 0; mFormat = 0;
mSensorPreviewSize = null;
super.dispatchResult(); super.dispatchResult();
} }
} }

Loading…
Cancel
Save