diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index 8526ea94..f6cd7025 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -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 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 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 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 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 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 modes = params.getSupportedFocusModes(); + switch (mFocus) { + case FOCUS_CONTINUOUS: case FOCUS_TAP: case FOCUS_TAP_WITH_MARKER: - mFocusOnTap = true; - final List 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 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 previewSizes = sizesFromList(mCameraParameters.getSupportedPreviewSizes()); + private Size computeCaptureSize() { + Camera.Parameters params = mCamera.getParameters(); if (mSessionType == SESSION_TYPE_PICTURE) { // Choose the max size. - List captureSizes = sizesFromList(mCameraParameters.getSupportedPictureSizes()); - mCaptureSize = Collections.max(captureSizes); + List 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 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 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); diff --git a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java index 40a218cc..0085db34 100644 --- a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java +++ b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java @@ -37,15 +37,18 @@ class Camera2 extends CameraImpl { private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper2(); private final HashMap 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 diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java index d3c18bdb..da19b35f 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java @@ -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(); diff --git a/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java index e5f2320f..10e625d3 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java @@ -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. diff --git a/camerakit/src/main/base/com/flurgle/camerakit/SurfaceViewPreview.java b/camerakit/src/main/base/com/flurgle/camerakit/SurfaceViewPreview.java index 1060e83a..e2fc77d7 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/SurfaceViewPreview.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/SurfaceViewPreview.java @@ -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(); } }); } diff --git a/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java b/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java index f8ace3a4..44633593 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java @@ -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; } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index 176dec7a..c7a05c4e 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -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); diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index b61d7d6b..9791980c 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -22,8 +22,9 @@ + android:name=".PicturePreviewActivity" /> + + diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java index 1527fb1f..6844d91c 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java @@ -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(); } }; diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/PreviewActivity.java b/demo/src/main/java/com/flurgle/camerakit/demo/PicturePreviewActivity.java similarity index 72% rename from demo/src/main/java/com/flurgle/camerakit/demo/PreviewActivity.java rename to demo/src/main/java/com/flurgle/camerakit/demo/PicturePreviewActivity.java index 996d3741..3d6adcfd 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/PreviewActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/PicturePreviewActivity.java @@ -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"); diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/ResultHolder.java b/demo/src/main/java/com/flurgle/camerakit/demo/ResultHolder.java index 2679785e..8c439558 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/ResultHolder.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/ResultHolder.java @@ -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 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); } diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java b/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java new file mode 100644 index 00000000..7c6e8c3c --- /dev/null +++ b/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java @@ -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(); + } +} diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index c1964a4b..25f6fe9b 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -43,7 +43,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:weightSum="3"> + android:weightSum="4"> + android:text="Picture" /> + android:text="Video" /> diff --git a/demo/src/main/res/layout/activity_preview.xml b/demo/src/main/res/layout/activity_picture_preview.xml similarity index 100% rename from demo/src/main/res/layout/activity_preview.xml rename to demo/src/main/res/layout/activity_picture_preview.xml diff --git a/demo/src/main/res/layout/activity_video_preview.xml b/demo/src/main/res/layout/activity_video_preview.xml new file mode 100644 index 00000000..d3ad5a7a --- /dev/null +++ b/demo/src/main/res/layout/activity_video_preview.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + +