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.engine.Mapper;
import com.otaliastudios.cameraview.internal.utils.ExifHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import androidx.annotation.NonNull;
@ -258,7 +259,7 @@ public class CameraUtils {
stream = new ByteArrayInputStream(source);
ExifInterface exif = new ExifInterface(stream);
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
orientation = readExifOrientation(exifOrientation);
orientation = ExifHelper.readExifOrientation(exifOrientation);
flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL ||
exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL ||
exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE ||
@ -315,31 +316,6 @@ public class CameraUtils {
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) {
// https://developer.android.com/topic/performance/graphics/load-bitmap.html
int inSampleSize = 1;

@ -211,7 +211,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight(), wasFlipped);
Camera.Parameters params = mCamera.getParameters();
mPreviewFormat = params.getPreviewFormat();
mPreviewStreamFormat = params.getPreviewFormat();
params.setPreviewSize(mPreviewStreamSize.getWidth(), mPreviewStreamSize.getHeight()); // <- not allowed during preview
if (mMode == Mode.PICTURE) {
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(this); // Add ourselves
mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewFormat), mPreviewStreamSize);
mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewStreamFormat), mPreviewStreamSize);
LOG.i(log, "Starting preview with startPreview().");
try {
@ -240,7 +240,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
}
private void stopPreview() {
mPreviewFormat = 0;
mPreviewStreamFormat = 0;
mFrameManager.release();
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
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", "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();
}
});
@ -646,7 +646,7 @@ public class Camera1Engine extends CameraEngine implements Camera.PreviewCallbac
System.currentTimeMillis(),
offset(REF_SENSOR, REF_OUTPUT),
mPreviewStreamSize,
mPreviewFormat);
mPreviewStreamFormat);
mCallback.dispatchFrame(frame);
}

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

@ -40,7 +40,7 @@ import java.nio.ByteOrder;
* There can be multiple surfaces associated with a single context.
*/
@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();
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.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
final class EglCore {
public final class EglCore {
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.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class EglWindowSurface extends EglBaseSurface {
public class EglWindowSurface extends EglBaseSurface {
private Surface mSurface;
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")
@Deprecated
class RotationHelper {
public class RotationHelper {
/**
* 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 com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraUtils;
import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.internal.utils.ExifHelper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -19,6 +19,7 @@ import java.io.IOException;
public class FullPictureRecorder extends PictureRecorder {
private static final String TAG = FullPictureRecorder.class.getSimpleName();
@SuppressWarnings("unused")
private static final CameraLogger LOG = CameraLogger.create(TAG);
private Camera mCamera;
@ -34,10 +35,8 @@ public class FullPictureRecorder extends PictureRecorder {
mCamera.setParameters(params);
}
// Camera2 constructor here...
@Override
void take() {
public void take() {
mCamera.takePicture(
new Camera.ShutterCallback() {
@Override
@ -54,7 +53,7 @@ public class FullPictureRecorder extends PictureRecorder {
try {
ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data));
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
exifRotation = CameraUtils.readExifOrientation(exifOrientation);
exifRotation = ExifHelper.readExifOrientation(exifOrientation);
} catch (IOException e) {
exifRotation = 0;
}

@ -4,6 +4,7 @@ import com.otaliastudios.cameraview.PictureResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
/**
* Interface for picture capturing.
@ -12,21 +13,58 @@ import androidx.annotation.Nullable;
*/
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);
PictureRecorder(@NonNull PictureResult.Stub stub, @Nullable PictureResultListener listener) {
/**
* 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;
/**
* 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;
mListener = listener;
}
/**
* Takes a picture.
*/
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")
protected void dispatchOnShutter(boolean 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() {
if (mListener != null) {
mListener.onPictureResult(mResult);
@ -34,9 +72,4 @@ public abstract class PictureRecorder {
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 CameraLogger LOG = CameraLogger.create(TAG);
private Camera1Engine mController;
private Camera1Engine mEngine1;
private Camera mCamera;
private CameraPreview mPreview;
private AspectRatio mOutputRatio;
private Size mSensorPreviewSize;
private int mFormat;
public SnapshotPictureRecorder(@NonNull PictureResult.Stub stub, @NonNull Camera1Engine controller,
@NonNull Camera camera, @NonNull AspectRatio outputRatio) {
super(stub, controller);
mController = controller;
mPreview = controller.mPreview;
/**
* Camera1 constructor.
*/
public SnapshotPictureRecorder(
@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;
mOutputRatio = outputRatio;
mFormat = mController.mPreviewFormat;
mSensorPreviewSize = mController.mPreviewStreamSize;
mFormat = engine.getPreviewStreamFormat();
}
@Override
@ -125,7 +130,7 @@ public class SnapshotPictureRecorder 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 = mController.flip(CameraEngine.REF_VIEW, CameraEngine.REF_SENSOR);
boolean flip = mEngine1.flip(CameraEngine.REF_VIEW, CameraEngine.REF_SENSOR);
float realScaleX = flip ? scaleY : scaleX;
float realScaleY = flip ? scaleX : scaleY;
float scaleTranslX = (1F - realScaleX) / 2F;
@ -185,12 +190,17 @@ public class SnapshotPictureRecorder 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);
if (previewStreamSize == null) {
throw new IllegalStateException("Preview stream size should never be null here.");
}
WorkerHandler.run(new Runnable() {
@Override
public void run() {
// Rotate the picture, because no one will write EXIF data,
// 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);
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.
camera.setPreviewCallbackWithBuffer(null); // Release anything left
camera.setPreviewCallbackWithBuffer(mController); // Add ourselves
mController.mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewStreamSize);
camera.setPreviewCallbackWithBuffer(mEngine1); // Add ourselves
mEngine1.getFrameManager().allocateBuffers(ImageFormat.getBitsPerPixel(mFormat), previewStreamSize);
}
});
}
@Override
protected void dispatchResult() {
mController = null;
mEngine1 = null;
mCamera = null;
mOutputRatio = null;
mFormat = 0;
mSensorPreviewSize = null;
super.dispatchResult();
}
}

Loading…
Cancel
Save