From 57d234c05b52e321d9d0e597247e84e0c1ecb418 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Tue, 29 Aug 2017 13:28:47 +0200 Subject: [PATCH] Improve threading, add CameraOptions.supports() (#13) * abstract WorkerHandler class, cleanup * Start and stop in our thread * Simplify Camera1 threading * Add CameraOptions.supports() shorthands * Improve supports() shorthands * Rearrange orientation callbacks --- .../com/otaliastudios/cameraview/Camera1.java | 107 ++++------- .../com/otaliastudios/cameraview/Camera2.java | 74 +------ .../cameraview/CameraController.java | 135 +++++++++++-- .../cameraview/CameraOptions.java | 68 +++++++ .../otaliastudios/cameraview/CameraView.java | 180 ++++++++---------- .../cameraview/OrientationHelper.java | 33 ++-- .../cameraview/WorkerHandler.java | 30 +++ .../com/otaliastudios/cameraview/Preview.java | 2 - 8 files changed, 358 insertions(+), 271 deletions(-) create mode 100644 cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 2018ee35..da503993 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -11,6 +11,7 @@ import android.os.Build; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; import android.util.Log; import android.view.SurfaceHolder; @@ -26,24 +27,31 @@ class Camera1 extends CameraController { private static final String TAG = Camera1.class.getSimpleName(); - private static final int DELAY_MILLIS_BEFORE_RESETTING_FOCUS = 3000; - private int mCameraId; private Camera mCamera; - private ExtraProperties mExtraProperties; - private CameraOptions mOptions; - private Size mPreviewSize; - private Size mCaptureSize; private MediaRecorder mMediaRecorder; private File mVideoFile; - private int mDisplayOffset; - private int mDeviceOrientation; private int mSensorOffset; private Location mLocation; - private Handler mFocusHandler = new Handler(); + private final int mPostFocusResetDelay = 3000; + private Runnable mPostFocusResetRunnable = new Runnable() { + @Override + public void run() { + if (!isCameraOpened()) return; + mCamera.cancelAutoFocus(); + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + params.setFocusAreas(null); + params.setMeteringAreas(null); + applyDefaultFocus(params); // Revert to internal focus. + mCamera.setParameters(params); + } + } + }; + private Mapper mMapper = new Mapper.Mapper1(); private boolean mIsSetup = false; private boolean mIsCapturingImage = false; @@ -129,9 +137,10 @@ class Camera1 extends CameraController { } + @WorkerThread @Override - void start() { - if (isCameraOpened()) stop(); + void onStart() { + if (isCameraOpened()) onStop(); if (collectCameraId()) { mCamera = Camera.open(mCameraId); @@ -155,10 +164,10 @@ class Camera1 extends CameraController { } } - + @WorkerThread @Override - void stop() { - mFocusHandler.removeCallbacksAndMessages(null); + void onStop() { + mHandler.get().removeCallbacks(mPostFocusResetRunnable); if (isCameraOpened()) { if (mIsCapturingVideo) endVideo(); mCamera.stopPreview(); @@ -187,17 +196,6 @@ class Camera1 extends CameraController { return false; } - @Override - void onDisplayOffset(int displayOrientation) { - // I doubt this will ever change. - this.mDisplayOffset = displayOrientation; - } - - @Override - void onDeviceOrientation(int deviceOrientation) { - this.mDeviceOrientation = deviceOrientation; - } - @Override void setSessionType(SessionType sessionType) { @@ -260,7 +258,7 @@ class Camera1 extends CameraController { } private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) { - if (mOptions.getSupportedWhiteBalance().contains(mWhiteBalance)) { + if (mOptions.supports(mWhiteBalance)) { params.setWhiteBalance((String) mMapper.map(mWhiteBalance)); return true; } @@ -281,7 +279,7 @@ class Camera1 extends CameraController { } private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) { - if (mOptions.getSupportedHdr().contains(mHdr)) { + if (mOptions.supports(mHdr)) { params.setSceneMode((String) mMapper.map(mHdr)); return true; } @@ -303,7 +301,7 @@ class Camera1 extends CameraController { private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) { - if (mOptions.getSupportedFlash().contains(mFlash)) { + if (mOptions.supports(mFlash)) { params.setFlashMode((String) mMapper.map(mFlash)); return true; } @@ -390,9 +388,15 @@ class Camera1 extends CameraController { mCamera.takePicture(null, null, null, new Camera.PictureCallback() { @Override - public void onPictureTaken(byte[] data, Camera camera) { + public void onPictureTaken(byte[] data, final Camera camera) { mIsCapturingImage = false; - camera.startPreview(); // This is needed, read somewhere in the docs. + mHandler.post(new Runnable() { + @Override + public void run() { + // This is needed, read somewhere in the docs. + camera.startPreview(); + } + }); mCameraCallbacks.processImage(data, consistentWithView, exifFlip); } }); @@ -443,16 +447,6 @@ class Camera1 extends CameraController { return true; } - @Override - Size getCaptureSize() { - return mCaptureSize; - } - - @Override - Size getPreviewSize() { - return mPreviewSize; - } - @Override boolean shouldFlipSizes() { return mSensorOffset % 180 != 0; @@ -463,17 +457,6 @@ class Camera1 extends CameraController { return mCamera != null; } - @Nullable - @Override - ExtraProperties getExtraProperties() { - return mExtraProperties; - } - - @Nullable - @Override - CameraOptions getCameraOptions() { - return mOptions; - } // Internal: @@ -715,7 +698,8 @@ class Camera1 extends CameraController { public void onAutoFocus(boolean success, Camera camera) { // TODO lock auto exposure and white balance for a while mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); - postResetFocus(); + mHandler.get().removeCallbacks(mPostFocusResetRunnable); + mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay); } }); } @@ -723,25 +707,6 @@ class Camera1 extends CameraController { } - private void postResetFocus() { - mFocusHandler.removeCallbacksAndMessages(null); - mFocusHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (!isCameraOpened()) return; - mCamera.cancelAutoFocus(); - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - params.setFocusAreas(null); - params.setMeteringAreas(null); - applyDefaultFocus(params); // Revert to internal focus. - mCamera.setParameters(params); - } - } - }, DELAY_MILLIS_BEFORE_RESETTING_FOCUS); - } - - private List computeMeteringAreas(double viewClickX, double viewClickY) { // Event came in view coordinates. We must rotate to sensor coordinates. // First, rescale to the -1000 ... 1000 range. diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java index b50c9ce8..65158f6a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java @@ -12,6 +12,7 @@ import android.hardware.camera2.params.StreamConfigurationMap; import android.location.Location; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; import android.util.Log; import java.io.File; @@ -29,8 +30,6 @@ class Camera2 extends CameraController { private String mCameraId; - private Size mCaptureSize; - private Size mPreviewSize; private Mapper mMapper = new Mapper.Mapper2(); private final HashMap mExtraPropertiesMap = new HashMap<>(); @@ -79,13 +78,15 @@ class Camera2 extends CameraController { // CameraImpl: + @WorkerThread @Override - void start() { + void onStart() { } + @WorkerThread @Override - void stop() { + void onStop() { } @@ -186,57 +187,7 @@ class Camera2 extends CameraController { return false; } - @Override - Size getCaptureSize() { - if (mCaptureSize == null && mCameraCharacteristics != null) { - TreeSet sizes = new TreeSet<>(); - sizes.addAll(getAvailableCaptureResolutions()); - - /* TreeSet aspectRatios = new CommonAspectRatioFilter( - getAvailablePreviewResolutions(), - getAvailableCaptureResolutions() - ).filter(); - AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null; - - Iterator descendingSizes = sizes.descendingIterator(); - Size size; - while (descendingSizes.hasNext() && mCaptureSize == null) { - size = descendingSizes.next(); - if (targetRatio == null || targetRatio.matches(size)) { - mCaptureSize = size; - break; - } - } */ - } - return mCaptureSize; - } - - @Override - Size getPreviewSize() { - if (mPreviewSize == null && mCameraCharacteristics != null) { - TreeSet sizes = new TreeSet<>(); - sizes.addAll(getAvailablePreviewResolutions()); - - /* TreeSet aspectRatios = new CommonAspectRatioFilter( - getAvailablePreviewResolutions(), - getAvailableCaptureResolutions() - ).filter(); - AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null; - - Iterator descendingSizes = sizes.descendingIterator(); - Size size; - while (descendingSizes.hasNext() && mPreviewSize == null) { - size = descendingSizes.next(); - if (targetRatio == null || targetRatio.matches(size)) { - mPreviewSize = size; - break; - } - } */ - } - - return mPreviewSize; - } @Override boolean shouldFlipSizes() { @@ -253,22 +204,9 @@ class Camera2 extends CameraController { return true; } - @Nullable - @Override - ExtraProperties getExtraProperties() { - if (mCamera == null) { - return null; - } - return mExtraPropertiesMap.get(mCamera.getId()); - } - // Internal + // Internal - @Nullable - @Override - CameraOptions getCameraOptions() { - return null; - } private List getAvailableCaptureResolutions() { List output = new ArrayList<>(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 6cd4c921..81d6dd7e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -4,6 +4,7 @@ import android.graphics.PointF; import android.location.Location; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; import java.io.File; @@ -19,47 +20,153 @@ abstract class CameraController implements Preview.SurfaceCallback { protected SessionType mSessionType; protected Hdr mHdr; + protected Size mCaptureSize; + protected Size mPreviewSize; + + protected ExtraProperties mExtraProperties; + protected CameraOptions mOptions; + + protected int mDisplayOffset; + protected int mDeviceOrientation; + + protected WorkerHandler mHandler; + CameraController(CameraView.CameraCallbacks callback, Preview preview) { mCameraCallbacks = callback; mPreview = preview; mPreview.setSurfaceCallback(this); + mHandler = new WorkerHandler("CameraViewController"); + } + + //region Start&Stop + + // Starts the preview asynchronously. + final void start() { + mHandler.post(new Runnable() { + @Override + public void run() { + onStart(); + } + }); } - abstract void start(); - abstract void stop(); + // Stops the preview asynchronously. + final void stop() { + mHandler.post(new Runnable() { + @Override + public void run() { + onStop(); + } + }); + } + + // Starts the preview. + @WorkerThread + abstract void onStart(); + + // Stops the preview. + @WorkerThread + abstract void onStop(); + + //endregion - abstract void onDisplayOffset(int displayOrientation); - abstract void onDeviceOrientation(int deviceOrientation); + //region Rotation callbacks + + void onDisplayOffset(int displayOrientation) { + // I doubt this will ever change. + mDisplayOffset = displayOrientation; + } + + void onDeviceOrientation(int deviceOrientation) { + mDeviceOrientation = deviceOrientation; + } + + //endregion + + //region Abstract setParameters abstract boolean setZoom(float zoom); + abstract boolean setExposureCorrection(float EVvalue); + abstract void setFacing(Facing facing); + abstract void setFlash(Flash flash); + abstract void setWhiteBalance(WhiteBalance whiteBalance); + abstract void setVideoQuality(VideoQuality videoQuality); + abstract void setSessionType(SessionType sessionType); + abstract void setHdr(Hdr hdr); + abstract void setLocation(Location location); + //endregion + + //region APIs + abstract boolean capturePicture(); + abstract boolean captureSnapshot(); + abstract boolean startVideo(@NonNull File file); + abstract boolean endVideo(); - abstract Size getCaptureSize(); - abstract Size getPreviewSize(); + abstract boolean shouldFlipSizes(); // Wheter the Sizes should be flipped to match the view orientation. + abstract boolean isCameraOpened(); abstract boolean startAutoFocus(@Nullable Gesture gesture, PointF point); - @Nullable abstract ExtraProperties getExtraProperties(); - @Nullable abstract CameraOptions getCameraOptions(); + //endregion + + //region final getters + + @Nullable + final ExtraProperties getExtraProperties() { + return mExtraProperties; + } + + @Nullable + final CameraOptions getCameraOptions() { + return mOptions; + } + + final Facing getFacing() { + return mFacing; + } + + final Flash getFlash() { + return mFlash; + } + + final WhiteBalance getWhiteBalance() { + return mWhiteBalance; + } + + final VideoQuality getVideoQuality() { + return mVideoQuality; + } + + final SessionType getSessionType() { + return mSessionType; + } + + final Hdr getHdr() { + return mHdr; + } + + final Size getCaptureSize() { + return mCaptureSize; + } + + final Size getPreviewSize() { + return mPreviewSize; + } - final Facing getFacing() { return mFacing; } - final Flash getFlash() { return mFlash; } - final WhiteBalance getWhiteBalance() { return mWhiteBalance; } - final VideoQuality getVideoQuality() { return mVideoQuality; } - final SessionType getSessionType() { return mSessionType; } - final Hdr getHdr() { return mHdr; } + //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index d40084db..352d912d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -88,6 +88,74 @@ public class CameraOptions { CameraOptions(CameraCharacteristics params) {} + /** + * Shorthand for getSupportedFacing().contains(value). + * + * @param facing value + * @return whether it's supported + */ + public boolean supports(Facing facing) { + return getSupportedFacing().contains(facing); + } + + + /** + * Shorthand for getSupportedFlash().contains(value). + * + * @param flash value + * @return whether it's supported + */ + public boolean supports(Flash flash) { + return getSupportedFlash().contains(flash); + } + + + /** + * Shorthand for getSupportedWhiteBalance().contains(value). + * + * @param whiteBalance value + * @return whether it's supported + */ + public boolean supports(WhiteBalance whiteBalance) { + return getSupportedWhiteBalance().contains(whiteBalance); + } + + + /** + * Shorthand for getSupportedHdr().contains(value). + * + * @param hdr value + * @return whether it's supported + */ + public boolean supports(Hdr hdr) { + return getSupportedHdr().contains(hdr); + } + + + /** + * Shorthand for other methods in this class, + * e.g. supports(GestureAction.ZOOM) == isZoomSupported(). + * + * @param action value to be checked + * @return whether it's supported + */ + public boolean supports(GestureAction action) { + switch (action) { + case FOCUS: + case FOCUS_WITH_MARKER: + return isAutoFocusSupported(); + case CAPTURE: + case NONE: + return true; + case ZOOM: + return isZoomSupported(); + case EXPOSURE_CORRECTION: + return isExposureCorrectionSupported(); + } + return false; + } + + /** * Set of supported facing values. * diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 3d408dcf..278bd790 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1,7 +1,6 @@ package com.otaliastudios.cameraview; import android.Manifest; -import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; @@ -48,31 +47,20 @@ public class CameraView extends FrameLayout { private final static int DEFAULT_JPEG_QUALITY = 100; private final static boolean DEFAULT_CROP_OUTPUT = false; - private Handler mWorkerHandler; - - private Handler getWorkerHandler() { - synchronized (this) { - if (mWorkerHandler == null) { - HandlerThread workerThread = new HandlerThread("CameraViewWorker"); - workerThread.setDaemon(true); - workerThread.start(); - mWorkerHandler = new Handler(workerThread.getLooper()); - } - } - return mWorkerHandler; - } - - private void run(Runnable runnable) { - getWorkerHandler().post(runnable); - } - + // Self managed parameters private int mJpegQuality; private boolean mCropOutput; + private float mZoomValue; + private float mExposureCorrectionValue; + private HashMap mGestureMap = new HashMap<>(4); + + // Components private CameraCallbacks mCameraCallbacks; private OrientationHelper mOrientationHelper; private CameraController mCameraController; private Preview mPreviewImpl; + // Views private GridLinesLayout mGridLinesLayout; private PinchGestureLayout mPinchGestureLayout; private TapGestureLayout mTapGestureLayout; @@ -80,10 +68,9 @@ public class CameraView extends FrameLayout { private boolean mIsStarted; private boolean mKeepScreenOn; - private float mZoomValue; - private float mExposureCorrectionValue; - - private HashMap mGestureMap = new HashMap<>(4); + // Threading + private Handler mUiHandler; + private WorkerHandler mWorkerHandler; public CameraView(@NonNull Context context) { super(context, null); @@ -120,9 +107,14 @@ public class CameraView extends FrameLayout { GestureAction scrollVerticalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value())); a.recycle(); + // Components mCameraCallbacks = new CameraCallbacks(); mPreviewImpl = new TextureViewPreview(context, this); mCameraController = new Camera1(mCameraCallbacks, mPreviewImpl); + mUiHandler = new Handler(Looper.getMainLooper()); + mWorkerHandler = new WorkerHandler("CameraViewWorker"); + + // Views mGridLinesLayout = new GridLinesLayout(context); mPinchGestureLayout = new PinchGestureLayout(context); mTapGestureLayout = new TapGestureLayout(context); @@ -134,11 +126,11 @@ public class CameraView extends FrameLayout { mIsStarted = false; - // Self managed + // Apply self managed setCropOutput(cropOutput); setJpegQuality(jpegQuality); - // Camera controller params + // Apply camera controller params setFacing(facing); setFlash(flash); setSessionType(sessionType); @@ -146,7 +138,7 @@ public class CameraView extends FrameLayout { setWhiteBalance(whiteBalance); setGrid(grid); - // Gestures + // Apply gestures mapGesture(Gesture.TAP, tapGesture); // mapGesture(Gesture.DOUBLE_TAP, doubleTapGesture); mapGesture(Gesture.LONG_TAP, longTapGesture); @@ -155,34 +147,7 @@ public class CameraView extends FrameLayout { mapGesture(Gesture.SCROLL_VERTICAL, scrollVerticalGesture); if (!isInEditMode()) { - mOrientationHelper = new OrientationHelper(context) { - - private Integer mDisplayOffset; - private Integer mDeviceOrientation; - - @Override - public void onDisplayOffsetChanged(int displayOffset) { - mCameraController.onDisplayOffset(displayOffset); - mPreviewImpl.onDisplayOffset(displayOffset); - mDisplayOffset = displayOffset; - send(); - } - - @Override - protected void onDeviceOrientationChanged(int deviceOrientation) { - mCameraController.onDeviceOrientation(deviceOrientation); - mPreviewImpl.onDeviceOrientation(deviceOrientation); - mDeviceOrientation = deviceOrientation; - send(); - } - - private void send() { - if (mDeviceOrientation == null) return; - if (mDisplayOffset == null) return; - int value = (mDeviceOrientation + mDisplayOffset) % 360; - mCameraCallbacks.dispatchOnOrientationChanged(value); - } - }; + mOrientationHelper = new OrientationHelper(context, mCameraCallbacks); } } @@ -497,12 +462,7 @@ public class CameraView extends FrameLayout { if (checkPermissions(getSessionType())) { mIsStarted = true; - run(new Runnable() { - @Override - public void run() { - mCameraController.start(); - } - }); + mCameraController.start(); } } @@ -584,9 +544,8 @@ public class CameraView extends FrameLayout { } public void destroy() { + // TODO: this is not strictly needed mCameraCallbacks.clearListeners(); // Release inner listener. - // This might be useless, but no time to think about it now. - mWorkerHandler = null; } //endregion @@ -788,12 +747,7 @@ public class CameraView extends FrameLayout { * @param facing a facing value. */ public void setFacing(final Facing facing) { - run(new Runnable() { - @Override - public void run() { - mCameraController.setFacing(facing); - } - }); + mCameraController.setFacing(facing); } @@ -1212,20 +1166,21 @@ public class CameraView extends FrameLayout { } } - class CameraCallbacks { - private ArrayList mListeners; - private Handler uiHandler; + class CameraCallbacks implements OrientationHelper.Callbacks { + // Outer listeners + private ArrayList mListeners = new ArrayList<>(2); - CameraCallbacks() { - mListeners = new ArrayList<>(2); - uiHandler = new Handler(Looper.getMainLooper()); - } + // Orientation TODO: move this logic into OrientationHelper + private Integer mDisplayOffset; + private Integer mDeviceOrientation; + + CameraCallbacks() {} public void dispatchOnCameraOpened(final CameraOptions options) { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { @@ -1237,7 +1192,7 @@ public class CameraView extends FrameLayout { public void dispatchOnCameraClosed() { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { @@ -1253,7 +1208,7 @@ public class CameraView extends FrameLayout { // Request a layout pass for onMeasure() to do its stuff. // Potentially this will change CameraView size, which changes Surface size, // which triggers a new Preview size. But hopefully it will converge. - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { requestLayout(); @@ -1280,7 +1235,7 @@ public class CameraView extends FrameLayout { * because it was taken with the front camera. */ public void processImage(final byte[] jpeg, final boolean consistentWithView, final boolean flipHorizontally) { - getWorkerHandler().post(new Runnable() { + mWorkerHandler.post(new Runnable() { @Override public void run() { byte[] jpeg2 = jpeg; @@ -1300,25 +1255,30 @@ public class CameraView extends FrameLayout { } - public void processSnapshot(YuvImage yuv, boolean consistentWithView, boolean flipHorizontally) { - byte[] jpeg; - if (mCropOutput && mPreviewImpl.isCropping()) { - int w = consistentWithView ? getWidth() : getHeight(); - int h = consistentWithView ? getHeight() : getWidth(); - AspectRatio targetRatio = AspectRatio.of(w, h); - jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality); - } else { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out); - jpeg = out.toByteArray(); - } - dispatchOnPictureTaken(jpeg); + public void processSnapshot(final YuvImage yuv, final boolean consistentWithView, boolean flipHorizontally) { + mWorkerHandler.post(new Runnable() { + @Override + public void run() { + byte[] jpeg; + if (mCropOutput && mPreviewImpl.isCropping()) { + int w = consistentWithView ? getWidth() : getHeight(); + int h = consistentWithView ? getHeight() : getWidth(); + AspectRatio targetRatio = AspectRatio.of(w, h); + jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality); + } else { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out); + jpeg = out.toByteArray(); + } + dispatchOnPictureTaken(jpeg); + } + }); } private void dispatchOnPictureTaken(byte[] jpeg) { final byte[] data = jpeg; - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { @@ -1330,7 +1290,7 @@ public class CameraView extends FrameLayout { public void dispatchOnVideoTaken(final File video) { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { @@ -1342,7 +1302,7 @@ public class CameraView extends FrameLayout { public void dispatchOnFocusStart(@Nullable final Gesture gesture, final PointF point) { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { if (gesture != null && mGestureMap.get(gesture) == GestureAction.FOCUS_WITH_MARKER) { @@ -1359,7 +1319,7 @@ public class CameraView extends FrameLayout { public void dispatchOnFocusEnd(@Nullable final Gesture gesture, final boolean success, final PointF point) { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { if (success && mUseSounds) { @@ -1378,9 +1338,29 @@ public class CameraView extends FrameLayout { }); } + @Override + public void onDisplayOffsetChanged(int displayOffset) { + mCameraController.onDisplayOffset(displayOffset); + mDisplayOffset = displayOffset; + if (mDeviceOrientation != null) { + int value = (mDeviceOrientation + mDisplayOffset) % 360; + dispatchOnOrientationChanged(value); + } + } + + @Override + public void onDeviceOrientationChanged(int deviceOrientation) { + mCameraController.onDeviceOrientation(deviceOrientation); + mDeviceOrientation = deviceOrientation; + if (mDisplayOffset != null) { + int value = (mDeviceOrientation + mDisplayOffset) % 360; + dispatchOnOrientationChanged(value); + } + } + - public void dispatchOnOrientationChanged(final int value) { - uiHandler.post(new Runnable() { + private void dispatchOnOrientationChanged(final int value) { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { @@ -1392,7 +1372,7 @@ public class CameraView extends FrameLayout { public void dispatchOnZoomChanged(final float newValue, final PointF[] fingers) { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { @@ -1406,7 +1386,7 @@ public class CameraView extends FrameLayout { public void dispatchOnExposureCorrectionChanged(final float newValue, final float[] bounds, final PointF[] fingers) { - uiHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java index 5b89f479..91230ef6 100644 --- a/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java +++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java @@ -2,14 +2,13 @@ package com.otaliastudios.cameraview; import android.content.Context; import android.hardware.SensorManager; +import android.support.annotation.NonNull; import android.util.SparseIntArray; import android.view.Display; import android.view.OrientationEventListener; import android.view.Surface; -abstract class OrientationHelper { - - private final OrientationEventListener mOrientationEventListener; +class OrientationHelper { private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); static { @@ -19,13 +18,20 @@ abstract class OrientationHelper { DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); } + private final OrientationEventListener mListener; + private final Callbacks mCallbacks; private Display mDisplay; - private int mLastKnownDisplayOffset = -1; private int mLastOrientation = -1; - OrientationHelper(Context context) { - mOrientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { + interface Callbacks { + void onDisplayOffsetChanged(int displayOffset); + void onDeviceOrientationChanged(int deviceOrientation); + } + + OrientationHelper(Context context, @NonNull Callbacks callbacks) { + mCallbacks = callbacks; + mListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { @Override public void onOrientationChanged(int orientation) { @@ -44,7 +50,7 @@ abstract class OrientationHelper { if (or != mLastOrientation) { mLastOrientation = or; - onDeviceOrientationChanged(mLastOrientation); + mCallbacks.onDeviceOrientationChanged(mLastOrientation); } // Let's see if display rotation has changed.. but how could it ever change...?? @@ -53,7 +59,7 @@ abstract class OrientationHelper { final int offset = mDisplay.getRotation(); if (mLastKnownDisplayOffset != offset) { mLastKnownDisplayOffset = offset; - onDisplayOffsetChanged(DISPLAY_ORIENTATIONS.get(offset)); + mCallbacks.onDisplayOffsetChanged(DISPLAY_ORIENTATIONS.get(offset)); } } } @@ -63,18 +69,13 @@ abstract class OrientationHelper { void enable(Display display) { mDisplay = display; - mOrientationEventListener.enable(); + mListener.enable(); mLastKnownDisplayOffset = DISPLAY_ORIENTATIONS.get(display.getRotation()); - onDisplayOffsetChanged(mLastKnownDisplayOffset); + mCallbacks.onDisplayOffsetChanged(mLastKnownDisplayOffset); } void disable() { - mOrientationEventListener.disable(); + mListener.disable(); mDisplay = null; } - - protected abstract void onDisplayOffsetChanged(int displayOffset); - - protected abstract void onDeviceOrientationChanged(int deviceOrientation); - } \ No newline at end of file diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java new file mode 100644 index 00000000..798d8366 --- /dev/null +++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java @@ -0,0 +1,30 @@ +package com.otaliastudios.cameraview; + +import android.os.Handler; +import android.os.HandlerThread; + +/** + * Class holding a background handler. + * Using setDaemon right now but a better approach would be to have + * start() and stop() callbacks here. TODO + */ +class WorkerHandler { + + private HandlerThread mThread; + private Handler mHandler; + + public WorkerHandler(String name) { + mThread = new HandlerThread(name); + mThread.setDaemon(true); + mThread.start(); + mHandler = new Handler(mThread.getLooper()); + } + + public Handler get() { + return mHandler; + } + + public void post(Runnable runnable) { + mHandler.post(runnable); + } +} diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java b/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java index d671c3f4..a10addc1 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java @@ -33,8 +33,6 @@ abstract class Preview { abstract View getView(); abstract Class getOutputClass(); abstract boolean isReady(); - protected void onDisplayOffset(int displayOrientation) {} - protected void onDeviceOrientation(int deviceOrientation) {} SurfaceHolder getSurfaceHolder() { return null; }