Correct orientation for captured preview frames. Bugs fixed. Refactoring useless classes

pull/1/head
Mattia Iavarone 7 years ago
parent c429a99972
commit 9c912e925b
  1. 67
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  2. 54
      camerakit/src/main/api16/com/flurgle/camerakit/ProcessStillTask.java
  3. 4
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  4. 4
      camerakit/src/main/java/com/flurgle/camerakit/CameraListener.java
  5. 156
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  6. 50
      camerakit/src/main/utils/com/flurgle/camerakit/Rotation.java

@ -46,7 +46,7 @@ class Camera1 extends CameraImpl {
private MediaRecorder mMediaRecorder; private MediaRecorder mMediaRecorder;
private File mVideoFile; private File mVideoFile;
private Camera.AutoFocusCallback mAutofocusCallback; private Camera.AutoFocusCallback mAutofocusCallback;
private boolean capturingImage = false; private boolean isCapturingImage = false;
private int mDisplayOffset; private int mDisplayOffset;
private int mDeviceOrientation; private int mDeviceOrientation;
@ -65,7 +65,7 @@ class Camera1 extends CameraImpl {
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1(); private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1();
Camera1(CameraListener callback, PreviewImpl preview) { Camera1(CameraView.CameraListenerWrapper callback, PreviewImpl preview) {
super(callback, preview); super(callback, preview);
preview.setCallback(new PreviewImpl.OnPreviewSurfaceChangedCallback() { preview.setCallback(new PreviewImpl.OnPreviewSurfaceChangedCallback() {
@Override @Override
@ -144,9 +144,20 @@ class Camera1 extends CameraImpl {
mLatitude = latitude; mLatitude = latitude;
mLongitude = longitude; mLongitude = longitude;
if (mCameraParameters != null) { 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.setGpsLatitude(latitude);
mCameraParameters.setGpsLongitude(longitude); mCameraParameters.setGpsLongitude(longitude);
// mCameraParameters.setGpsAltitude(0);
// mCameraParameters.setGpsTimestamp(System.currentTimeMillis());
// mCameraParameters.setGpsProcessingMethod("GPS");
mCamera.setParameters(mCameraParameters); mCamera.setParameters(mCameraParameters);
} catch (Exception e) {
// Reset or everything after will throw as well.
e.printStackTrace();
mCameraParameters = mCamera.getParameters();
}
} }
} }
@ -260,12 +271,12 @@ class Camera1 extends CameraImpl {
@Override @Override
void captureImage() { void captureImage() {
if (isCapturingImage) return;
if (mCamera == null) return;
switch (mSessionType) { switch (mSessionType) {
case SESSION_TYPE_PICTURE: case SESSION_TYPE_PICTURE:
// Null check required for camera here as is briefly null when View is detached
if (!capturingImage && mCamera != null) {
// Set boolean to wait for image callback // Set boolean to wait for image callback
capturingImage = true; isCapturingImage = true;
Camera.Parameters parameters = mCamera.getParameters(); Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(computeExifOrientation()); parameters.setRotation(computeExifOrientation());
mCamera.setParameters(parameters); mCamera.setParameters(parameters);
@ -274,29 +285,38 @@ class Camera1 extends CameraImpl {
@Override @Override
public void onPictureTaken(byte[] data, Camera camera) { public void onPictureTaken(byte[] data, Camera camera) {
mCameraListener.onPictureTaken(data); mCameraListener.onPictureTaken(data);
// Reset capturing state to allow photos to be taken isCapturingImage = false;
capturingImage = false; camera.startPreview(); // TODO: is this needed? why?
camera.startPreview();
} }
}); });
} else {
Log.w(TAG, "Unable, waiting for picture to be taken");
}
break; break;
case SESSION_TYPE_VIDEO: case SESSION_TYPE_VIDEO:
// If we are in a video session, camera captures are fast captures coming // If we are in a video session, camera captures are fast captures coming
// from the preview stream. // from the preview stream.
// TODO: will calculateCaptureRotation work here? It's the only usage. Test... // TODO: will this work while recording a video? test...
isCapturingImage = true;
mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override @Override
public void onPreviewFrame(byte[] data, Camera camera) { public void onPreviewFrame(final byte[] data, Camera camera) {
new Thread(new ProcessStillTask(data, camera, calculateCaptureRotation(), new ProcessStillTask.OnStillProcessedListener() { // 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.
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();
new Thread(new Runnable() {
@Override @Override
public void onStillProcessed(final YuvImage yuv) { public void run() {
mCameraListener.onPictureTaken(yuv); byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, rotation);
YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null);
mCameraListener.processYuvImage(yuv);
isCapturingImage = false;
} }
})).start(); }).start();
} }
}); });
break; break;
@ -378,16 +398,6 @@ class Camera1 extends CameraImpl {
return (mDeviceOrientation + mSensorOffset) % 360; return (mDeviceOrientation + mSensorOffset) % 360;
} }
private int calculateCaptureRotation() {
int previewRotation = computeCameraToDisplayOffset();
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//Front is flipped
return (previewRotation + 180 + 2* mDisplayOffset + 720) % 360;
} else {
return previewRotation;
}
}
/** /**
* This is called either on cameraView.start(), or when the underlying surface changes. * This is called either on cameraView.start(), or when the underlying surface changes.
@ -411,9 +421,10 @@ class Camera1 extends CameraImpl {
mCaptureSize = new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); mCaptureSize = new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
} }
mPreviewSize = computePreviewSize(previewSizes, mCaptureSize, mPreview.getSurfaceSize()); mPreviewSize = computePreviewSize(previewSizes, mCaptureSize, mPreview.getSurfaceSize());
Log.e("Camera1", "CaptureSize is "+mCaptureSize.toString());
Log.e("Camera1", "PreviewSize is "+mPreviewSize.toString());
} }
private void adjustCameraParameters() { private void adjustCameraParameters() {
boolean invertPreviewSizes = shouldFlipSizes(); // mDisplayOffset % 180 != 0; boolean invertPreviewSizes = shouldFlipSizes(); // mDisplayOffset % 180 != 0;
mPreview.setDesiredSize( mPreview.setDesiredSize(

@ -1,54 +0,0 @@
package com.flurgle.camerakit;
import android.graphics.YuvImage;
import android.hardware.Camera;
class ProcessStillTask implements Runnable {
private byte[] data;
private Camera camera;
private int rotation;
private OnStillProcessedListener onStillProcessedListener;
public ProcessStillTask(byte[] data, Camera camera, int rotation, OnStillProcessedListener onStillProcessedListener) {
this.data = data;
this.camera = camera;
this.rotation = rotation;
this.onStillProcessedListener = onStillProcessedListener;
}
@Override
public void run() {
Camera.Parameters parameters = camera.getParameters();
int width = parameters.getPreviewSize().width;
int height = parameters.getPreviewSize().height;
byte[] rotatedData = new Rotation(data, width, height, rotation).getYuv();
int postWidth;
int postHeight;
switch (rotation) {
case 90:
case 270:
postWidth = height;
postHeight = width;
break;
case 0:
case 180:
default:
postWidth = width;
postHeight = height;
break;
}
YuvImage yuv = new YuvImage(rotatedData, parameters.getPreviewFormat(), postWidth, postHeight, null);
onStillProcessedListener.onStillProcessed(yuv);
}
interface OnStillProcessedListener {
void onStillProcessed(YuvImage yuv);
}
}

@ -4,10 +4,10 @@ import android.support.annotation.Nullable;
abstract class CameraImpl { abstract class CameraImpl {
protected final CameraListener mCameraListener; protected final CameraView.CameraListenerWrapper mCameraListener;
protected final PreviewImpl mPreview; protected final PreviewImpl mPreview;
CameraImpl(CameraListener callback, PreviewImpl preview) { CameraImpl(CameraView.CameraListenerWrapper callback, PreviewImpl preview) {
mCameraListener = callback; mCameraListener = callback;
mPreview = preview; mPreview = preview;
} }

@ -18,10 +18,6 @@ public abstract class CameraListener {
} }
public void onPictureTaken(YuvImage yuv) {
}
public void onVideoTaken(File video) { public void onVideoTaken(File video) {
} }

@ -129,7 +129,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
mCameraListener = new CameraListenerWrapper(); mCameraListener = new CameraListenerWrapper();
mPreviewImpl = new TextureViewPreview(context, this); mPreviewImpl = new TextureViewPreview(context, this);
mCameraImpl = new Camera1(mCameraListener, mPreviewImpl); mCameraImpl = new Camera1(mCameraListener, mPreviewImpl);
@ -138,8 +137,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
setFlash(mFlash); setFlash(mFlash);
setFocus(mFocus); setFocus(mFocus);
setSessionType(mSessionType); setSessionType(mSessionType);
// setCaptureMethod(mMethod);
// setPermissionPolicy(mPermissions);
setZoom(mZoom); setZoom(mZoom);
setVideoQuality(mVideoQuality); setVideoQuality(mVideoQuality);
setWhiteBalance(mWhiteBalance); setWhiteBalance(mWhiteBalance);
@ -182,8 +179,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
super.onAttachedToWindow(); super.onAttachedToWindow();
if (!isInEditMode()) { if (!isInEditMode()) {
WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay(); mOrientationHelper.enable(manager.getDefaultDisplay());
mOrientationHelper.enable(display);
} }
} }
@ -216,8 +212,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
Size previewSize = getPreviewSize(); Size previewSize = getPreviewSize();
if (previewSize == null) { if (previewSize == null) { // Early measure.
// Early measure.
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return; return;
} }
@ -236,8 +231,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// preview: 1600x1200 // preview: 1600x1200
// parent: 1080x1794 // parent: 1080x1794
float parentHeight = MeasureSpec.getSize(heightMeasureSpec); // 1794. float parentHeight = MeasureSpec.getSize(heightMeasureSpec);
float parentWidth = MeasureSpec.getSize(widthMeasureSpec); // 1080. mode = AT_MOST float parentWidth = MeasureSpec.getSize(widthMeasureSpec); // mode = AT_MOST
float targetRatio = previewHeight / previewWidth; float targetRatio = previewHeight / previewWidth;
float currentRatio = parentHeight / parentWidth; float currentRatio = parentHeight / parentWidth;
Log.e(TAG, "parentHeight="+parentHeight+", parentWidth="+parentWidth); Log.e(TAG, "parentHeight="+parentHeight+", parentWidth="+parentWidth);
@ -273,10 +268,16 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
} }
/**
* Returns whether the camera has started showing its preview.
* @return whether the camera has started
*/
public boolean isStarted() { public boolean isStarted() {
return mIsStarted; return mIsStarted;
} }
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
super.setEnabled(enabled); super.setEnabled(enabled);
@ -315,7 +316,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
return; return;
} }
if (checkPermissions()) { if (checkPermissions(mSessionType)) {
mIsStarted = true; mIsStarted = true;
run(new Runnable() { run(new Runnable() {
@Override @Override
@ -332,11 +333,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* Throws if session = audio and manifest did not add the microphone permissions. * Throws if session = audio and manifest did not add the microphone permissions.
* @return true if we can go on, false otherwise. * @return true if we can go on, false otherwise.
*/ */
private boolean checkPermissions() { private boolean checkPermissions(@SessionType int sessionType) {
checkPermissionsManifestOrThrow(); checkPermissionsManifestOrThrow(sessionType);
int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA); int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA);
int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO); int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO);
switch (mSessionType) { switch (sessionType) {
case SESSION_TYPE_VIDEO: case SESSION_TYPE_VIDEO:
if (cameraCheck != PackageManager.PERMISSION_GRANTED || audioCheck != PackageManager.PERMISSION_GRANTED) { if (cameraCheck != PackageManager.PERMISSION_GRANTED || audioCheck != PackageManager.PERMISSION_GRANTED) {
requestPermissions(true, true); requestPermissions(true, true);
@ -360,8 +361,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* If the developer did not add this to its manifest, throw and fire warnings. * If the developer did not add this to its manifest, throw and fire warnings.
* (Hoping this is not cought elsewhere... we should test). * (Hoping this is not cought elsewhere... we should test).
*/ */
private void checkPermissionsManifestOrThrow() { private void checkPermissionsManifestOrThrow(@SessionType int sessionType) {
if (mSessionType == SESSION_TYPE_VIDEO) { if (sessionType == SESSION_TYPE_VIDEO) {
try { try {
PackageManager manager = getContext().getPackageManager(); PackageManager manager = getContext().getPackageManager();
PackageInfo info = manager.getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS); PackageInfo info = manager.getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS);
@ -464,6 +465,28 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
/**
* Toggles the facing value between {@link CameraKit.Constants#FACING_BACK}
* and {@link CameraKit.Constants#FACING_FRONT}.
*
* @return the new facing value
*/
@Facing
public int toggleFacing() {
switch (mFacing) {
case FACING_BACK:
setFacing(FACING_FRONT);
break;
case FACING_FRONT:
setFacing(FACING_BACK);
break;
}
return mFacing;
}
/** /**
* Sets the flash mode. * Sets the flash mode.
* *
@ -490,6 +513,34 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
/**
* Toggles the flash mode between {@link CameraKit.Constants#FLASH_OFF},
* {@link CameraKit.Constants#FLASH_ON}, {@link CameraKit.Constants#FLASH_AUTO} and
* {@link CameraKit.Constants#FOCUS_OFF}, in this order.
*
* @return the new flash value
*/
@Flash
public int toggleFlash() {
switch (mFlash) {
case FLASH_OFF:
setFlash(FLASH_ON);
break;
case FLASH_ON:
setFlash(FLASH_AUTO);
break;
case FLASH_AUTO:
case FLASH_TORCH:
setFlash(FLASH_OFF);
break;
}
return mFlash;
}
/** /**
* Sets the current focus behavior. * Sets the current focus behavior.
* *
@ -511,24 +562,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
/**
* Gets the current focus behavior.
* @return a focus behavior
*/
@Focus
public int getFocus() {
return mFocus;
}
/** /**
* This does nothing. * This does nothing.
* @param method no-op
* @deprecated * @deprecated
*/ */
@Deprecated @Deprecated
public void setCaptureMethod(@Method int method) { public void setCaptureMethod(@Method int method) {}
}
/** /**
* This does nothing. * This does nothing.
* @param permissions no-op
* @deprecated * @deprecated
*/ */
@Deprecated @Deprecated
public void setPermissionPolicy(@Permissions int permissions) { public void setPermissionPolicy(@Permissions int permissions) {}
}
/** /**
@ -546,7 +603,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mSessionType = sessionType; mSessionType = sessionType;
mCameraImpl.setSessionType(sessionType); mCameraImpl.setSessionType(sessionType);
} else if (checkPermissions()) { } else if (checkPermissions(sessionType)) {
// Camera is running. CameraImpl setSessionType will do the trick. // Camera is running. CameraImpl setSessionType will do the trick.
mSessionType = sessionType; mSessionType = sessionType;
mCameraImpl.setSessionType(sessionType); mCameraImpl.setSessionType(sessionType);
@ -560,6 +617,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
} }
/**
* Gets the current session type.
* @return the current session type
*/
@SessionType
public int getSessionType() {
return mSessionType;
}
public void setVideoQuality(@VideoQuality int videoQuality) { public void setVideoQuality(@VideoQuality int videoQuality) {
this.mVideoQuality = videoQuality; this.mVideoQuality = videoQuality;
mCameraImpl.setVideoQuality(mVideoQuality); mCameraImpl.setVideoQuality(mVideoQuality);
@ -581,46 +650,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
/**
* Toggles the facing value between {@link CameraKit.Constants#FACING_BACK}
* and {@link CameraKit.Constants#FACING_FRONT}.
*
* @return the new facing value
*/
@Facing
public int toggleFacing() {
switch (mFacing) {
case FACING_BACK:
setFacing(FACING_FRONT);
break;
case FACING_FRONT:
setFacing(FACING_BACK);
break;
}
return mFacing;
}
@Flash
public int toggleFlash() {
switch (mFlash) {
case FLASH_OFF:
setFlash(FLASH_ON);
break;
case FLASH_ON:
setFlash(FLASH_AUTO);
break;
case FLASH_AUTO:
case FLASH_TORCH:
setFlash(FLASH_OFF);
break;
}
return mFlash;
}
/** /**
@ -685,7 +717,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
} }
private class CameraListenerWrapper extends CameraListener { class CameraListenerWrapper extends CameraListener {
private CameraListener mWrappedListener; private CameraListener mWrappedListener;
@ -715,9 +747,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
} }
@Override void processYuvImage(YuvImage yuv) {
public void onPictureTaken(YuvImage yuv) {
super.onPictureTaken(yuv);
if (mWrappedListener == null) return; if (mWrappedListener == null) return;
if (mCropOutput) { if (mCropOutput) {
AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight()); AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight());

@ -1,50 +0,0 @@
package com.flurgle.camerakit;
public class Rotation {
private byte[] rotatedYuv;
public Rotation(final byte[] yuv, final int width, final int height, final int rotation) {
if (rotation == 0) this.rotatedYuv = yuv;
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
}
final byte[] output = new byte[yuv.length];
final int frameSize = width * height;
final boolean swap = rotation % 180 != 0;
final boolean xflip = rotation % 270 != 0;
final boolean yflip = rotation >= 180;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
final int yIn = j * width + i;
final int uIn = frameSize + (j >> 1) * width + (i & ~1);
final int vIn = uIn + 1;
final int wOut = swap ? height : width;
final int hOut = swap ? width : height;
final int iSwapped = swap ? j : i;
final int jSwapped = swap ? i : j;
final int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
final int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;
final int yOut = jOut * wOut + iOut;
final int uOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
final int vOut = uOut + 1;
output[yOut] = (byte) (0xff & yuv[yIn]);
output[uOut] = (byte) (0xff & yuv[uIn]);
output[vOut] = (byte) (0xff & yuv[vIn]);
}
}
this.rotatedYuv = output;
}
public byte[] getYuv() {
return this.rotatedYuv;
}
}
Loading…
Cancel
Save