diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index bff379f7..3fe2a7a3 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -48,13 +48,7 @@ class Camera1 extends CameraImpl { private int mDeviceOrientation; private int mSensorOffset; - @Facing private int mFacing; - @Flash private int mFlash; - @Focus private int mFocus; @ZoomMode private int mZoom; - @VideoQuality private int mVideoQuality; - @WhiteBalance private int mWhiteBalance; - @SessionType private int mSessionType; private double mLatitude; private double mLongitude; private boolean mFocusOnTap; @@ -136,8 +130,8 @@ class Camera1 extends CameraImpl { ); synchronized (mLock) { Camera.Parameters params = mCamera.getParameters(); - params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); - params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- not allowed during preview + params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview + params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed mCamera.setParameters(params); } mCamera.startPreview(); @@ -358,8 +352,27 @@ class Camera1 extends CameraImpl { @Override void setVideoQuality(int videoQuality) { - this.mVideoQuality = videoQuality; - // TODO: restore preview size if needed. + if (mIsCapturingVideo) { + throw new IllegalStateException("Can't change video quality while recording a video."); + } + mVideoQuality = videoQuality; + if (isCameraOpened() && mSessionType == CameraKit.Constants.SESSION_TYPE_VIDEO) { + // Change capture size to a size that fits the video aspect ratio. + Size oldSize = mCaptureSize; + mCaptureSize = computeCaptureSize(); + if (!mCaptureSize.equals(oldSize)) { + // New video quality triggers a new aspect ratio. + // Go on and see if preview size should change also. + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); + mCamera.setParameters(params); + } + onSurfaceChanged(); + } + Log.e(TAG, "captureSize: "+mCaptureSize); + Log.e(TAG, "previewSize: "+mPreviewSize); + } } @Override @@ -520,16 +533,19 @@ class Camera1 extends CameraImpl { } 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); - return new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); + // We want the picture size to be the max picture consistent with the video aspect ratio. + List captureSizes = sizesFromList(params.getSupportedPictureSizes()); + CamcorderProfile profile = getCamcorderProfile(mVideoQuality); + AspectRatio targetRatio = AspectRatio.of(profile.videoFrameWidth, profile.videoFrameHeight); + return matchSize(captureSizes, targetRatio, new Size(0, 0), true); } } private Size computePreviewSize() { Camera.Parameters params = mCamera.getParameters(); List previewSizes = sizesFromList(params.getSupportedPreviewSizes()); - return computePreviewSize(previewSizes, mCaptureSize, mPreview.getSurfaceSize()); + AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight()); + return matchSize(previewSizes, targetRatio, mPreview.getSurfaceSize(), false); } @@ -761,20 +777,19 @@ class Camera1 extends CameraImpl { /** * Policy here is to return a size that is big enough to fit the surface size, - * and possibly consistent with the capture size. + * and possibly consistent with the target aspect ratio. * @param sizes list of possible sizes - * @param captureSize size representing desired aspect ratio - * @param surfaceSize size representing the current surface size. We'd like the returned value to be bigger. + * @param targetRatio aspect ratio + * @param biggerThan size representing the current surface size * @return chosen size */ - private static Size computePreviewSize(List sizes, Size captureSize, Size surfaceSize) { + private static Size matchSize(List sizes, AspectRatio targetRatio, Size biggerThan, boolean biggestPossible) { if (sizes == null) return null; List consistent = new ArrayList<>(5); List bigEnoughAndConsistent = new ArrayList<>(5); - final AspectRatio targetRatio = AspectRatio.of(captureSize.getWidth(), captureSize.getHeight()); - final Size targetSize = surfaceSize; + final Size targetSize = biggerThan; for (Size size : sizes) { AspectRatio ratio = AspectRatio.of(size.getWidth(), size.getHeight()); if (ratio.equals(targetRatio)) { @@ -785,9 +800,15 @@ class Camera1 extends CameraImpl { } } - if (bigEnoughAndConsistent.size() > 0) return Collections.min(bigEnoughAndConsistent); - if (consistent.size() > 0) return Collections.max(consistent); - return Collections.max(sizes); + if (biggestPossible) { + if (bigEnoughAndConsistent.size() > 0) return Collections.max(bigEnoughAndConsistent); + if (consistent.size() > 0) return Collections.max(consistent); + return Collections.max(sizes); + } else { + if (bigEnoughAndConsistent.size() > 0) return Collections.min(bigEnoughAndConsistent); + if (consistent.size() > 0) return Collections.max(consistent); + return Collections.max(sizes); + } } diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java index e3eb9611..b9b67023 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java @@ -11,6 +11,13 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback { protected final CameraView.CameraCallbacks mCameraListener; protected final PreviewImpl mPreview; + @Facing protected int mFacing; + @Flash protected int mFlash; + @Focus protected int mFocus; + @VideoQuality protected int mVideoQuality; + @WhiteBalance protected int mWhiteBalance; + @SessionType protected int mSessionType; + CameraImpl(CameraView.CameraCallbacks callback, PreviewImpl preview) { mCameraListener = callback; mPreview = preview; @@ -47,4 +54,33 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback { @Nullable abstract ExtraProperties getExtraProperties(); + @Facing + final int getFacing() { + return mFacing; + } + + @Flash + final int getFlash() { + return mFlash; + } + + @Focus + final int getFocus() { + return mFocus; + } + + @WhiteBalance + final int getWhiteBalance() { + return mWhiteBalance; + } + + @VideoQuality + final int getVideoQuality() { + return mVideoQuality; + } + + @SessionType + final int getSessionType() { + return mSessionType; + } } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index db9ce954..42fc8e79 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -83,15 +83,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { getWorkerHandler().post(runnable); } - @Facing private int mFacing; - @Flash private int mFlash; - @Focus private int mFocus; - // @Method private int mMethod; @ZoomMode private int mZoom; - // @Permissions private int mPermissions; - @SessionType private int mSessionType; - @VideoQuality private int mVideoQuality; - @WhiteBalance private int mWhiteBalance; private int mJpegQuality; private boolean mCropOutput; private int mDisplayOffset; @@ -118,13 +110,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver { @SuppressWarnings("WrongConstant") private void init(@NonNull Context context, @Nullable AttributeSet attrs) { TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0); - mFacing = a.getInteger(R.styleable.CameraView_cameraFacing, CameraKit.Defaults.DEFAULT_FACING); - mFlash = a.getInteger(R.styleable.CameraView_cameraFlash, CameraKit.Defaults.DEFAULT_FLASH); - mFocus = a.getInteger(R.styleable.CameraView_cameraFocus, CameraKit.Defaults.DEFAULT_FOCUS); - mSessionType = a.getInteger(R.styleable.CameraView_cameraSessionType, CameraKit.Defaults.DEFAULT_SESSION_TYPE); + int facing = a.getInteger(R.styleable.CameraView_cameraFacing, CameraKit.Defaults.DEFAULT_FACING); + int flash = a.getInteger(R.styleable.CameraView_cameraFlash, CameraKit.Defaults.DEFAULT_FLASH); + int focus = a.getInteger(R.styleable.CameraView_cameraFocus, CameraKit.Defaults.DEFAULT_FOCUS); + int sessionType = a.getInteger(R.styleable.CameraView_cameraSessionType, CameraKit.Defaults.DEFAULT_SESSION_TYPE); + int whiteBalance = a.getInteger(R.styleable.CameraView_cameraWhiteBalance, CameraKit.Defaults.DEFAULT_WHITE_BALANCE); + int videoQuality = a.getInteger(R.styleable.CameraView_cameraVideoQuality, CameraKit.Defaults.DEFAULT_VIDEO_QUALITY); mZoom = a.getInteger(R.styleable.CameraView_cameraZoomMode, CameraKit.Defaults.DEFAULT_ZOOM); - mWhiteBalance = a.getInteger(R.styleable.CameraView_cameraWhiteBalance, CameraKit.Defaults.DEFAULT_WHITE_BALANCE); - mVideoQuality = a.getInteger(R.styleable.CameraView_cameraVideoQuality, CameraKit.Defaults.DEFAULT_VIDEO_QUALITY); mJpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, CameraKit.Defaults.DEFAULT_JPEG_QUALITY); mCropOutput = a.getBoolean(R.styleable.CameraView_cameraCropOutput, CameraKit.Defaults.DEFAULT_CROP_OUTPUT); a.recycle(); @@ -136,13 +128,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver { addView(mFocusMarkerLayout); mIsStarted = false; - setFacing(mFacing); - setFlash(mFlash); - setFocus(mFocus); - setSessionType(mSessionType); + setFacing(facing); + setFlash(flash); + setFocus(focus); + setSessionType(sessionType); setZoomMode(mZoom); - setVideoQuality(mVideoQuality); - setWhiteBalance(mWhiteBalance); + setVideoQuality(videoQuality); + setWhiteBalance(whiteBalance); if (!isInEditMode()) { mOrientationHelper = new OrientationHelper(context) { @@ -159,19 +151,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mPreviewImpl.onDeviceOrientation(deviceOrientation); } }; - - /* focusMarkerLayout.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent motionEvent) { - int action = motionEvent.getAction(); - if (action == MotionEvent.ACTION_UP && mFocus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) { - focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY()); - } - - mPreviewImpl.getView().dispatchTouchEvent(motionEvent); - return true; - } - }); */ } mLifecycle = null; } @@ -384,13 +363,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver { destroy(); } + + /** + * Starts the camera preview, if not started already. + * This should be called onResume(), or when you are ready with permissions. + */ public void start() { if (mIsStarted || !isEnabled()) { // Already started, do nothing. return; } - if (checkPermissions(mSessionType)) { + if (checkPermissions(getSessionType())) { mIsStarted = true; run(new Runnable() { @Override @@ -455,6 +439,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } } + + /** + * Stops the current preview, if any was started. + * This should be called onPause(). + */ public void stop() { if (!mIsStarted) { // Already stopped, do nothing. @@ -484,6 +473,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { /** * Set location coordinates to be found later in the jpeg EXIF header + * * @param latitude current latitude * @param longitude current longitude */ @@ -491,22 +481,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraImpl.setLocation(latitude, longitude); } + /** * Sets desired white balance to current camera session. + * + * @see CameraKit.Constants#WHITE_BALANCE_AUTO + * @see CameraKit.Constants#WHITE_BALANCE_CLOUDY + * @see CameraKit.Constants#WHITE_BALANCE_DAYLIGHT + * @see CameraKit.Constants#WHITE_BALANCE_FLUORESCENT + * @see CameraKit.Constants#WHITE_BALANCE_INCANDESCENT + * * @param whiteBalance desired white balance behavior. */ public void setWhiteBalance(@WhiteBalance int whiteBalance) { - mWhiteBalance = whiteBalance; mCameraImpl.setWhiteBalance(whiteBalance); } + /** * Returns the current white balance behavior. * @return white balance value. */ @WhiteBalance public int getWhiteBalance() { - return mWhiteBalance; + return mCameraImpl.getWhiteBalance(); } @@ -519,7 +517,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @param facing a facing value. */ public void setFacing(@Facing final int facing) { - this.mFacing = facing; run(new Runnable() { @Override public void run() { @@ -535,7 +532,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @Facing public int getFacing() { - return mFacing; + return mCameraImpl.getFacing(); } @@ -547,7 +544,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @Facing public int toggleFacing() { - switch (mFacing) { + int facing = mCameraImpl.getFacing(); + switch (facing) { case FACING_BACK: setFacing(FACING_FRONT); break; @@ -557,7 +555,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { break; } - return mFacing; + return mCameraImpl.getFacing(); } @@ -572,7 +570,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @param flash desired flash mode. */ public void setFlash(@Flash int flash) { - this.mFlash = flash; mCameraImpl.setFlash(flash); } @@ -583,7 +580,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @Flash public int getFlash() { - return mFlash; + return mCameraImpl.getFlash(); } @@ -596,7 +593,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @Flash public int toggleFlash() { - switch (mFlash) { + int flash = mCameraImpl.getFlash(); + switch (flash) { case FLASH_OFF: setFlash(FLASH_ON); break; @@ -611,7 +609,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { break; } - return mFlash; + return mCameraImpl.getFlash(); } @@ -626,7 +624,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @param focus a Focus value. */ public void setFocus(@Focus int focus) { - mFocus = focus; mFocusMarkerLayout.setEnabled(focus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER); if (focus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) { focus = CameraKit.Constants.FOCUS_TAP; @@ -641,7 +638,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @Focus public int getFocus() { - return mFocus; + return mCameraImpl.getFocus(); } @@ -674,14 +671,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ public void setSessionType(@SessionType int sessionType) { - if (sessionType == mSessionType || !mIsStarted) { + if (sessionType == getSessionType() || !mIsStarted) { // Check did took place, or will happen on start(). - mSessionType = sessionType; mCameraImpl.setSessionType(sessionType); } else if (checkPermissions(sessionType)) { // Camera is running. CameraImpl setSessionType will do the trick. - mSessionType = sessionType; mCameraImpl.setSessionType(sessionType); } else { @@ -700,7 +695,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @SessionType public int getSessionType() { - return mSessionType; + return mCameraImpl.getSessionType(); } @@ -728,27 +723,62 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + /** + * Sets video recording quality. This is not guaranteed to be supported by current device. + * If it's not, a lower quality will be chosen, until a supported one is found. + * If sessionType is video, this might trigger a camera restart and a change in preview size. + * + * @see CameraKit.Constants#VIDEO_QUALITY_LOWEST + * @see CameraKit.Constants#VIDEO_QUALITY_QVGA + * @see CameraKit.Constants#VIDEO_QUALITY_480P + * @see CameraKit.Constants#VIDEO_QUALITY_720P + * @see CameraKit.Constants#VIDEO_QUALITY_1080P + * @see CameraKit.Constants#VIDEO_QUALITY_2160P + * @see CameraKit.Constants#VIDEO_QUALITY_HIGHEST + * + * @param videoQuality requested video quality + */ public void setVideoQuality(@VideoQuality int videoQuality) { - this.mVideoQuality = videoQuality; - mCameraImpl.setVideoQuality(mVideoQuality); + mCameraImpl.setVideoQuality(videoQuality); } + /** + * Gets the current video quality. + * @return the current video quality + */ + @VideoQuality + public int getVideoQuality() { + return mCameraImpl.getVideoQuality(); + } - + /** + * Sets the JPEG compression quality for image outputs. + * @param jpegQuality a 0-100 integer. + */ public void setJpegQuality(int jpegQuality) { - this.mJpegQuality = jpegQuality; + if (jpegQuality <= 0 || jpegQuality > 100) { + throw new IllegalArgumentException("JPEG quality should be > 0 and <= 0"); + } + mJpegQuality = jpegQuality; } + + /** + * Whether we should crop the picture output to match CameraView aspect ratio. + * This is only relevant if CameraView dimensions were somehow constrained + * (e.g. by fixed value or MATCH_PARENT) and do not match internal aspect ratio. + * + * Please note that this requires additional computations after the picture is taken. + * + * @param cropOutput whether to crop + */ public void setCropOutput(boolean cropOutput) { this.mCropOutput = cropOutput; } - - - /** * Sets a {@link CameraListener} instance to be notified of all * interesting events that will happen during the camera lifecycle. 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 c9ad736d..0a2ee82c 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java @@ -40,6 +40,10 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan @BindView(R.id.cropModeRadioGroup) RadioGroup cropModeRadioGroup; + // Video Quality: + @BindView(R.id.videoQualityRadioGroup) + RadioGroup videoQualityRadioGroup; + // Width: @BindView(R.id.screenWidth) TextView screenWidth; @@ -86,6 +90,7 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan cropModeRadioGroup.setOnCheckedChangeListener(cropModeChangedListener); widthModeRadioGroup.setOnCheckedChangeListener(widthModeChangedListener); heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener); + videoQualityRadioGroup.setOnCheckedChangeListener(videoQualityChangedListener); } private void message(String content, boolean important) { @@ -116,9 +121,8 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan if (mCapturingPicture) return; mCapturingPicture = true; final long startTime = System.currentTimeMillis(); - final boolean snapshot = camera.getSessionType() != CameraKit.Constants.SESSION_TYPE_PICTURE; - final Size nativeSize = snapshot ? camera.getSnapshotSize() : camera.getCaptureSize(); - message(snapshot ? "Capturing snapshot..." : "Capturing picture...", false); + final Size nativeSize = camera.getCaptureSize(); + message("Capturing picture...", false); camera.removeCameraListener(mPictureListener); mPictureListener = new CameraListener() { @Override @@ -226,6 +230,25 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan } }; + RadioGroup.OnCheckedChangeListener videoQualityChangedListener = new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (mCapturingVideo) return; + int videoQuality = CameraKit.Constants.VIDEO_QUALITY_HIGHEST; + switch (checkedId) { + case R.id.videoQualityLowest: videoQuality = CameraKit.Constants.VIDEO_QUALITY_LOWEST; break; + case R.id.videoQualityQvga: videoQuality = CameraKit.Constants.VIDEO_QUALITY_QVGA; break; + case R.id.videoQuality480p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_480P; break; + case R.id.videoQuality720p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_720P; break; + case R.id.videoQuality1080p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_1080P; break; + case R.id.videoQuality2160p: videoQuality = CameraKit.Constants.VIDEO_QUALITY_2160P; break; + case R.id.videoQualityHighest: videoQuality = CameraKit.Constants.VIDEO_QUALITY_HIGHEST; break; + } + camera.setVideoQuality(videoQuality); + message("Video quality changed!", false); + } + }; + @OnClick(R.id.widthUpdate) void widthUpdateClicked() { if (mCapturingPicture) return; diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java b/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java index 17228a3d..9841f933 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/VideoPreviewActivity.java @@ -51,9 +51,9 @@ public class VideoPreviewActivity extends Activity { float viewWidth = videoView.getWidth(); lp.height = (int) (viewWidth * (videoHeight / videoWidth)); videoView.setLayoutParams(lp); + playVideo(); } }); - playVideo(); } diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index f3bab5c1..7975b5d4 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -389,6 +389,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +