update camera preview strategy

// FREEBIE
This commit is contained in:
Jake McGinty 2015-11-19 16:00:07 -08:00 committed by Moxie Marlinspike
parent 7817c7697e
commit 4e8e8978f4
2 changed files with 91 additions and 63 deletions

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.camera;
import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -12,40 +13,46 @@ import android.view.Surface;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings("deprecation")
public class CameraUtils {
private static final String TAG = CameraUtils.class.getSimpleName();
/*
* modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
*/
public static @Nullable Size getPreferredPreviewSize(int displayOrientation,
int width,
int height,
@NonNull Camera camera) {
Log.w("CameraUtils", String.format("getPreferredPreviewSize(%d, %d, %d)", displayOrientation, width, height));
double targetRatio = (double)width / height;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
@NonNull Parameters parameters) {
final int targetWidth = displayOrientation % 180 == 90 ? height : width;
final int targetHeight = displayOrientation % 180 == 90 ? width : height;
final double targetRatio = (double) targetWidth / targetHeight;
if (displayOrientation == 90 || displayOrientation == 270) {
targetRatio = (double)height / width;
}
Log.w(TAG, String.format("getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f",
displayOrientation, width, height,
targetWidth, targetHeight, targetRatio));
List<Size> sizes = camera.getParameters().getSupportedPreviewSizes();
Collections.sort(sizes, Collections.reverseOrder(new SizeComparator()));
List<Size> sizes = parameters.getSupportedPreviewSizes();
List<Size> ideals = new LinkedList<>();
List<Size> bigEnough = new LinkedList<>();
for (Size size : sizes) {
double ratio = (double)size.width / size.height;
Log.w(TAG, String.format(" %dx%d (%.02f)", size.width, size.height, (float)size.width / size.height));
if (Math.abs(ratio - targetRatio) < minDiff) {
optimalSize = size;
minDiff = Math.abs(ratio - targetRatio);
if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) {
ideals.add(size);
Log.w(TAG, " (ideal ratio)");
} else if (size.width >= targetWidth && size.height >= targetHeight) {
bigEnough.add(size);
Log.w(TAG, " (good size, suboptimal ratio)");
}
}
return optimalSize;
if (!ideals.isEmpty()) return Collections.min(ideals, new AreaComparator());
else if (!bigEnough.isEmpty()) return Collections.min(bigEnough, new AspectRatioComparator(targetRatio));
else return Collections.max(sizes, new AreaComparator());
}
// based on
@ -74,15 +81,26 @@ public class CameraUtils {
}
}
private static class SizeComparator implements Comparator<Size> {
private static class AreaComparator implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
int left = lhs.width * lhs.height;
int right = rhs.width * rhs.height;
return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height);
}
}
if (left < right) return -1;
if (left > right) return 1;
else return 0;
private static class AspectRatioComparator extends AreaComparator {
private final double target;
public AspectRatioComparator(double target) {
this.target = target;
}
@Override
public int compare(Size lhs, Size rhs) {
final double lhsDiff = Math.abs(target - (double) lhs.width / lhs.height);
final double rhsDiff = Math.abs(target - (double) rhs.width / rhs.height);
if (lhsDiff < rhsDiff) return -1;
else if (lhsDiff > rhsDiff) return 1;
else return super.compare(lhs, rhs);
}
}
}

View File

@ -58,10 +58,11 @@ public class CameraView extends FrameLayout {
private @NonNull volatile Optional<Camera> camera = Optional.absent();
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
private boolean started;
private @NonNull State state = State.PAUSED;
private @Nullable Size previewSize;
private @Nullable CameraViewListener listener;
private int displayOrientation = -1;
private int outputOrientation = -1;
private int displayOrientation = -1;
private int outputOrientation = -1;
public CameraView(Context context) {
this(context, null);
@ -92,8 +93,8 @@ public class CameraView extends FrameLayout {
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onResume() {
if (started) return;
started = true;
if (state != State.PAUSED) return;
state = State.RESUMED;
Log.w(TAG, "onResume() queued");
enqueueTask(new SerialAsyncTask<Camera>() {
@Override
@ -125,8 +126,6 @@ public class CameraView extends FrameLayout {
synchronized (CameraView.this) {
CameraView.this.notifyAll();
}
requestLayout();
invalidate();
Log.w(TAG, "onResume() completed");
} catch (RuntimeException e) {
Log.w(TAG, "exception when starting camera preview", e);
@ -137,8 +136,8 @@ public class CameraView extends FrameLayout {
}
public void onPause() {
if (!started) return;
started = false;
if (state == State.PAUSED) return;
state = State.PAUSED;
Log.w(TAG, "onPause() queued");
enqueueTask(new SerialAsyncTask<Void>() {
@ -175,28 +174,7 @@ public class CameraView extends FrameLayout {
}
public boolean isStarted() {
return started;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera.isPresent()) {
final Size preferredPreviewSize = CameraUtils.getPreferredPreviewSize(displayOrientation,
getMeasuredWidth(),
getMeasuredHeight(),
camera.get());
final Parameters parameters = camera.get().getParameters();
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
Log.w(TAG, "setting preview size to " + preferredPreviewSize.width + "x" + preferredPreviewSize.height);
stopPreview();
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
camera.get().setParameters(parameters);
requestLayout();
startPreview();
}
}
return state != State.PAUSED;
}
@SuppressWarnings("SuspiciousNameCombination")
@ -207,8 +185,7 @@ public class CameraView extends FrameLayout {
final int previewWidth;
final int previewHeight;
if (camera.isPresent()) {
final Size previewSize = camera.get().getParameters().getPreviewSize();
if (camera.isPresent() && previewSize != null) {
if (displayOrientation == 90 || displayOrientation == 270) {
previewWidth = previewSize.height;
previewHeight = previewSize.width;
@ -225,7 +202,6 @@ public class CameraView extends FrameLayout {
Log.w(TAG, "skipping layout due to zero-width/height preview size");
return;
}
Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight);
if (width * previewHeight > height * previewWidth) {
final int scaledChildHeight = previewHeight * width / previewWidth;
@ -236,11 +212,18 @@ public class CameraView extends FrameLayout {
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.w(TAG, "onSizeChanged(" + oldw + "x" + oldh + " -> " + w + "x" + h + ")");
super.onSizeChanged(w, h, oldw, oldh);
if (camera.isPresent()) startPreview(camera.get().getParameters());
}
public void setListener(@Nullable CameraViewListener listener) {
this.listener = listener;
}
public void setPreviewCallback(final PreviewCallback previewCallback) {
public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) {
enqueueTask(new PostInitializationTask<Void>() {
@Override
protected void onPostMain(Void avoid) {
@ -252,8 +235,8 @@ public class CameraView extends FrameLayout {
return;
}
final int rotation = getCameraPictureOrientation();
final Size previewSize = camera.getParameters().getPreviewSize();
final int rotation = getCameraPictureOrientation();
final Size previewSize = camera.getParameters().getPreviewSize();
if (data != null) {
previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation));
}
@ -308,6 +291,7 @@ public class CameraView extends FrameLayout {
if (camera.isPresent()) {
try {
camera.get().setPreviewDisplay(surface.getHolder());
startPreview(parameters);
requestLayout();
} catch (Exception e) {
Log.w(TAG, e);
@ -317,10 +301,24 @@ public class CameraView extends FrameLayout {
});
}
private void startPreview() {
if (camera.isPresent()) {
private void startPreview(final @NonNull Parameters parameters) {
if (this.camera.isPresent()) {
try {
camera.get().startPreview();
final Camera camera = this.camera.get();
final Size preferredPreviewSize = getPreferredPreviewSize(parameters);
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
Log.w(TAG, "starting preview with size " + preferredPreviewSize.width + "x" + preferredPreviewSize.height);
if (state == State.ACTIVE) stopPreview();
previewSize = preferredPreviewSize;
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
camera.setParameters(parameters);
} else {
previewSize = parameters.getPreviewSize();
}
camera.startPreview();
requestLayout();
state = State.ACTIVE;
} catch (Exception e) {
Log.w(TAG, e);
}
@ -331,13 +329,21 @@ public class CameraView extends FrameLayout {
if (camera.isPresent()) {
try {
camera.get().stopPreview();
state = State.RESUMED;
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
public int getCameraPictureOrientation() {
private Size getPreferredPreviewSize(@NonNull Parameters parameters) {
return CameraUtils.getPreferredPreviewSize(displayOrientation,
getMeasuredWidth(),
getMeasuredHeight(),
parameters);
}
private int getCameraPictureOrientation() {
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
.getDefaultDisplay()
@ -589,4 +595,8 @@ public class CameraView extends FrameLayout {
return orientation;
}
}
private enum State {
PAUSED, RESUMED, ACTIVE
}
}