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.app.Activity;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.Camera.CameraInfo; import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size; import android.hardware.Camera.Size;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -12,40 +13,46 @@ import android.view.Surface;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class CameraUtils { 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 * 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, public static @Nullable Size getPreferredPreviewSize(int displayOrientation,
int width, int width,
int height, int height,
@NonNull Camera camera) { @NonNull Parameters parameters) {
Log.w("CameraUtils", String.format("getPreferredPreviewSize(%d, %d, %d)", displayOrientation, width, height)); final int targetWidth = displayOrientation % 180 == 90 ? height : width;
double targetRatio = (double)width / height; final int targetHeight = displayOrientation % 180 == 90 ? width : height;
Size optimalSize = null; final double targetRatio = (double) targetWidth / targetHeight;
double minDiff = Double.MAX_VALUE;
if (displayOrientation == 90 || displayOrientation == 270) { Log.w(TAG, String.format("getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f",
targetRatio = (double)height / width; displayOrientation, width, height,
} targetWidth, targetHeight, targetRatio));
List<Size> sizes = camera.getParameters().getSupportedPreviewSizes(); List<Size> sizes = parameters.getSupportedPreviewSizes();
List<Size> ideals = new LinkedList<>();
Collections.sort(sizes, Collections.reverseOrder(new SizeComparator())); List<Size> bigEnough = new LinkedList<>();
for (Size size : sizes) { 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) { if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) {
optimalSize = size; ideals.add(size);
minDiff = Math.abs(ratio - targetRatio); 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 // based on
@ -74,15 +81,26 @@ public class CameraUtils {
} }
} }
private static class SizeComparator implements Comparator<Size> { private static class AreaComparator implements Comparator<Size> {
@Override @Override
public int compare(Size lhs, Size rhs) { public int compare(Size lhs, Size rhs) {
int left = lhs.width * lhs.height; return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height);
int right = rhs.width * rhs.height; }
}
if (left < right) return -1; private static class AspectRatioComparator extends AreaComparator {
if (left > right) return 1; private final double target;
else return 0; 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,7 +58,8 @@ public class CameraView extends FrameLayout {
private @NonNull volatile Optional<Camera> camera = Optional.absent(); private @NonNull volatile Optional<Camera> camera = Optional.absent();
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK; 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 @Nullable CameraViewListener listener;
private int displayOrientation = -1; private int displayOrientation = -1;
private int outputOrientation = -1; private int outputOrientation = -1;
@ -92,8 +93,8 @@ public class CameraView extends FrameLayout {
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onResume() { public void onResume() {
if (started) return; if (state != State.PAUSED) return;
started = true; state = State.RESUMED;
Log.w(TAG, "onResume() queued"); Log.w(TAG, "onResume() queued");
enqueueTask(new SerialAsyncTask<Camera>() { enqueueTask(new SerialAsyncTask<Camera>() {
@Override @Override
@ -125,8 +126,6 @@ public class CameraView extends FrameLayout {
synchronized (CameraView.this) { synchronized (CameraView.this) {
CameraView.this.notifyAll(); CameraView.this.notifyAll();
} }
requestLayout();
invalidate();
Log.w(TAG, "onResume() completed"); Log.w(TAG, "onResume() completed");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Log.w(TAG, "exception when starting camera preview", e); Log.w(TAG, "exception when starting camera preview", e);
@ -137,8 +136,8 @@ public class CameraView extends FrameLayout {
} }
public void onPause() { public void onPause() {
if (!started) return; if (state == State.PAUSED) return;
started = false; state = State.PAUSED;
Log.w(TAG, "onPause() queued"); Log.w(TAG, "onPause() queued");
enqueueTask(new SerialAsyncTask<Void>() { enqueueTask(new SerialAsyncTask<Void>() {
@ -175,28 +174,7 @@ public class CameraView extends FrameLayout {
} }
public boolean isStarted() { public boolean isStarted() {
return started; return state != State.PAUSED;
}
@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();
}
}
} }
@SuppressWarnings("SuspiciousNameCombination") @SuppressWarnings("SuspiciousNameCombination")
@ -207,8 +185,7 @@ public class CameraView extends FrameLayout {
final int previewWidth; final int previewWidth;
final int previewHeight; final int previewHeight;
if (camera.isPresent()) { if (camera.isPresent() && previewSize != null) {
final Size previewSize = camera.get().getParameters().getPreviewSize();
if (displayOrientation == 90 || displayOrientation == 270) { if (displayOrientation == 90 || displayOrientation == 270) {
previewWidth = previewSize.height; previewWidth = previewSize.height;
previewHeight = previewSize.width; previewHeight = previewSize.width;
@ -225,7 +202,6 @@ public class CameraView extends FrameLayout {
Log.w(TAG, "skipping layout due to zero-width/height preview size"); Log.w(TAG, "skipping layout due to zero-width/height preview size");
return; return;
} }
Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight);
if (width * previewHeight > height * previewWidth) { if (width * previewHeight > height * previewWidth) {
final int scaledChildHeight = previewHeight * width / 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) { public void setListener(@Nullable CameraViewListener listener) {
this.listener = listener; this.listener = listener;
} }
public void setPreviewCallback(final PreviewCallback previewCallback) { public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) {
enqueueTask(new PostInitializationTask<Void>() { enqueueTask(new PostInitializationTask<Void>() {
@Override @Override
protected void onPostMain(Void avoid) { protected void onPostMain(Void avoid) {
@ -308,6 +291,7 @@ public class CameraView extends FrameLayout {
if (camera.isPresent()) { if (camera.isPresent()) {
try { try {
camera.get().setPreviewDisplay(surface.getHolder()); camera.get().setPreviewDisplay(surface.getHolder());
startPreview(parameters);
requestLayout(); requestLayout();
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, e); Log.w(TAG, e);
@ -317,10 +301,24 @@ public class CameraView extends FrameLayout {
}); });
} }
private void startPreview() { private void startPreview(final @NonNull Parameters parameters) {
if (camera.isPresent()) { if (this.camera.isPresent()) {
try { 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) { } catch (Exception e) {
Log.w(TAG, e); Log.w(TAG, e);
} }
@ -331,13 +329,21 @@ public class CameraView extends FrameLayout {
if (camera.isPresent()) { if (camera.isPresent()) {
try { try {
camera.get().stopPreview(); camera.get().stopPreview();
state = State.RESUMED;
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, 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) { if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager() outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
.getDefaultDisplay() .getDefaultDisplay()
@ -589,4 +595,8 @@ public class CameraView extends FrameLayout {
return orientation; return orientation;
} }
} }
private enum State {
PAUSED, RESUMED, ACTIVE
}
} }