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
pull/19/head
Mattia Iavarone 7 years ago committed by GitHub
parent cbac776f91
commit 57d234c05b
  1. 107
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  2. 74
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  3. 135
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  4. 68
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  5. 180
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  6. 33
      cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java
  7. 30
      cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java
  8. 2
      cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java

@ -11,6 +11,7 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log; import android.util.Log;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -26,24 +27,31 @@ class Camera1 extends CameraController {
private static final String TAG = Camera1.class.getSimpleName(); private static final String TAG = Camera1.class.getSimpleName();
private static final int DELAY_MILLIS_BEFORE_RESETTING_FOCUS = 3000;
private int mCameraId; private int mCameraId;
private Camera mCamera; private Camera mCamera;
private ExtraProperties mExtraProperties;
private CameraOptions mOptions;
private Size mPreviewSize;
private Size mCaptureSize;
private MediaRecorder mMediaRecorder; private MediaRecorder mMediaRecorder;
private File mVideoFile; private File mVideoFile;
private int mDisplayOffset;
private int mDeviceOrientation;
private int mSensorOffset; private int mSensorOffset;
private Location mLocation; 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 Mapper mMapper = new Mapper.Mapper1();
private boolean mIsSetup = false; private boolean mIsSetup = false;
private boolean mIsCapturingImage = false; private boolean mIsCapturingImage = false;
@ -129,9 +137,10 @@ class Camera1 extends CameraController {
} }
@WorkerThread
@Override @Override
void start() { void onStart() {
if (isCameraOpened()) stop(); if (isCameraOpened()) onStop();
if (collectCameraId()) { if (collectCameraId()) {
mCamera = Camera.open(mCameraId); mCamera = Camera.open(mCameraId);
@ -155,10 +164,10 @@ class Camera1 extends CameraController {
} }
} }
@WorkerThread
@Override @Override
void stop() { void onStop() {
mFocusHandler.removeCallbacksAndMessages(null); mHandler.get().removeCallbacks(mPostFocusResetRunnable);
if (isCameraOpened()) { if (isCameraOpened()) {
if (mIsCapturingVideo) endVideo(); if (mIsCapturingVideo) endVideo();
mCamera.stopPreview(); mCamera.stopPreview();
@ -187,17 +196,6 @@ class Camera1 extends CameraController {
return false; 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 @Override
void setSessionType(SessionType sessionType) { void setSessionType(SessionType sessionType) {
@ -260,7 +258,7 @@ class Camera1 extends CameraController {
} }
private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) { private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) {
if (mOptions.getSupportedWhiteBalance().contains(mWhiteBalance)) { if (mOptions.supports(mWhiteBalance)) {
params.setWhiteBalance((String) mMapper.map(mWhiteBalance)); params.setWhiteBalance((String) mMapper.map(mWhiteBalance));
return true; return true;
} }
@ -281,7 +279,7 @@ class Camera1 extends CameraController {
} }
private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) { private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) {
if (mOptions.getSupportedHdr().contains(mHdr)) { if (mOptions.supports(mHdr)) {
params.setSceneMode((String) mMapper.map(mHdr)); params.setSceneMode((String) mMapper.map(mHdr));
return true; return true;
} }
@ -303,7 +301,7 @@ class Camera1 extends CameraController {
private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) { private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) {
if (mOptions.getSupportedFlash().contains(mFlash)) { if (mOptions.supports(mFlash)) {
params.setFlashMode((String) mMapper.map(mFlash)); params.setFlashMode((String) mMapper.map(mFlash));
return true; return true;
} }
@ -390,9 +388,15 @@ class Camera1 extends CameraController {
mCamera.takePicture(null, null, null, mCamera.takePicture(null, null, null,
new Camera.PictureCallback() { new Camera.PictureCallback() {
@Override @Override
public void onPictureTaken(byte[] data, Camera camera) { public void onPictureTaken(byte[] data, final Camera camera) {
mIsCapturingImage = false; 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); mCameraCallbacks.processImage(data, consistentWithView, exifFlip);
} }
}); });
@ -443,16 +447,6 @@ class Camera1 extends CameraController {
return true; return true;
} }
@Override
Size getCaptureSize() {
return mCaptureSize;
}
@Override
Size getPreviewSize() {
return mPreviewSize;
}
@Override @Override
boolean shouldFlipSizes() { boolean shouldFlipSizes() {
return mSensorOffset % 180 != 0; return mSensorOffset % 180 != 0;
@ -463,17 +457,6 @@ class Camera1 extends CameraController {
return mCamera != null; return mCamera != null;
} }
@Nullable
@Override
ExtraProperties getExtraProperties() {
return mExtraProperties;
}
@Nullable
@Override
CameraOptions getCameraOptions() {
return mOptions;
}
// Internal: // Internal:
@ -715,7 +698,8 @@ class Camera1 extends CameraController {
public void onAutoFocus(boolean success, Camera camera) { public void onAutoFocus(boolean success, Camera camera) {
// TODO lock auto exposure and white balance for a while // TODO lock auto exposure and white balance for a while
mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); 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<Camera.Area> computeMeteringAreas(double viewClickX, double viewClickY) { private List<Camera.Area> computeMeteringAreas(double viewClickX, double viewClickY) {
// Event came in view coordinates. We must rotate to sensor coordinates. // Event came in view coordinates. We must rotate to sensor coordinates.
// First, rescale to the -1000 ... 1000 range. // First, rescale to the -1000 ... 1000 range.

@ -12,6 +12,7 @@ import android.hardware.camera2.params.StreamConfigurationMap;
import android.location.Location; import android.location.Location;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log; import android.util.Log;
import java.io.File; import java.io.File;
@ -29,8 +30,6 @@ class Camera2 extends CameraController {
private String mCameraId; private String mCameraId;
private Size mCaptureSize;
private Size mPreviewSize;
private Mapper mMapper = new Mapper.Mapper2(); private Mapper mMapper = new Mapper.Mapper2();
private final HashMap<String, ExtraProperties> mExtraPropertiesMap = new HashMap<>(); private final HashMap<String, ExtraProperties> mExtraPropertiesMap = new HashMap<>();
@ -79,13 +78,15 @@ class Camera2 extends CameraController {
// CameraImpl: // CameraImpl:
@WorkerThread
@Override @Override
void start() { void onStart() {
} }
@WorkerThread
@Override @Override
void stop() { void onStop() {
} }
@ -186,57 +187,7 @@ class Camera2 extends CameraController {
return false; return false;
} }
@Override
Size getCaptureSize() {
if (mCaptureSize == null && mCameraCharacteristics != null) {
TreeSet<Size> sizes = new TreeSet<>();
sizes.addAll(getAvailableCaptureResolutions());
/* TreeSet<AspectRatio> aspectRatios = new CommonAspectRatioFilter(
getAvailablePreviewResolutions(),
getAvailableCaptureResolutions()
).filter();
AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null;
Iterator<Size> 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<Size> sizes = new TreeSet<>();
sizes.addAll(getAvailablePreviewResolutions());
/* TreeSet<AspectRatio> aspectRatios = new CommonAspectRatioFilter(
getAvailablePreviewResolutions(),
getAvailableCaptureResolutions()
).filter();
AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null;
Iterator<Size> 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 @Override
boolean shouldFlipSizes() { boolean shouldFlipSizes() {
@ -253,22 +204,9 @@ class Camera2 extends CameraController {
return true; 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<Size> getAvailableCaptureResolutions() { private List<Size> getAvailableCaptureResolutions() {
List<Size> output = new ArrayList<>(); List<Size> output = new ArrayList<>();

@ -4,6 +4,7 @@ import android.graphics.PointF;
import android.location.Location; import android.location.Location;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import java.io.File; import java.io.File;
@ -19,47 +20,153 @@ abstract class CameraController implements Preview.SurfaceCallback {
protected SessionType mSessionType; protected SessionType mSessionType;
protected Hdr mHdr; 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) { CameraController(CameraView.CameraCallbacks callback, Preview preview) {
mCameraCallbacks = callback; mCameraCallbacks = callback;
mPreview = preview; mPreview = preview;
mPreview.setSurfaceCallback(this); 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(); // Stops the preview asynchronously.
abstract void stop(); 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); //region Rotation callbacks
abstract void onDeviceOrientation(int deviceOrientation);
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 setZoom(float zoom);
abstract boolean setExposureCorrection(float EVvalue); abstract boolean setExposureCorrection(float EVvalue);
abstract void setFacing(Facing facing); abstract void setFacing(Facing facing);
abstract void setFlash(Flash flash); abstract void setFlash(Flash flash);
abstract void setWhiteBalance(WhiteBalance whiteBalance); abstract void setWhiteBalance(WhiteBalance whiteBalance);
abstract void setVideoQuality(VideoQuality videoQuality); abstract void setVideoQuality(VideoQuality videoQuality);
abstract void setSessionType(SessionType sessionType); abstract void setSessionType(SessionType sessionType);
abstract void setHdr(Hdr hdr); abstract void setHdr(Hdr hdr);
abstract void setLocation(Location location); abstract void setLocation(Location location);
//endregion
//region APIs
abstract boolean capturePicture(); abstract boolean capturePicture();
abstract boolean captureSnapshot(); abstract boolean captureSnapshot();
abstract boolean startVideo(@NonNull File file); abstract boolean startVideo(@NonNull File file);
abstract boolean endVideo(); 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 shouldFlipSizes(); // Wheter the Sizes should be flipped to match the view orientation.
abstract boolean isCameraOpened(); abstract boolean isCameraOpened();
abstract boolean startAutoFocus(@Nullable Gesture gesture, PointF point); abstract boolean startAutoFocus(@Nullable Gesture gesture, PointF point);
@Nullable abstract ExtraProperties getExtraProperties(); //endregion
@Nullable abstract CameraOptions getCameraOptions();
//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; } //endregion
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; }
} }

@ -88,6 +88,74 @@ public class CameraOptions {
CameraOptions(CameraCharacteristics params) {} 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. * Set of supported facing values.
* *

@ -1,7 +1,6 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -48,31 +47,20 @@ public class CameraView extends FrameLayout {
private final static int DEFAULT_JPEG_QUALITY = 100; private final static int DEFAULT_JPEG_QUALITY = 100;
private final static boolean DEFAULT_CROP_OUTPUT = false; private final static boolean DEFAULT_CROP_OUTPUT = false;
private Handler mWorkerHandler; // Self managed parameters
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);
}
private int mJpegQuality; private int mJpegQuality;
private boolean mCropOutput; private boolean mCropOutput;
private float mZoomValue;
private float mExposureCorrectionValue;
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
// Components
private CameraCallbacks mCameraCallbacks; private CameraCallbacks mCameraCallbacks;
private OrientationHelper mOrientationHelper; private OrientationHelper mOrientationHelper;
private CameraController mCameraController; private CameraController mCameraController;
private Preview mPreviewImpl; private Preview mPreviewImpl;
// Views
private GridLinesLayout mGridLinesLayout; private GridLinesLayout mGridLinesLayout;
private PinchGestureLayout mPinchGestureLayout; private PinchGestureLayout mPinchGestureLayout;
private TapGestureLayout mTapGestureLayout; private TapGestureLayout mTapGestureLayout;
@ -80,10 +68,9 @@ public class CameraView extends FrameLayout {
private boolean mIsStarted; private boolean mIsStarted;
private boolean mKeepScreenOn; private boolean mKeepScreenOn;
private float mZoomValue; // Threading
private float mExposureCorrectionValue; private Handler mUiHandler;
private WorkerHandler mWorkerHandler;
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
public CameraView(@NonNull Context context) { public CameraView(@NonNull Context context) {
super(context, null); 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())); GestureAction scrollVerticalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value()));
a.recycle(); a.recycle();
// Components
mCameraCallbacks = new CameraCallbacks(); mCameraCallbacks = new CameraCallbacks();
mPreviewImpl = new TextureViewPreview(context, this); mPreviewImpl = new TextureViewPreview(context, this);
mCameraController = new Camera1(mCameraCallbacks, mPreviewImpl); mCameraController = new Camera1(mCameraCallbacks, mPreviewImpl);
mUiHandler = new Handler(Looper.getMainLooper());
mWorkerHandler = new WorkerHandler("CameraViewWorker");
// Views
mGridLinesLayout = new GridLinesLayout(context); mGridLinesLayout = new GridLinesLayout(context);
mPinchGestureLayout = new PinchGestureLayout(context); mPinchGestureLayout = new PinchGestureLayout(context);
mTapGestureLayout = new TapGestureLayout(context); mTapGestureLayout = new TapGestureLayout(context);
@ -134,11 +126,11 @@ public class CameraView extends FrameLayout {
mIsStarted = false; mIsStarted = false;
// Self managed // Apply self managed
setCropOutput(cropOutput); setCropOutput(cropOutput);
setJpegQuality(jpegQuality); setJpegQuality(jpegQuality);
// Camera controller params // Apply camera controller params
setFacing(facing); setFacing(facing);
setFlash(flash); setFlash(flash);
setSessionType(sessionType); setSessionType(sessionType);
@ -146,7 +138,7 @@ public class CameraView extends FrameLayout {
setWhiteBalance(whiteBalance); setWhiteBalance(whiteBalance);
setGrid(grid); setGrid(grid);
// Gestures // Apply gestures
mapGesture(Gesture.TAP, tapGesture); mapGesture(Gesture.TAP, tapGesture);
// mapGesture(Gesture.DOUBLE_TAP, doubleTapGesture); // mapGesture(Gesture.DOUBLE_TAP, doubleTapGesture);
mapGesture(Gesture.LONG_TAP, longTapGesture); mapGesture(Gesture.LONG_TAP, longTapGesture);
@ -155,34 +147,7 @@ public class CameraView extends FrameLayout {
mapGesture(Gesture.SCROLL_VERTICAL, scrollVerticalGesture); mapGesture(Gesture.SCROLL_VERTICAL, scrollVerticalGesture);
if (!isInEditMode()) { if (!isInEditMode()) {
mOrientationHelper = new OrientationHelper(context) { mOrientationHelper = new OrientationHelper(context, mCameraCallbacks);
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);
}
};
} }
} }
@ -497,12 +462,7 @@ public class CameraView extends FrameLayout {
if (checkPermissions(getSessionType())) { if (checkPermissions(getSessionType())) {
mIsStarted = true; mIsStarted = true;
run(new Runnable() { mCameraController.start();
@Override
public void run() {
mCameraController.start();
}
});
} }
} }
@ -584,9 +544,8 @@ public class CameraView extends FrameLayout {
} }
public void destroy() { public void destroy() {
// TODO: this is not strictly needed
mCameraCallbacks.clearListeners(); // Release inner listener. mCameraCallbacks.clearListeners(); // Release inner listener.
// This might be useless, but no time to think about it now.
mWorkerHandler = null;
} }
//endregion //endregion
@ -788,12 +747,7 @@ public class CameraView extends FrameLayout {
* @param facing a facing value. * @param facing a facing value.
*/ */
public void setFacing(final Facing facing) { public void setFacing(final Facing facing) {
run(new Runnable() { mCameraController.setFacing(facing);
@Override
public void run() {
mCameraController.setFacing(facing);
}
});
} }
@ -1212,20 +1166,21 @@ public class CameraView extends FrameLayout {
} }
} }
class CameraCallbacks {
private ArrayList<CameraListener> mListeners; class CameraCallbacks implements OrientationHelper.Callbacks {
private Handler uiHandler;
// Outer listeners
private ArrayList<CameraListener> mListeners = new ArrayList<>(2);
CameraCallbacks() { // Orientation TODO: move this logic into OrientationHelper
mListeners = new ArrayList<>(2); private Integer mDisplayOffset;
uiHandler = new Handler(Looper.getMainLooper()); private Integer mDeviceOrientation;
}
CameraCallbacks() {}
public void dispatchOnCameraOpened(final CameraOptions options) { public void dispatchOnCameraOpened(final CameraOptions options) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
@ -1237,7 +1192,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnCameraClosed() { public void dispatchOnCameraClosed() {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
@ -1253,7 +1208,7 @@ public class CameraView extends FrameLayout {
// Request a layout pass for onMeasure() to do its stuff. // Request a layout pass for onMeasure() to do its stuff.
// Potentially this will change CameraView size, which changes Surface size, // Potentially this will change CameraView size, which changes Surface size,
// which triggers a new Preview size. But hopefully it will converge. // which triggers a new Preview size. But hopefully it will converge.
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
requestLayout(); requestLayout();
@ -1280,7 +1235,7 @@ public class CameraView extends FrameLayout {
* because it was taken with the front camera. * because it was taken with the front camera.
*/ */
public void processImage(final byte[] jpeg, final boolean consistentWithView, final boolean flipHorizontally) { public void processImage(final byte[] jpeg, final boolean consistentWithView, final boolean flipHorizontally) {
getWorkerHandler().post(new Runnable() { mWorkerHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
byte[] jpeg2 = jpeg; byte[] jpeg2 = jpeg;
@ -1300,25 +1255,30 @@ public class CameraView extends FrameLayout {
} }
public void processSnapshot(YuvImage yuv, boolean consistentWithView, boolean flipHorizontally) { public void processSnapshot(final YuvImage yuv, final boolean consistentWithView, boolean flipHorizontally) {
byte[] jpeg; mWorkerHandler.post(new Runnable() {
if (mCropOutput && mPreviewImpl.isCropping()) { @Override
int w = consistentWithView ? getWidth() : getHeight(); public void run() {
int h = consistentWithView ? getHeight() : getWidth(); byte[] jpeg;
AspectRatio targetRatio = AspectRatio.of(w, h); if (mCropOutput && mPreviewImpl.isCropping()) {
jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality); int w = consistentWithView ? getWidth() : getHeight();
} else { int h = consistentWithView ? getHeight() : getWidth();
ByteArrayOutputStream out = new ByteArrayOutputStream(); AspectRatio targetRatio = AspectRatio.of(w, h);
yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out); jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality);
jpeg = out.toByteArray(); } else {
} ByteArrayOutputStream out = new ByteArrayOutputStream();
dispatchOnPictureTaken(jpeg); yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out);
jpeg = out.toByteArray();
}
dispatchOnPictureTaken(jpeg);
}
});
} }
private void dispatchOnPictureTaken(byte[] jpeg) { private void dispatchOnPictureTaken(byte[] jpeg) {
final byte[] data = jpeg; final byte[] data = jpeg;
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
@ -1330,7 +1290,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnVideoTaken(final File video) { public void dispatchOnVideoTaken(final File video) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
@ -1342,7 +1302,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnFocusStart(@Nullable final Gesture gesture, final PointF point) { public void dispatchOnFocusStart(@Nullable final Gesture gesture, final PointF point) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (gesture != null && mGestureMap.get(gesture) == GestureAction.FOCUS_WITH_MARKER) { 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, public void dispatchOnFocusEnd(@Nullable final Gesture gesture, final boolean success,
final PointF point) { final PointF point) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (success && mUseSounds) { 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) { private void dispatchOnOrientationChanged(final int value) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
@ -1392,7 +1372,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnZoomChanged(final float newValue, final PointF[] fingers) { public void dispatchOnZoomChanged(final float newValue, final PointF[] fingers) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {
@ -1406,7 +1386,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnExposureCorrectionChanged(final float newValue, public void dispatchOnExposureCorrectionChanged(final float newValue,
final float[] bounds, final float[] bounds,
final PointF[] fingers) { final PointF[] fingers) {
uiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
for (CameraListener listener : mListeners) { for (CameraListener listener : mListeners) {

@ -2,14 +2,13 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.support.annotation.NonNull;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import android.view.Display; import android.view.Display;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
import android.view.Surface; import android.view.Surface;
abstract class OrientationHelper { class OrientationHelper {
private final OrientationEventListener mOrientationEventListener;
private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
static { static {
@ -19,13 +18,20 @@ abstract class OrientationHelper {
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
} }
private final OrientationEventListener mListener;
private final Callbacks mCallbacks;
private Display mDisplay; private Display mDisplay;
private int mLastKnownDisplayOffset = -1; private int mLastKnownDisplayOffset = -1;
private int mLastOrientation = -1; private int mLastOrientation = -1;
OrientationHelper(Context context) { interface Callbacks {
mOrientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { 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 @Override
public void onOrientationChanged(int orientation) { public void onOrientationChanged(int orientation) {
@ -44,7 +50,7 @@ abstract class OrientationHelper {
if (or != mLastOrientation) { if (or != mLastOrientation) {
mLastOrientation = or; mLastOrientation = or;
onDeviceOrientationChanged(mLastOrientation); mCallbacks.onDeviceOrientationChanged(mLastOrientation);
} }
// Let's see if display rotation has changed.. but how could it ever change...?? // 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(); final int offset = mDisplay.getRotation();
if (mLastKnownDisplayOffset != offset) { if (mLastKnownDisplayOffset != offset) {
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) { void enable(Display display) {
mDisplay = display; mDisplay = display;
mOrientationEventListener.enable(); mListener.enable();
mLastKnownDisplayOffset = DISPLAY_ORIENTATIONS.get(display.getRotation()); mLastKnownDisplayOffset = DISPLAY_ORIENTATIONS.get(display.getRotation());
onDisplayOffsetChanged(mLastKnownDisplayOffset); mCallbacks.onDisplayOffsetChanged(mLastKnownDisplayOffset);
} }
void disable() { void disable() {
mOrientationEventListener.disable(); mListener.disable();
mDisplay = null; mDisplay = null;
} }
protected abstract void onDisplayOffsetChanged(int displayOffset);
protected abstract void onDeviceOrientationChanged(int deviceOrientation);
} }

@ -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);
}
}

@ -33,8 +33,6 @@ abstract class Preview {
abstract View getView(); abstract View getView();
abstract Class getOutputClass(); abstract Class getOutputClass();
abstract boolean isReady(); abstract boolean isReady();
protected void onDisplayOffset(int displayOrientation) {}
protected void onDeviceOrientation(int deviceOrientation) {}
SurfaceHolder getSurfaceHolder() { SurfaceHolder getSurfaceHolder() {
return null; return null;
} }

Loading…
Cancel
Save