From aa74a0d2a16199fe312bb81f11a62df5a6208dc1 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Tue, 14 Nov 2017 13:33:03 +0100 Subject: [PATCH] Implement SizeSelectors in CameraController --- .../cameraview/CameraViewTest.java | 5 +- .../com/otaliastudios/cameraview/Camera1.java | 156 ++---------------- .../cameraview/CameraController.java | 98 ++++++++++- .../otaliastudios/cameraview/CameraView.java | 5 +- .../cameraview/SizeSelectors.java | 10 +- 5 files changed, 117 insertions(+), 157 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index ff06d65c..8c96a170 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -558,11 +558,8 @@ public class CameraViewTest extends BaseTest { SizeSelector source = SizeSelectors.minHeight(50); cameraView.setPictureSize(source); SizeSelector result = mockController.getPictureSizeSelector(); - - // Ensure we wrapped the selector in a OrSelector to have fallbacks. assertNotNull(result); - assertNotEquals(result, source); - assertTrue(result instanceof SizeSelectors.OrSelector); + assertEquals(result, source); } //endregion diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index ddb9500e..332cf336 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -28,7 +28,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera private static final String TAG = Camera1.class.getSimpleName(); private static final CameraLogger LOG = CameraLogger.create(TAG); - private int mCameraId; private Camera mCamera; private MediaRecorder mMediaRecorder; private File mVideoFile; @@ -104,7 +103,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera if (!mIsBound) return; // Compute a new camera preview size. - Size newSize = computePreviewSize(); + Size newSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes())); if (newSize.equals(mPreviewSize)) return; // Apply. @@ -136,8 +135,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera throw new CameraException(e); } - mCaptureSize = computeCaptureSize(); - mPreviewSize = computePreviewSize(); + mPictureSize = computePictureSize(sizesFromList(mCamera.getParameters().getSupportedPictureSizes())); + mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes())); applySizesAndStartPreview("bindToSurface:"); mIsBound = true; } @@ -156,7 +155,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera Camera.Parameters params = mCamera.getParameters(); mPreviewFormat = params.getPreviewFormat(); params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview - params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed + params.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight()); // <- allowed mCamera.setParameters(params); mCamera.setPreviewCallbackWithBuffer(null); // Release anything left @@ -234,7 +233,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera mOptions = null; mCamera = null; mPreviewSize = null; - mCaptureSize = null; + mPictureSize = null; mIsBound = false; if (error != null) throw new CameraException(error); @@ -456,17 +455,17 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera if (mSessionType == SessionType.VIDEO) { // Change capture size to a size that fits the video aspect ratio. - Size oldSize = mCaptureSize; - mCaptureSize = computeCaptureSize(); - if (!mCaptureSize.equals(oldSize)) { + Size oldSize = mPictureSize; + mPictureSize = computePictureSize(sizesFromList(mCamera.getParameters().getSupportedPictureSizes())); + if (!mPictureSize.equals(oldSize)) { // New video quality triggers a new aspect ratio. // Go on and see if preview size should change also. Camera.Parameters params = mCamera.getParameters(); - params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); + params.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight()); mCamera.setParameters(params); onSurfaceChanged(); } - LOG.i("setVideoQuality:", "captureSize:", mCaptureSize); + LOG.i("setVideoQuality:", "captureSize:", mPictureSize); LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); } } @@ -638,7 +637,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera } } - /** * Whether the exif tag should include a 'flip' operation. */ @@ -647,43 +645,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera } - /** - * 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 CameraPreview.SurfaceCallback} should be called, - * and this should be refreshed. - */ - private Size computeCaptureSize() { - Camera.Parameters params = mCamera.getParameters(); - if (mSessionType == SessionType.PICTURE) { - // Choose the max size. - List captureSizes = sizesFromList(params.getSupportedPictureSizes()); - Size maxSize = Collections.max(captureSizes); - LOG.i("size:", "computeCaptureSize:", "computed", maxSize); - return Collections.max(captureSizes); - } else { - // Choose according to developer choice in setVideoQuality. - // The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc. - // We want the picture size to be the max picture consistent with the video aspect ratio. - List captureSizes = sizesFromList(params.getSupportedPictureSizes()); - CamcorderProfile profile = getCamcorderProfile(mCameraId, mVideoQuality); - AspectRatio targetRatio = AspectRatio.of(profile.videoFrameWidth, profile.videoFrameHeight); - LOG.i("size:", "computeCaptureSize:", "videoQuality:", mVideoQuality, "targetRatio:", targetRatio); - return matchSize(captureSizes, targetRatio, new Size(0, 0), true); - } - } - - private Size computePreviewSize() { - Camera.Parameters params = mCamera.getParameters(); - List previewSizes = sizesFromList(params.getSupportedPreviewSizes()); - AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight()); - Size biggerThan = mPreview.getSurfaceSize(); - LOG.i("size:", "computePreviewSize:", "targetRatio:", targetRatio, "surface size:", biggerThan); - return matchSize(previewSizes, targetRatio, biggerThan, false); - } - - // ----------------- // Video recording stuff. @@ -755,7 +716,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera // Must be called before setOutputFormat. mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); } - CamcorderProfile profile = getCamcorderProfile(mCameraId, mVideoQuality); + CamcorderProfile profile = getCamcorderProfile(); mMediaRecorder.setOutputFormat(profile.fileFormat); mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); @@ -778,50 +739,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); } - @NonNull - private static CamcorderProfile getCamcorderProfile(int cameraId, VideoQuality videoQuality) { - switch (videoQuality) { - case HIGHEST: - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); - - case MAX_2160P: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && - CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P); - } - // Don't break. - - case MAX_1080P: - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); - } - // Don't break. - - case MAX_720P: - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); - } - // Don't break. - - case MAX_480P: - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); - } - // Don't break. - - case MAX_QVGA: - if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA); - } - // Don't break. - - case LOWEST: - default: - // Fallback to lowest. - return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); - } - } - // ----------------- // Zoom and simpler stuff. @@ -962,62 +879,17 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera // ----------------- - // Size static stuff. + // Size stuff. - /** - * Returns a list of {@link Size} out of Camera.Sizes. - */ @Nullable - private static List sizesFromList(List sizes) { + private List sizesFromList(List sizes) { if (sizes == null) return null; List result = new ArrayList<>(sizes.size()); for (Camera.Size size : sizes) { result.add(new Size(size.width, size.height)); } - LOG.i("size:", "sizesFromList:", result); - return result; - } - - - /** - * Policy here is to return a size that is big enough to fit the surface size, - * and possibly consistent with the target aspect ratio. - * @param sizes list of possible sizes - * @param targetRatio aspect ratio - * @param biggerThan size representing the current surface size - * @return chosen size - */ - 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 Size targetSize = biggerThan; - for (Size size : sizes) { - AspectRatio ratio = AspectRatio.of(size.getWidth(), size.getHeight()); - if (ratio.equals(targetRatio)) { - consistent.add(size); - if (size.getHeight() >= targetSize.getHeight() && size.getWidth() >= targetSize.getWidth()) { - bigEnoughAndConsistent.add(size); - } - } - } - - LOG.i("size:", "matchSize:", "found consistent:", consistent.size()); - LOG.i("size:", "matchSize:", "found big enough and consistent:", bigEnoughAndConsistent.size()); - Size result; - if (bigEnoughAndConsistent.size() > 0) { - result = biggestPossible ? - Collections.max(bigEnoughAndConsistent) : - Collections.min(bigEnoughAndConsistent); - } else if (consistent.size() > 0) { - result = Collections.max(consistent); - } else { - result = Collections.max(sizes); - } - LOG.i("size", "matchSize:", "returning result", result); + LOG.i("size:", "previewSizes:", result); return result; } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 31dce7a6..d9549086 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -5,6 +5,8 @@ import android.hardware.Camera; import android.location.Location; +import android.media.CamcorderProfile; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; @@ -12,6 +14,7 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -32,6 +35,7 @@ abstract class CameraController implements protected CameraPreview mPreview; protected WorkerHandler mHandler; /* for tests */ Handler mCrashHandler; + protected int mCameraId; protected Facing mFacing; protected Flash mFlash; @@ -45,7 +49,7 @@ abstract class CameraController implements protected float mZoomValue; protected float mExposureCorrectionValue; - protected Size mCaptureSize; + protected Size mPictureSize; protected Size mPreviewSize; protected int mPreviewFormat; @@ -357,7 +361,7 @@ abstract class CameraController implements } final Size getPictureSize() { - return mCaptureSize; + return mPictureSize; } final float getZoomValue() { @@ -376,6 +380,96 @@ abstract class CameraController implements //region Size utils + /** + * 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 CameraPreview.SurfaceCallback} should be called, + * and this should be refreshed. + */ + protected Size computePictureSize(List captureSizes) { + SizeSelector selector; + if (mSessionType == SessionType.PICTURE) { + selector = SizeSelectors.or( + mPictureSizeSelector, + SizeSelectors.max() // Fallback to max. + ); + } else { + // The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc. + // We want the picture size to be the max picture consistent with the video aspect ratio. + // -> Use the external picture selector, but enforce the ratio constraint. + CamcorderProfile profile = getCamcorderProfile(); + AspectRatio targetRatio = AspectRatio.of(profile.videoFrameWidth, profile.videoFrameHeight); + LOG.i("size:", "computeCaptureSize:", "videoQuality:", mVideoQuality, "targetRatio:", targetRatio); + SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); + selector = SizeSelectors.or( + SizeSelectors.and(matchRatio, mPictureSizeSelector), + SizeSelectors.and(matchRatio), + mPictureSizeSelector + ); + } + return selector.select(captureSizes).get(0); + } + + protected Size computePreviewSize(List previewSizes) { + AspectRatio targetRatio = AspectRatio.of(mPictureSize.getWidth(), mPictureSize.getHeight()); + Size targetMinSize = mPreview.getSurfaceSize(); + LOG.i("size:", "computePreviewSize:", "targetRatio:", targetRatio, "targetMinSize:", targetMinSize); + SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); + SizeSelector matchSize = SizeSelectors.and( + SizeSelectors.minHeight(targetMinSize.getHeight()), + SizeSelectors.minWidth(targetMinSize.getWidth())); + SizeSelector matchAll = SizeSelectors.or( + SizeSelectors.and(matchRatio, matchSize), + matchRatio, // If couldn't match both, match ratio. + SizeSelectors.max() // If couldn't match any, take the biggest. + ); + return matchAll.select(previewSizes).get(0); + } + + @NonNull + protected CamcorderProfile getCamcorderProfile() { + switch (mVideoQuality) { + case HIGHEST: + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH); + + case MAX_2160P: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_2160P); + } + // Don't break. + + case MAX_1080P: + if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P)) { + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_1080P); + } + // Don't break. + + case MAX_720P: + if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P)) { + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_720P); + } + // Don't break. + + case MAX_480P: + if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P)) { + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_480P); + } + // Don't break. + + case MAX_QVGA: + if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_QVGA)) { + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_QVGA); + } + // Don't break. + + case LOWEST: + default: + // Fallback to lowest. + return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_LOW); + } + } //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 669d42a2..d13c2355 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -975,10 +975,7 @@ public class CameraView extends FrameLayout { * @param selector a size selector */ public void setPictureSize(@NonNull SizeSelector selector) { - // Add a max fallback. - mCameraController.setPictureSizeSelector( - SizeSelectors.or(selector, SizeSelectors.max()) - ); + mCameraController.setPictureSizeSelector(selector); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/SizeSelectors.java b/cameraview/src/main/java/com/otaliastudios/cameraview/SizeSelectors.java index 2c183f89..2ef9c0f1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/SizeSelectors.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/SizeSelectors.java @@ -210,7 +210,7 @@ public class SizeSelectors { //region private utilities - static class FilterSelector implements SizeSelector { + private static class FilterSelector implements SizeSelector { private Filter constraint; @@ -231,9 +231,9 @@ public class SizeSelectors { } } - static class AndSelector implements SizeSelector { + private static class AndSelector implements SizeSelector { - SizeSelector[] values; + private SizeSelector[] values; private AndSelector(@NonNull SizeSelector... values) { this.values = values; @@ -250,9 +250,9 @@ public class SizeSelectors { } } - static class OrSelector implements SizeSelector { + private static class OrSelector implements SizeSelector { - SizeSelector[] values; + private SizeSelector[] values; private OrSelector(@NonNull SizeSelector... values) { this.values = values;