Heavy refactoring, setParameters bug fixed

pull/1/head
Mattia Iavarone 7 years ago
parent 474d26e0c4
commit 3dbd7fa945
  1. 430
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  2. 17
      camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java
  3. 3
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  4. 38
      camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java
  5. 11
      camerakit/src/main/base/com/flurgle/camerakit/SurfaceViewPreview.java
  6. 9
      camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java
  7. 13
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  8. 5
      demo/src/main/AndroidManifest.xml
  9. 31
      demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
  10. 14
      demo/src/main/java/com/flurgle/camerakit/demo/PicturePreviewActivity.java
  11. 21
      demo/src/main/java/com/flurgle/camerakit/demo/ResultHolder.java
  12. 63
      demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java
  13. 16
      demo/src/main/res/layout/activity_main.xml
  14. 0
      demo/src/main/res/layout/activity_picture_preview.xml
  15. 39
      demo/src/main/res/layout/activity_video_preview.xml

@ -8,11 +8,10 @@ import android.media.MediaRecorder;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import java.io.File;
import java.io.IOException;
@ -38,9 +37,7 @@ class Camera1 extends CameraImpl {
private int mCameraId;
private Camera mCamera;
private Camera.Parameters mCameraParameters;
private ExtraProperties mExtraProperties;
private Camera.CameraInfo mCameraInfo;
private Size mPreviewSize;
private Size mCaptureSize;
private MediaRecorder mMediaRecorder;
@ -63,48 +60,60 @@ class Camera1 extends CameraImpl {
private double mLongitude;
private boolean mFocusOnTap;
private Handler mHandler = new Handler();
private Handler mFocusHandler = new Handler();
private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1();
private boolean mIsSetup = false;
private final Object mLock = new Object();
Camera1(CameraView.CameraListenerWrapper callback, PreviewImpl preview) {
super(callback, preview);
preview.setCallback(new PreviewImpl.OnPreviewSurfaceChangedCallback() {
@Override
public void onPreviewSurfaceChanged() {
if (mCamera != null) {
setupPreview();
computeCameraSizes();
adjustCameraParameters();
}
}
});
mCameraInfo = new Camera.CameraInfo();
Camera1(CameraView.CameraListenerWrapper callback, final PreviewImpl preview) {
super(callback, preview);
}
// CameraImpl:
/**
* Preview surface is now available. If camera is open, set up.
*/
@Override
void start() {
setFacing(mFacing);
openCamera();
if (mPreview.isReady()) setupPreview();
mCamera.startPreview();
public void onSurfaceAvailable() {
Log.e(TAG, "onSurfaceAvailable, size is "+mPreview.getSurfaceSize());
if (shouldSetup()) setup();
}
/**
* Preview surface did change its size. Compute a new preview size.
* This requires stopping and restarting the preview.
*/
@Override
void stop() {
if (mCamera != null) mCamera.stopPreview();
mHandler.removeCallbacksAndMessages(null);
releaseCamera();
public void onSurfaceChanged() {
Log.e(TAG, "onSurfaceChanged, size is "+mPreview.getSurfaceSize());
if (mIsSetup) {
// Compute a new camera preview size.
Size newSize = computePreviewSize();
if (!newSize.equals(mPreviewSize)) {
mPreviewSize = newSize;
synchronized (mLock) {
mCamera.stopPreview();
Camera.Parameters params = mCamera.getParameters();
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mCamera.setParameters(params);
}
boolean invertPreviewSizes = shouldFlipSizes();
mPreview.setDesiredSize(
invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(),
invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight()
);
mCamera.startPreview();
}
}
}
/**
* Sets the output stream for our preview.
* To be called when preview is ready.
*/
private void setupPreview() {
private boolean shouldSetup() {
return isCameraOpened() && mPreview.isReady() && !mIsSetup;
}
// The act of binding an "open" camera to a "ready" preview.
// These can happen at different times but we want to end up here.
private void setup() {
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
@ -114,9 +123,79 @@ class Camera1 extends CameraImpl {
} catch (IOException e) {
throw new RuntimeException(e);
}
boolean invertPreviewSizes = shouldFlipSizes(); // mDisplayOffset % 180 != 0;
mCaptureSize = computeCaptureSize();
mPreviewSize = computePreviewSize();
mPreview.setDesiredSize(
invertPreviewSizes ? mPreviewSize.getHeight() : mPreviewSize.getWidth(),
invertPreviewSizes ? mPreviewSize.getWidth() : mPreviewSize.getHeight()
);
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- not allowed during preview
mCamera.setParameters(params);
}
mCamera.startPreview();
mIsSetup = true;
}
@Override
void start() {
if (isCameraOpened()) stop();
if (collectCameraId()) {
mCamera = Camera.open(mCameraId);
mCameraListener.onCameraOpened();
// Set parameters that might have been set before the camera was opened.
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
mergeFocus(params, mFocus);
mergeFlash(params, mFlash);
mergeLocation(params, mLatitude, mLongitude);
mergeWhiteBalance(params, mWhiteBalance);
params.setRecordingHint(mSessionType == SESSION_TYPE_VIDEO);
mCamera.setParameters(params);
}
// Try starting preview.
mCamera.setDisplayOrientation(computeCameraToDisplayOffset()); // <- not allowed during preview
if (shouldSetup()) setup();
collectExtraProperties();
}
}
@Override
void stop() {
mFocusHandler.removeCallbacksAndMessages(null);
if (isCameraOpened()) {
mCamera.stopPreview();
mCamera.release();
mCameraListener.onCameraClosed();
}
mCamera = null;
mPreviewSize = null;
mCaptureSize = null;
mIsSetup = false;
}
private boolean collectCameraId() {
int internalFacing = mMapper.mapFacing(mFacing);
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == internalFacing) {
mSensorOffset = cameraInfo.orientation;
mCameraId = i;
return true;
}
}
return false;
}
@Override
void onDisplayOffset(int displayOrientation) {
// I doubt this will ever change.
@ -134,7 +213,6 @@ class Camera1 extends CameraImpl {
if (sessionType != mSessionType) {
mSessionType = sessionType;
if (isCameraOpened()) {
stop();
start();
}
}
@ -142,119 +220,129 @@ class Camera1 extends CameraImpl {
@Override
void setLocation(double latitude, double longitude) {
double oldLat = mLatitude;
double oldLong = mLongitude;
mLatitude = latitude;
mLongitude = longitude;
if (mCameraParameters != null) {
// Sometimes this will fail... I have no idea why.
// Since native_setParameters is quite a black box, there's nothing we can do about it.
try {
mCameraParameters.setGpsLatitude(latitude);
mCameraParameters.setGpsLongitude(longitude);
// mCameraParameters.setGpsAltitude(0);
// mCameraParameters.setGpsTimestamp(System.currentTimeMillis());
// mCameraParameters.setGpsProcessingMethod("GPS");
mCamera.setParameters(mCameraParameters);
} catch (Exception e) {
// Reset or everything after will throw as well.
e.printStackTrace();
mCameraParameters = mCamera.getParameters();
if (isCameraOpened()) {
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
if (mergeLocation(params, oldLat, oldLong)) mCamera.setParameters(params);
}
}
}
private boolean mergeLocation(Camera.Parameters params, double oldLatitude, double oldLongitude) {
if (mLatitude != 0 && mLongitude != 0) {
params.setGpsLatitude(mLatitude);
params.setGpsLongitude(mLongitude);
params.setGpsTimestamp(System.currentTimeMillis());
params.setGpsProcessingMethod("Unknown");
}
return true;
}
@Override
void setFacing(@Facing int facing) {
int internalFacing = mMapper.mapFacing(facing);
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == internalFacing) {
mSensorOffset = mCameraInfo.orientation;
mCameraId = i;
mFacing = facing;
break;
if (facing != mFacing) {
mFacing = facing;
if (collectCameraId() && isCameraOpened()) {
start();
}
}
if (mFacing == facing && isCameraOpened()) {
stop();
start();
}
}
@Override
void setWhiteBalance(@WhiteBalance int whiteBalance) {
int old = mWhiteBalance;
mWhiteBalance = whiteBalance;
if (mCameraParameters != null) {
List<String> supported = mCameraParameters.getSupportedWhiteBalance();
String internal = mMapper.mapWhiteBalance(whiteBalance);
if (supported != null && supported.contains(internal)) {
mCameraParameters.setWhiteBalance(internal);
mCamera.setParameters(mCameraParameters);
} else {
mWhiteBalance = old;
if (isCameraOpened()) {
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
if (mergeWhiteBalance(params, old)) mCamera.setParameters(params);
}
}
}
private boolean mergeWhiteBalance(Camera.Parameters params, @WhiteBalance int oldWhiteBalance) {
List<String> supported = params.getSupportedWhiteBalance();
String internal = mMapper.mapWhiteBalance(mWhiteBalance);
if (supported != null && supported.contains(internal)) {
params.setWhiteBalance(internal);
return true;
}
mWhiteBalance = oldWhiteBalance;
return false;
}
@Override
void setFlash(@Flash int flash) {
int old = mFlash;
mFlash = flash;
if (mCameraParameters != null) {
List<String> flashes = mCameraParameters.getSupportedFlashModes();
String internalFlash = mMapper.mapFlash(flash);
if (flashes != null && flashes.contains(internalFlash)) {
mCameraParameters.setFlashMode(internalFlash);
mCamera.setParameters(mCameraParameters);
} else {
mFlash = old;
if (isCameraOpened()) {
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
if (mergeFlash(params, old)) mCamera.setParameters(params);
}
}
}
private boolean mergeFlash(Camera.Parameters params, @Flash int oldFlash) {
List<String> flashes = params.getSupportedFlashModes();
String internalFlash = mMapper.mapFlash(mFlash);
if (flashes != null && flashes.contains(internalFlash)) {
params.setFlashMode(internalFlash);
return true;
}
mFlash = oldFlash;
return false;
}
@Override
void setFocus(@Focus int focus) {
int old = mFocus;
mFocus = focus;
if (mCameraParameters == null) return;
switch (focus) {
case FOCUS_CONTINUOUS:
mFocusOnTap = false;
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else {
mFocus = old;
}
break;
if (isCameraOpened()) {
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
if (mergeFocus(params, old)) mCamera.setParameters(params);
}
}
mFocusOnTap = mFocus == FOCUS_TAP || mFocus == FOCUS_TAP_WITH_MARKER;
}
private boolean mergeFocus(Camera.Parameters params, @Focus int oldFocus) {
final List<String> modes = params.getSupportedFocusModes();
switch (mFocus) {
case FOCUS_CONTINUOUS:
case FOCUS_TAP:
case FOCUS_TAP_WITH_MARKER:
mFocusOnTap = true;
final List<String> modes1 = mCameraParameters.getSupportedFocusModes();
if (modes1.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else {
mFocus = old;
if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
return true;
}
break;
mFocus = oldFocus;
return false;
case FOCUS_OFF:
mFocusOnTap = false;
final List<String> modes2 = mCameraParameters.getSupportedFocusModes();
if (modes2.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
} else if (modes2.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
} else {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
return true;
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
return true;
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
return true;
}
break;
}
return false;
}
@Override
void setZoom(@ZoomMode int zoom) {
this.mZoom = zoom;
@ -263,26 +351,29 @@ class Camera1 extends CameraImpl {
@Override
void setVideoQuality(int videoQuality) {
this.mVideoQuality = videoQuality;
// TODO: restore preview size if needed.
}
@Override
void captureImage() {
if (isCapturingImage) return;
if (mCamera == null) return;
if (!isCameraOpened()) return;
switch (mSessionType) {
case SESSION_TYPE_PICTURE:
// Set boolean to wait for image callback
isCapturingImage = true;
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(computeExifOrientation());
mCamera.setParameters(parameters);
synchronized (mLock) {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(computeExifOrientation());
mCamera.setParameters(parameters);
}
mCamera.takePicture(null, null, null,
new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
mCameraListener.onPictureTaken(data);
isCapturingImage = false;
camera.startPreview(); // TODO: is this needed? why?
camera.startPreview(); // This is needed, read somewhere in the docs.
}
});
break;
@ -297,13 +388,14 @@ class Camera1 extends CameraImpl {
public void onPreviewFrame(final byte[] data, Camera camera) {
// Got to rotate the preview frame, since byte[] data here does not include
// EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
Camera.Parameters params = mCamera.getParameters();
final int rotation = computeExifOrientation();
final boolean flip = rotation % 180 != 0;
final int preWidth = mPreviewSize.getWidth();
final int preHeight = mPreviewSize.getHeight();
final int postWidth = flip ? preHeight : preWidth;
final int postHeight = flip ? preWidth : preHeight;
final int format = mCameraParameters.getPreviewFormat();
final int format = params.getPreviewFormat();
new Thread(new Runnable() {
@Override
public void run() {
@ -347,38 +439,13 @@ class Camera1 extends CameraImpl {
// Internal:
private void openCamera() {
if (mCamera != null) {
releaseCamera();
}
mCamera = Camera.open(mCameraId);
mCameraParameters = mCamera.getParameters();
collectCameraProperties();
computeCameraSizes();
adjustCameraParameters();
mCamera.setDisplayOrientation(computeCameraToDisplayOffset());
mCameraListener.onCameraOpened();
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
mCameraParameters = null;
mPreviewSize = null;
mCaptureSize = null;
mCameraListener.onCameraClosed();
}
}
/**
* Returns how much should the sensor image be rotated before being shown.
* It is meant to be fed to Camera.setDisplayOrientation().
*/
private int computeCameraToDisplayOffset() {
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
if (mFacing == CameraKit.Constants.FACING_FRONT) {
// or: (360 - ((info.orientation + displayOrientation) % 360)) % 360;
return ((mSensorOffset - mDisplayOffset) + 360 + 180) % 360;
} else {
@ -399,45 +466,35 @@ class Camera1 extends CameraImpl {
* This is called either on cameraView.start(), or when the underlying surface changes.
* It is possible that in the first call the preview surface has not already computed its
* dimensions.
* But when it does, the {@link PreviewImpl.OnPreviewSurfaceChangedCallback} should be called,
* But when it does, the {@link PreviewImpl.SurfaceCallback} should be called,
* and this should be refreshed.
*/
private void computeCameraSizes() {
mCameraParameters.setRecordingHint(mSessionType == SESSION_TYPE_VIDEO);
List<Size> previewSizes = sizesFromList(mCameraParameters.getSupportedPreviewSizes());
private Size computeCaptureSize() {
Camera.Parameters params = mCamera.getParameters();
if (mSessionType == SESSION_TYPE_PICTURE) {
// Choose the max size.
List<Size> captureSizes = sizesFromList(mCameraParameters.getSupportedPictureSizes());
mCaptureSize = Collections.max(captureSizes);
List<Size> captureSizes = sizesFromList(params.getSupportedPictureSizes());
return Collections.max(captureSizes);
} else {
// Choose according to developer choice in setVideoQuality.
// The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc.
// So its output is our output.
CamcorderProfile camcorderProfile = getCamcorderProfile(mVideoQuality);
mCaptureSize = new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
return new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
}
mPreviewSize = computePreviewSize(previewSizes, mCaptureSize, mPreview.getSurfaceSize());
// Log.e("Camera1", "CaptureSize is "+mCaptureSize.toString());
// Log.e("Camera1", "PreviewSize is "+mPreviewSize.toString());
}
private void adjustCameraParameters() {
boolean invertPreviewSizes = shouldFlipSizes(); // mDisplayOffset % 180 != 0;
mPreview.setDesiredSize(
invertPreviewSizes ? getPreviewSize().getHeight() : getPreviewSize().getWidth(),
invertPreviewSizes ? getPreviewSize().getWidth() : getPreviewSize().getHeight()
);
mCameraParameters.setPreviewSize(getPreviewSize().getWidth(), getPreviewSize().getHeight());
mCameraParameters.setPictureSize(getCaptureSize().getWidth(), getCaptureSize().getHeight());
setFocus(mFocus);
setFlash(mFlash);
setLocation(mLatitude, mLongitude);
mCamera.setParameters(mCameraParameters);
private Size computePreviewSize() {
Camera.Parameters params = mCamera.getParameters();
List<Size> previewSizes = sizesFromList(params.getSupportedPreviewSizes());
return computePreviewSize(previewSizes, mCaptureSize, mPreview.getSurfaceSize());
}
private void collectCameraProperties() {
mExtraProperties = new ExtraProperties(mCameraParameters.getVerticalViewAngle(),
mCameraParameters.getHorizontalViewAngle());
private void collectExtraProperties() {
Camera.Parameters params = mCamera.getParameters();
mExtraProperties = new ExtraProperties(params.getVerticalViewAngle(),
params.getHorizontalViewAngle());
}
@ -546,23 +603,26 @@ class Camera1 extends CameraImpl {
if (!mFocusOnTap) return;
if (mCamera == null) return;
if (event.getAction() != MotionEvent.ACTION_UP) return;
Camera.Parameters parameters = mCamera.getParameters();
Rect rect = calculateFocusArea(event.getX(), event.getY());
List<Camera.Area> meteringAreas = new ArrayList<>();
meteringAreas.add(new Camera.Area(rect, getFocusMeteringAreaWeight()));
boolean autofocusSupported = parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO);
if (autofocusSupported) {
if (parameters.getMaxNumFocusAreas() > 0) parameters.setFocusAreas(meteringAreas);
if (parameters.getMaxNumMeteringAreas() > 0) parameters.setMeteringAreas(meteringAreas);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(parameters);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
resetFocus(success, camera);
}
});
synchronized (mLock) {
Camera.Parameters parameters = mCamera.getParameters();
boolean autofocusSupported = parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO);
if (autofocusSupported) {
if (parameters.getMaxNumFocusAreas() > 0) parameters.setFocusAreas(meteringAreas);
if (parameters.getMaxNumMeteringAreas() > 0)
parameters.setMeteringAreas(meteringAreas);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(parameters);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
resetFocus(success, camera);
}
});
}
}
}
@ -578,18 +638,20 @@ class Camera1 extends CameraImpl {
private void resetFocus(final boolean success, final Camera camera) {
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(new Runnable() {
mFocusHandler.removeCallbacksAndMessages(null);
mFocusHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (camera != null) {
camera.cancelAutoFocus();
Camera.Parameters params = camera.getParameters();
if (!params.getFocusMode().equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
params.setFocusAreas(null);
params.setMeteringAreas(null);
camera.setParameters(params);
synchronized (mLock) {
Camera.Parameters params = camera.getParameters();
if (!params.getFocusMode().equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
params.setFocusAreas(null);
params.setMeteringAreas(null);
mCamera.setParameters(params);
}
}
if (mAutofocusCallback != null) mAutofocusCallback.onAutoFocus(success, camera);

@ -37,15 +37,18 @@ class Camera2 extends CameraImpl {
private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper2();
private final HashMap<String, ExtraProperties> mExtraPropertiesMap = new HashMap<>();
Camera2(CameraView.CameraListenerWrapper callback, PreviewImpl preview, Context context) {
super(callback, preview);
preview.setCallback(new PreviewImpl.OnPreviewSurfaceChangedCallback() {
@Override
public void onPreviewSurfaceChanged() {
@Override
public void onSurfaceChanged() {
}
});
}
@Override
public void onSurfaceAvailable() {
}
Camera2(CameraView.CameraListenerWrapper callback, PreviewImpl preview, Context context) {
super(callback, preview);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
// Get all view angles

@ -6,7 +6,7 @@ import android.view.MotionEvent;
import java.io.File;
abstract class CameraImpl {
abstract class CameraImpl implements PreviewImpl.SurfaceCallback {
protected final CameraView.CameraListenerWrapper mCameraListener;
protected final PreviewImpl mPreview;
@ -14,6 +14,7 @@ abstract class CameraImpl {
CameraImpl(CameraView.CameraListenerWrapper callback, PreviewImpl preview) {
mCameraListener = callback;
mPreview = preview;
mPreview.setSurfaceCallback(this);
}
abstract void start();

@ -7,11 +7,12 @@ import android.view.View;
abstract class PreviewImpl {
interface OnPreviewSurfaceChangedCallback {
void onPreviewSurfaceChanged();
interface SurfaceCallback {
void onSurfaceAvailable();
void onSurfaceChanged();
}
private OnPreviewSurfaceChangedCallback mOnPreviewSurfaceChangedCallback;
private SurfaceCallback mSurfaceCallback;
// As far as I can see, these are the view/surface dimensions.
// This live in the 'View' orientation.
@ -22,8 +23,8 @@ abstract class PreviewImpl {
private int mDesiredWidth;
private int mDesiredHeight;
void setCallback(OnPreviewSurfaceChangedCallback callback) {
mOnPreviewSurfaceChangedCallback = callback;
void setSurfaceCallback(SurfaceCallback callback) {
mSurfaceCallback = callback;
}
abstract Surface getSurface();
@ -37,10 +38,6 @@ abstract class PreviewImpl {
abstract boolean isReady();
protected void dispatchSurfaceChanged() {
mOnPreviewSurfaceChangedCallback.onPreviewSurfaceChanged();
}
SurfaceHolder getSurfaceHolder() {
return null;
}
@ -49,12 +46,27 @@ abstract class PreviewImpl {
return null;
}
protected final void onSurfaceAvailable(int width, int height) {
mSurfaceWidth = width;
mSurfaceHeight = height;
refreshScale();
mSurfaceCallback.onSurfaceAvailable();
}
// As far as I can see, these are the view/surface dimensions.
// This is called by subclasses.
protected void setSurfaceSize(int width, int height) {
this.mSurfaceWidth = width;
this.mSurfaceHeight = height;
refreshScale(); // Refresh true preview size to adjust scaling
protected final void onSurfaceSizeChanged(int width, int height) {
if (width != mSurfaceWidth || height != mSurfaceHeight) {
mSurfaceWidth = width;
mSurfaceHeight = height;
refreshScale();
mSurfaceCallback.onSurfaceChanged();
}
}
protected final void onSurfaceDestroyed() {
mSurfaceWidth = 0;
mSurfaceHeight = 0;
refreshScale();
}
// As far as I can see, these are the actual preview dimensions, as set in CameraParameters.

@ -1,12 +1,9 @@
package com.flurgle.camerakit;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
@ -23,19 +20,17 @@ class SurfaceViewPreview extends PreviewImpl {
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
setSurfaceSize(mSurfaceView.getWidth(), mSurfaceView.getHeight());
dispatchSurfaceChanged();
onSurfaceAvailable(mSurfaceView.getWidth(), mSurfaceView.getHeight());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
setSurfaceSize(width, height);
dispatchSurfaceChanged();
onSurfaceSizeChanged(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
setSurfaceSize(0, 0);
onSurfaceDestroyed();
}
});
}

@ -21,20 +21,17 @@ class TextureViewPreview extends PreviewImpl {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
setSurfaceSize(width, height);
dispatchSurfaceChanged();
onSurfaceAvailable(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
setSurfaceSize(width, height);
dispatchSurfaceChanged();
// refreshScale();
onSurfaceSizeChanged(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
setSurfaceSize(0, 0);
onSurfaceDestroyed();
return true;
}

@ -203,12 +203,14 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!mAdjustViewBounds) {
Log.e(TAG, "onMeasure, adjustViewBounds=false");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
Size previewSize = getPreviewSize();
if (previewSize == null) { // Early measure.
Log.e(TAG, "onMeasure, early measure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
@ -218,21 +220,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
boolean flip = mCameraImpl.shouldFlipSizes();
float previewWidth = flip ? previewSize.getHeight() : previewSize.getWidth();
float previewHeight = flip ? previewSize.getWidth() : previewSize.getHeight();
float parentHeight = MeasureSpec.getSize(heightMeasureSpec);
float parentWidth = MeasureSpec.getSize(widthMeasureSpec); // mode = AT_MOST
Log.e(TAG, "onMeasure, parent size is "+new Size((int)parentWidth, (int)parentHeight)); // 1080x1794
Log.e(TAG, "onMeasure, surface size is "+new Size((int)previewWidth, (int)previewHeight)); // 1600x1200
if (wwc && hwc) {
// If both dimensions are WRAP_CONTENT, let's try to fit the preview size perfectly
// without cropping.
// TODO: This should actually be a flag, like scaleMode, that replaces cropOutput and adjustViewBounds.
// This is like a fitCenter.
// preview: 1600x1200
// parent: 1080x1794
float parentHeight = MeasureSpec.getSize(heightMeasureSpec);
float parentWidth = MeasureSpec.getSize(widthMeasureSpec); // mode = AT_MOST
float targetRatio = previewHeight / previewWidth;
float currentRatio = parentHeight / parentWidth;
Log.e(TAG, "parentHeight="+parentHeight+", parentWidth="+parentWidth);
Log.e(TAG, "previewHeight="+previewHeight+", previewWidth="+previewWidth);
if (currentRatio > targetRatio) {
// View is too tall. Must reduce height.
int newHeight = (int) (parentWidth * targetRatio);

@ -22,8 +22,9 @@
</activity>
<activity
android:name=".PreviewActivity"
android:screenOrientation="portrait" />
android:name=".PicturePreviewActivity" />
<activity android:name=".VideoPreviewActivity"/>
</application>

@ -3,6 +3,7 @@ package com.flurgle.camerakit.demo;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
@ -34,8 +35,8 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
// Capture Mode:
@BindView(R.id.captureModeRadioGroup)
RadioGroup captureModeRadioGroup;
@BindView(R.id.sessionTypeRadioGroup)
RadioGroup sessionTypeRadioGroup;
// Crop Mode:
@ -84,7 +85,7 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
camera.addOnLayoutChangeListener(this);
captureModeRadioGroup.setOnCheckedChangeListener(captureModeChangedListener);
sessionTypeRadioGroup.setOnCheckedChangeListener(sessionTypeChangedListener);
cropModeRadioGroup.setOnCheckedChangeListener(cropModeChangedListener);
widthModeRadioGroup.setOnCheckedChangeListener(widthModeChangedListener);
heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener);
@ -122,12 +123,8 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length);
ResultHolder.dispose();
ResultHolder.setImage(bitmap);
ResultHolder.setNativeCaptureSize(
captureModeRadioGroup.getCheckedRadioButtonId() == R.id.modeCaptureStandard ?
camera.getCaptureSize() : camera.getPreviewSize()
);
ResultHolder.setTimeToCallback(callbackTime - startTime);
Intent intent = new Intent(MainActivity.this, PreviewActivity.class);
Intent intent = new Intent(MainActivity.this, PicturePreviewActivity.class);
startActivity(intent);
}
});
@ -136,6 +133,10 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
@OnClick(R.id.captureVideo)
void captureVideo() {
if (camera.getSessionType() != CameraKit.Constants.SESSION_TYPE_VIDEO) {
Toast.makeText(this, "Can't record video while session type is 'picture'.", Toast.LENGTH_SHORT).show();
return;
}
if (mCapturing) return;
mCapturing = true;
camera.setCameraListener(new CameraListener() {
@ -143,16 +144,20 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
public void onVideoTaken(File video) {
super.onVideoTaken(video);
mCapturing = false;
ResultHolder.dispose();
ResultHolder.setVideo(Uri.fromFile(video));
Intent intent = new Intent(MainActivity.this, VideoPreviewActivity.class);
startActivity(intent);
}
});
Toast.makeText(this, "Recording for 3 seconds...", Toast.LENGTH_LONG).show();
Toast.makeText(this, "Recording for 8 seconds...", Toast.LENGTH_LONG).show();
camera.startCapturingVideo(null);
camera.postDelayed(new Runnable() {
@Override
public void run() {
camera.stopCapturingVideo();
}
}, 3000);
}, 8000);
}
@OnClick(R.id.toggleCamera)
@ -188,16 +193,16 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan
}
}
RadioGroup.OnCheckedChangeListener captureModeChangedListener = new RadioGroup.OnCheckedChangeListener() {
RadioGroup.OnCheckedChangeListener sessionTypeChangedListener = new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mCapturing) return;
camera.setSessionType(
checkedId == R.id.modeCaptureStandard ?
checkedId == R.id.sessionTypePicture ?
CameraKit.Constants.SESSION_TYPE_PICTURE :
CameraKit.Constants.SESSION_TYPE_VIDEO
);
Toast.makeText(MainActivity.this, "Session type set to" + (checkedId == R.id.modeCaptureStandard ? " picture!" : " video!"), Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivity.this, "Session type set to" + (checkedId == R.id.sessionTypePicture ? " picture!" : " video!"), Toast.LENGTH_SHORT).show();
}
};

@ -13,7 +13,7 @@ import com.flurgle.camerakit.Size;
import butterknife.BindView;
import butterknife.ButterKnife;
public class PreviewActivity extends Activity {
public class PicturePreviewActivity extends Activity {
@BindView(R.id.image)
ImageView imageView;
@ -33,7 +33,7 @@ public class PreviewActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preview);
setContentView(R.layout.activity_picture_preview);
ButterKnife.bind(this);
Bitmap bitmap = ResultHolder.getImage();
@ -44,12 +44,10 @@ public class PreviewActivity extends Activity {
imageView.setImageBitmap(bitmap);
Size captureSize = ResultHolder.getNativeCaptureSize();
if (captureSize != null) {
// Native sizes are landscape, hardcode flip because demo app forced to portrait.
AspectRatio aspectRatio = AspectRatio.of(captureSize.getHeight(), captureSize.getWidth());
nativeCaptureResolution.setText(captureSize.getHeight() + " x " + captureSize.getWidth() + " (" + aspectRatio.toString() + ")");
}
// Native sizes are landscape, activity might now. <- not clear what this means but OK
// TODO: ncr and ar might be different when cropOutput is true.
AspectRatio aspectRatio = AspectRatio.of(bitmap.getHeight(), bitmap.getWidth());
nativeCaptureResolution.setText(bitmap.getHeight() + " x " + bitmap.getWidth() + " (" + aspectRatio.toString() + ")");
actualResolution.setText(bitmap.getWidth() + " x " + bitmap.getHeight());
approxUncompressedSize.setText(getApproximateFileMegabytes(bitmap) + "MB");

@ -1,6 +1,7 @@
package com.flurgle.camerakit.demo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.flurgle.camerakit.Size;
@ -12,24 +13,24 @@ public class ResultHolder {
private static WeakReference<Bitmap> image;
private static Size nativeCaptureSize;
private static long timeToCallback;
private static Uri video;
public static void setImage(@Nullable Bitmap image) {
ResultHolder.image = image != null ? new WeakReference<>(image) : null;
public static Uri getVideo() {
return video;
}
@Nullable
public static Bitmap getImage() {
return image != null ? image.get() : null;
public static void setVideo(Uri video) {
ResultHolder.video = video;
}
public static void setNativeCaptureSize(@Nullable Size nativeCaptureSize) {
ResultHolder.nativeCaptureSize = nativeCaptureSize;
public static void setImage(@Nullable Bitmap image) {
ResultHolder.image = image != null ? new WeakReference<>(image) : null;
}
@Nullable
public static Size getNativeCaptureSize() {
return nativeCaptureSize;
public static Bitmap getImage() {
return image != null ? image.get() : null;
}
public static void setTimeToCallback(long timeToCallback) {
@ -42,7 +43,7 @@ public class ResultHolder {
public static void dispose() {
setImage(null);
setNativeCaptureSize(null);
setVideo(null);
setTimeToCallback(0);
}

@ -0,0 +1,63 @@
package com.flurgle.camerakit.demo;
import android.app.Activity;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;
import com.flurgle.camerakit.AspectRatio;
import com.flurgle.camerakit.Size;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class VideoPreviewActivity extends Activity {
@BindView(R.id.video)
VideoView videoView;
@BindView(R.id.actualResolution)
TextView actualResolution;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_preview);
ButterKnife.bind(this);
Uri videoUri = ResultHolder.getVideo();
if (videoUri == null) {
finish();
return;
}
MediaController controller = new MediaController(this);
controller.setAnchorView(videoView);
controller.setMediaPlayer(videoView);
videoView.setMediaController(controller);
videoView.setVideoURI(videoUri);
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
actualResolution.setText(mp.getVideoWidth() + " x " + mp.getVideoHeight());
}
});
playVideo();
}
@OnClick(R.id.video)
void playVideo() {
if (videoView.isPlaying()) return;
videoView.start();
}
}

@ -43,7 +43,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="3">
android:weightSum="4">
<ImageButton
android:id="@+id/capturePhoto"
@ -113,31 +113,31 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="CAPTURE METHOD"
android:text="SESSION TYPE"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/captureModeRadioGroup"
android:id="@+id/sessionTypeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:checkedButton="@+id/modeCaptureStandard"
android:checkedButton="@+id/sessionTypePicture"
android:orientation="horizontal">
<RadioButton
android:id="@+id/modeCaptureStandard"
android:id="@+id/sessionTypePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Standard" />
android:text="Picture" />
<RadioButton
android:id="@+id/modeCaptureStill"
android:id="@+id/sessionTypeVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Still" />
android:text="Video" />
</RadioGroup>

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="@+id/video"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="bottom"
android:gravity="center_vertical"
android:background="@android:color/white"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="ACTUAL RESOLUTION"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/actualResolution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textColor="@android:color/black"
android:textSize="14.5sp" />
</LinearLayout>
</FrameLayout>
Loading…
Cancel
Save