Implement SizeSelectors in CameraController

pull/99/head
Mattia Iavarone 8 years ago
parent 074466aed9
commit aa74a0d2a1
  1. 5
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  2. 156
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  3. 98
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  4. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  5. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/SizeSelectors.java

@ -558,11 +558,8 @@ public class CameraViewTest extends BaseTest {
SizeSelector source = SizeSelectors.minHeight(50); SizeSelector source = SizeSelectors.minHeight(50);
cameraView.setPictureSize(source); cameraView.setPictureSize(source);
SizeSelector result = mockController.getPictureSizeSelector(); SizeSelector result = mockController.getPictureSizeSelector();
// Ensure we wrapped the selector in a OrSelector to have fallbacks.
assertNotNull(result); assertNotNull(result);
assertNotEquals(result, source); assertEquals(result, source);
assertTrue(result instanceof SizeSelectors.OrSelector);
} }
//endregion //endregion

@ -28,7 +28,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
private static final String TAG = Camera1.class.getSimpleName(); private static final String TAG = Camera1.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private int mCameraId;
private Camera mCamera; private Camera mCamera;
private MediaRecorder mMediaRecorder; private MediaRecorder mMediaRecorder;
private File mVideoFile; private File mVideoFile;
@ -104,7 +103,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
if (!mIsBound) return; if (!mIsBound) return;
// Compute a new camera preview size. // Compute a new camera preview size.
Size newSize = computePreviewSize(); Size newSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes()));
if (newSize.equals(mPreviewSize)) return; if (newSize.equals(mPreviewSize)) return;
// Apply. // Apply.
@ -136,8 +135,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
throw new CameraException(e); throw new CameraException(e);
} }
mCaptureSize = computeCaptureSize(); mPictureSize = computePictureSize(sizesFromList(mCamera.getParameters().getSupportedPictureSizes()));
mPreviewSize = computePreviewSize(); mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes()));
applySizesAndStartPreview("bindToSurface:"); applySizesAndStartPreview("bindToSurface:");
mIsBound = true; mIsBound = true;
} }
@ -156,7 +155,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
mPreviewFormat = params.getPreviewFormat(); mPreviewFormat = params.getPreviewFormat();
params.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // <- not allowed during preview 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.setParameters(params);
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
@ -234,7 +233,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mOptions = null; mOptions = null;
mCamera = null; mCamera = null;
mPreviewSize = null; mPreviewSize = null;
mCaptureSize = null; mPictureSize = null;
mIsBound = false; mIsBound = false;
if (error != null) throw new CameraException(error); if (error != null) throw new CameraException(error);
@ -456,17 +455,17 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
if (mSessionType == SessionType.VIDEO) { if (mSessionType == SessionType.VIDEO) {
// Change capture size to a size that fits the video aspect ratio. // Change capture size to a size that fits the video aspect ratio.
Size oldSize = mCaptureSize; Size oldSize = mPictureSize;
mCaptureSize = computeCaptureSize(); mPictureSize = computePictureSize(sizesFromList(mCamera.getParameters().getSupportedPictureSizes()));
if (!mCaptureSize.equals(oldSize)) { if (!mPictureSize.equals(oldSize)) {
// New video quality triggers a new aspect ratio. // New video quality triggers a new aspect ratio.
// Go on and see if preview size should change also. // Go on and see if preview size should change also.
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); params.setPictureSize(mPictureSize.getWidth(), mPictureSize.getHeight());
mCamera.setParameters(params); mCamera.setParameters(params);
onSurfaceChanged(); onSurfaceChanged();
} }
LOG.i("setVideoQuality:", "captureSize:", mCaptureSize); LOG.i("setVideoQuality:", "captureSize:", mPictureSize);
LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); 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. * 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<Size> 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<Size> 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<Size> 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. // Video recording stuff.
@ -755,7 +716,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// Must be called before setOutputFormat. // Must be called before setOutputFormat.
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
} }
CamcorderProfile profile = getCamcorderProfile(mCameraId, mVideoQuality); CamcorderProfile profile = getCamcorderProfile();
mMediaRecorder.setOutputFormat(profile.fileFormat); mMediaRecorder.setOutputFormat(profile.fileFormat);
mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
@ -778,50 +739,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); // 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. // 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 @Nullable
private static List<Size> sizesFromList(List<Camera.Size> sizes) { private List<Size> sizesFromList(List<Camera.Size> sizes) {
if (sizes == null) return null; if (sizes == null) return null;
List<Size> result = new ArrayList<>(sizes.size()); List<Size> result = new ArrayList<>(sizes.size());
for (Camera.Size size : sizes) { for (Camera.Size size : sizes) {
result.add(new Size(size.width, size.height)); result.add(new Size(size.width, size.height));
} }
LOG.i("size:", "sizesFromList:", result); LOG.i("size:", "previewSizes:", 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<Size> sizes, AspectRatio targetRatio, Size biggerThan, boolean biggestPossible) {
if (sizes == null) return null;
List<Size> consistent = new ArrayList<>(5);
List<Size> 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);
return result; return result;
} }
} }

@ -5,6 +5,8 @@ import android.hardware.Camera;
import android.location.Location; import android.location.Location;
import android.media.CamcorderProfile;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -12,6 +14,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -32,6 +35,7 @@ abstract class CameraController implements
protected CameraPreview mPreview; protected CameraPreview mPreview;
protected WorkerHandler mHandler; protected WorkerHandler mHandler;
/* for tests */ Handler mCrashHandler; /* for tests */ Handler mCrashHandler;
protected int mCameraId;
protected Facing mFacing; protected Facing mFacing;
protected Flash mFlash; protected Flash mFlash;
@ -45,7 +49,7 @@ abstract class CameraController implements
protected float mZoomValue; protected float mZoomValue;
protected float mExposureCorrectionValue; protected float mExposureCorrectionValue;
protected Size mCaptureSize; protected Size mPictureSize;
protected Size mPreviewSize; protected Size mPreviewSize;
protected int mPreviewFormat; protected int mPreviewFormat;
@ -357,7 +361,7 @@ abstract class CameraController implements
} }
final Size getPictureSize() { final Size getPictureSize() {
return mCaptureSize; return mPictureSize;
} }
final float getZoomValue() { final float getZoomValue() {
@ -376,6 +380,96 @@ abstract class CameraController implements
//region Size utils //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<Size> 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<Size> 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 //endregion
} }

@ -975,10 +975,7 @@ public class CameraView extends FrameLayout {
* @param selector a size selector * @param selector a size selector
*/ */
public void setPictureSize(@NonNull SizeSelector selector) { public void setPictureSize(@NonNull SizeSelector selector) {
// Add a max fallback. mCameraController.setPictureSizeSelector(selector);
mCameraController.setPictureSizeSelector(
SizeSelectors.or(selector, SizeSelectors.max())
);
} }

@ -210,7 +210,7 @@ public class SizeSelectors {
//region private utilities //region private utilities
static class FilterSelector implements SizeSelector { private static class FilterSelector implements SizeSelector {
private Filter constraint; 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) { private AndSelector(@NonNull SizeSelector... values) {
this.values = 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) { private OrSelector(@NonNull SizeSelector... values) {
this.values = values; this.values = values;

Loading…
Cancel
Save