323 lines
12 KiB
Java
323 lines
12 KiB
Java
/*
|
|
* Copyright 2012-2015 the original author or authors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package eu.siacs.conversations.ui.service;
|
|
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
|
|
import com.google.zxing.PlanarYUVLuminanceSource;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.hardware.Camera;
|
|
import android.hardware.Camera.CameraInfo;
|
|
import android.hardware.Camera.PreviewCallback;
|
|
import android.util.Log;
|
|
import android.view.TextureView;
|
|
|
|
import eu.siacs.conversations.Config;
|
|
|
|
/**
|
|
* @author Andreas Schildbach
|
|
*/
|
|
@SuppressWarnings("deprecation")
|
|
public final class CameraManager {
|
|
private static final int MIN_FRAME_SIZE = 240;
|
|
private static final int MAX_FRAME_SIZE = 600;
|
|
private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
|
|
private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
|
|
|
|
private Camera camera;
|
|
private CameraInfo cameraInfo = new CameraInfo();
|
|
private Camera.Size cameraResolution;
|
|
private Rect frame;
|
|
private RectF framePreview;
|
|
|
|
public Rect getFrame() {
|
|
return frame;
|
|
}
|
|
|
|
public RectF getFramePreview() {
|
|
return framePreview;
|
|
}
|
|
|
|
public int getFacing() {
|
|
return cameraInfo.facing;
|
|
}
|
|
|
|
public int getOrientation() {
|
|
return cameraInfo.orientation;
|
|
}
|
|
|
|
public Camera open(final TextureView textureView, final int displayOrientation, final boolean continuousAutoFocus)
|
|
throws IOException {
|
|
final int cameraId = determineCameraId();
|
|
Camera.getCameraInfo(cameraId, cameraInfo);
|
|
|
|
camera = Camera.open(cameraId);
|
|
|
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
|
|
camera.setDisplayOrientation((720 - displayOrientation - cameraInfo.orientation) % 360);
|
|
else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
|
|
camera.setDisplayOrientation((720 - displayOrientation + cameraInfo.orientation) % 360);
|
|
else
|
|
throw new IllegalStateException("facing: " + cameraInfo.facing);
|
|
|
|
camera.setPreviewTexture(textureView.getSurfaceTexture());
|
|
|
|
final int width = textureView.getWidth();
|
|
final int height = textureView.getHeight();
|
|
|
|
final Camera.Parameters parameters = camera.getParameters();
|
|
|
|
cameraResolution = findBestPreviewSizeValue(parameters, width, height);
|
|
|
|
final int rawSize = Math.min(width * 2 / 3, height * 2 / 3);
|
|
final int frameSize = Math.max(MIN_FRAME_SIZE, Math.min(MAX_FRAME_SIZE, rawSize));
|
|
|
|
final int leftOffset = (width - frameSize) / 2;
|
|
final int topOffset = (height - frameSize) / 2;
|
|
frame = new Rect(leftOffset, topOffset, leftOffset + frameSize, topOffset + frameSize);
|
|
|
|
float widthFactor;
|
|
float heightFactor;
|
|
Rect orientedFrame;
|
|
boolean isTexturePortrait = width < height;
|
|
boolean isCameraPortrait = cameraResolution.width < cameraResolution.height;
|
|
if (isTexturePortrait == isCameraPortrait) {
|
|
widthFactor = (float)cameraResolution.width / width;
|
|
heightFactor = (float)cameraResolution.height / height;
|
|
orientedFrame = new Rect(frame);
|
|
} else {
|
|
widthFactor = (float)cameraResolution.width / height;
|
|
heightFactor = (float)cameraResolution.height / width;
|
|
// Swap X and Y coordinates to flip frame to the same orientation as cameraResolution
|
|
orientedFrame = new Rect(frame.top, frame.left, frame.bottom, frame.right);
|
|
}
|
|
|
|
framePreview = new RectF(orientedFrame.left * widthFactor, orientedFrame.top * heightFactor,
|
|
orientedFrame.right * widthFactor, orientedFrame.bottom * heightFactor);
|
|
|
|
final String savedParameters = parameters == null ? null : parameters.flatten();
|
|
|
|
try {
|
|
setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
|
|
} catch (final RuntimeException x) {
|
|
if (savedParameters != null) {
|
|
final Camera.Parameters parameters2 = camera.getParameters();
|
|
parameters2.unflatten(savedParameters);
|
|
try {
|
|
camera.setParameters(parameters2);
|
|
setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
|
|
} catch (final RuntimeException x2) {
|
|
Log.d(Config.LOGTAG,"problem setting camera parameters", x2);
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
camera.startPreview();
|
|
return camera;
|
|
} catch (final RuntimeException x) {
|
|
Log.w(Config.LOGTAG,"something went wrong while starting camera preview", x);
|
|
camera.release();
|
|
throw x;
|
|
}
|
|
}
|
|
|
|
private int determineCameraId() {
|
|
final int cameraCount = Camera.getNumberOfCameras();
|
|
final CameraInfo cameraInfo = new CameraInfo();
|
|
|
|
// prefer back-facing camera
|
|
for (int i = 0; i < cameraCount; i++) {
|
|
Camera.getCameraInfo(i, cameraInfo);
|
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
|
|
return i;
|
|
}
|
|
|
|
// fall back to front-facing camera
|
|
for (int i = 0; i < cameraCount; i++) {
|
|
Camera.getCameraInfo(i, cameraInfo);
|
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public void close() {
|
|
if (camera != null) {
|
|
try {
|
|
camera.stopPreview();
|
|
} catch (final RuntimeException x) {
|
|
Log.w(Config.LOGTAG,"something went wrong while stopping camera preview", x);
|
|
}
|
|
|
|
camera.release();
|
|
}
|
|
}
|
|
|
|
private static final Comparator<Camera.Size> numPixelComparator = new Comparator<Camera.Size>() {
|
|
@Override
|
|
public int compare(final Camera.Size size1, final Camera.Size size2) {
|
|
final int pixels1 = size1.height * size1.width;
|
|
final int pixels2 = size2.height * size2.width;
|
|
|
|
if (pixels1 < pixels2)
|
|
return 1;
|
|
else if (pixels1 > pixels2)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
private static Camera.Size findBestPreviewSizeValue(final Camera.Parameters parameters, int width, int height) {
|
|
if (height > width) {
|
|
final int temp = width;
|
|
width = height;
|
|
height = temp;
|
|
}
|
|
|
|
final float screenAspectRatio = (float) width / (float) height;
|
|
|
|
final List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
|
|
if (rawSupportedSizes == null)
|
|
return parameters.getPreviewSize();
|
|
|
|
// sort by size, descending
|
|
final List<Camera.Size> supportedPreviewSizes = new ArrayList<Camera.Size>(rawSupportedSizes);
|
|
Collections.sort(supportedPreviewSizes, numPixelComparator);
|
|
|
|
Camera.Size bestSize = null;
|
|
float diff = Float.POSITIVE_INFINITY;
|
|
|
|
for (final Camera.Size supportedPreviewSize : supportedPreviewSizes) {
|
|
final int realWidth = supportedPreviewSize.width;
|
|
final int realHeight = supportedPreviewSize.height;
|
|
final int realPixels = realWidth * realHeight;
|
|
if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS)
|
|
continue;
|
|
|
|
final boolean isCandidatePortrait = realWidth < realHeight;
|
|
final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
|
|
final int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
|
|
if (maybeFlippedWidth == width && maybeFlippedHeight == height)
|
|
return supportedPreviewSize;
|
|
|
|
final float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
|
|
final float newDiff = Math.abs(aspectRatio - screenAspectRatio);
|
|
if (newDiff < diff) {
|
|
bestSize = supportedPreviewSize;
|
|
diff = newDiff;
|
|
}
|
|
}
|
|
|
|
if (bestSize != null)
|
|
return bestSize;
|
|
else
|
|
return parameters.getPreviewSize();
|
|
}
|
|
|
|
@SuppressLint("InlinedApi")
|
|
private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution,
|
|
final boolean continuousAutoFocus) {
|
|
final Camera.Parameters parameters = camera.getParameters();
|
|
if (parameters == null)
|
|
return;
|
|
|
|
final List<String> supportedFocusModes = parameters.getSupportedFocusModes();
|
|
final String focusMode = continuousAutoFocus
|
|
? findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
|
|
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO,
|
|
Camera.Parameters.FOCUS_MODE_MACRO)
|
|
: findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_MACRO);
|
|
if (focusMode != null)
|
|
parameters.setFocusMode(focusMode);
|
|
|
|
parameters.setPreviewSize(cameraResolution.width, cameraResolution.height);
|
|
|
|
camera.setParameters(parameters);
|
|
}
|
|
|
|
public void requestPreviewFrame(final PreviewCallback callback) {
|
|
try {
|
|
camera.setOneShotPreviewCallback(callback);
|
|
} catch (final RuntimeException x) {
|
|
Log.d(Config.LOGTAG,"problem requesting preview frame, callback won't be called", x);
|
|
}
|
|
}
|
|
|
|
public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) {
|
|
return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height,
|
|
(int) framePreview.left, (int) framePreview.top, (int) framePreview.width(),
|
|
(int) framePreview.height(), false);
|
|
}
|
|
|
|
public void setTorch(final boolean enabled) {
|
|
if (enabled != getTorchEnabled(camera))
|
|
setTorchEnabled(camera, enabled);
|
|
}
|
|
|
|
private static boolean getTorchEnabled(final Camera camera) {
|
|
final Camera.Parameters parameters = camera.getParameters();
|
|
if (parameters != null) {
|
|
final String flashMode = camera.getParameters().getFlashMode();
|
|
return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)
|
|
|| Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void setTorchEnabled(final Camera camera, final boolean enabled) {
|
|
final Camera.Parameters parameters = camera.getParameters();
|
|
|
|
final List<String> supportedFlashModes = parameters.getSupportedFlashModes();
|
|
if (supportedFlashModes != null) {
|
|
final String flashMode;
|
|
if (enabled)
|
|
flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH,
|
|
Camera.Parameters.FLASH_MODE_ON);
|
|
else
|
|
flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
|
|
|
|
if (flashMode != null) {
|
|
camera.cancelAutoFocus(); // autofocus can cause conflict
|
|
|
|
parameters.setFlashMode(flashMode);
|
|
camera.setParameters(parameters);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static String findValue(final Collection<String> values, final String... valuesToFind) {
|
|
for (final String valueToFind : valuesToFind)
|
|
if (values.contains(valueToFind))
|
|
return valueToFind;
|
|
|
|
return null;
|
|
}
|
|
}
|