CameraManager.java

  1/*
  2 * Copyright 2012-2015 the original author or authors.
  3 *
  4 * This program is free software: you can redistribute it and/or modify
  5 * it under the terms of the GNU General Public License as published by
  6 * the Free Software Foundation, either version 3 of the License, or
  7 * (at your option) any later version.
  8 *
  9 * This program is distributed in the hope that it will be useful,
 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12 * GNU General Public License for more details.
 13 *
 14 * You should have received a copy of the GNU General Public License
 15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 16 */
 17
 18package eu.siacs.conversations.ui.service;
 19
 20import android.annotation.SuppressLint;
 21import android.graphics.Rect;
 22import android.graphics.RectF;
 23import android.hardware.Camera;
 24import android.hardware.Camera.CameraInfo;
 25import android.hardware.Camera.PreviewCallback;
 26import android.util.Log;
 27import android.view.TextureView;
 28import com.google.zxing.PlanarYUVLuminanceSource;
 29import eu.siacs.conversations.Config;
 30import java.io.IOException;
 31import java.util.ArrayList;
 32import java.util.Collection;
 33import java.util.Collections;
 34import java.util.Comparator;
 35import java.util.List;
 36
 37/**
 38 * @author Andreas Schildbach
 39 */
 40@SuppressWarnings("deprecation")
 41public final class CameraManager {
 42    private static final int MIN_FRAME_SIZE = 240;
 43    private static final int MAX_FRAME_SIZE = 600;
 44    private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
 45    private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
 46
 47    private Camera camera;
 48    private final CameraInfo cameraInfo = new CameraInfo();
 49    private Camera.Size cameraResolution;
 50    private Rect frame;
 51    private RectF framePreview;
 52
 53    public Rect getFrame() {
 54        return frame;
 55    }
 56
 57    public RectF getFramePreview() {
 58        return framePreview;
 59    }
 60
 61    public int getFacing() {
 62        return cameraInfo.facing;
 63    }
 64
 65    public int getOrientation() {
 66        return cameraInfo.orientation;
 67    }
 68
 69    public Camera open(
 70            final TextureView textureView,
 71            final int displayOrientation,
 72            final boolean continuousAutoFocus)
 73            throws IOException {
 74        final int cameraId = determineCameraId();
 75        Camera.getCameraInfo(cameraId, cameraInfo);
 76
 77        camera = Camera.open(cameraId);
 78
 79        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
 80            camera.setDisplayOrientation((720 - displayOrientation - cameraInfo.orientation) % 360);
 81        else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
 82            camera.setDisplayOrientation((720 - displayOrientation + cameraInfo.orientation) % 360);
 83        else throw new IllegalStateException("facing: " + cameraInfo.facing);
 84
 85        camera.setPreviewTexture(textureView.getSurfaceTexture());
 86
 87        final int width = textureView.getWidth();
 88        final int height = textureView.getHeight();
 89
 90        final Camera.Parameters parameters = camera.getParameters();
 91
 92        cameraResolution = findBestPreviewSizeValue(parameters, width, height);
 93
 94        final int rawSize = Math.min(width * 2 / 3, height * 2 / 3);
 95        final int frameSize = Math.max(MIN_FRAME_SIZE, Math.min(MAX_FRAME_SIZE, rawSize));
 96
 97        final int leftOffset = (width - frameSize) / 2;
 98        final int topOffset = (height - frameSize) / 2;
 99        frame = new Rect(leftOffset, topOffset, leftOffset + frameSize, topOffset + frameSize);
100
101        float widthFactor;
102        float heightFactor;
103        Rect orientedFrame;
104        boolean isTexturePortrait = width < height;
105        boolean isCameraPortrait = cameraResolution.width < cameraResolution.height;
106        if (isTexturePortrait == isCameraPortrait) {
107            widthFactor = (float) cameraResolution.width / width;
108            heightFactor = (float) cameraResolution.height / height;
109            orientedFrame = new Rect(frame);
110        } else {
111            widthFactor = (float) cameraResolution.width / height;
112            heightFactor = (float) cameraResolution.height / width;
113            // Swap X and Y coordinates to flip frame to the same orientation as cameraResolution
114            orientedFrame = new Rect(frame.top, frame.left, frame.bottom, frame.right);
115        }
116
117        framePreview =
118                new RectF(
119                        orientedFrame.left * widthFactor,
120                        orientedFrame.top * heightFactor,
121                        orientedFrame.right * widthFactor,
122                        orientedFrame.bottom * heightFactor);
123
124        final String savedParameters = parameters == null ? null : parameters.flatten();
125
126        try {
127            setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
128        } catch (final RuntimeException x) {
129            if (savedParameters != null) {
130                final Camera.Parameters parameters2 = camera.getParameters();
131                parameters2.unflatten(savedParameters);
132                try {
133                    camera.setParameters(parameters2);
134                    setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
135                } catch (final RuntimeException x2) {
136                    Log.d(Config.LOGTAG, "problem setting camera parameters", x2);
137                }
138            }
139        }
140
141        try {
142            camera.startPreview();
143            return camera;
144        } catch (final RuntimeException x) {
145            Log.w(Config.LOGTAG, "something went wrong while starting camera preview", x);
146            camera.release();
147            throw x;
148        }
149    }
150
151    private int determineCameraId() {
152        final int cameraCount = Camera.getNumberOfCameras();
153        final CameraInfo cameraInfo = new CameraInfo();
154
155        // prefer back-facing camera
156        for (int i = 0; i < cameraCount; i++) {
157            Camera.getCameraInfo(i, cameraInfo);
158            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) return i;
159        }
160
161        // fall back to front-facing camera
162        for (int i = 0; i < cameraCount; i++) {
163            Camera.getCameraInfo(i, cameraInfo);
164            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) return i;
165        }
166
167        return -1;
168    }
169
170    public void close() {
171        if (camera != null) {
172            try {
173                camera.stopPreview();
174            } catch (final RuntimeException x) {
175                Log.w(Config.LOGTAG, "something went wrong while stopping camera preview", x);
176            }
177
178            camera.release();
179        }
180    }
181
182    private static final Comparator<Camera.Size> numPixelComparator =
183            new Comparator<Camera.Size>() {
184                @Override
185                public int compare(final Camera.Size size1, final Camera.Size size2) {
186                    final int pixels1 = size1.height * size1.width;
187                    final int pixels2 = size2.height * size2.width;
188
189                    if (pixels1 < pixels2) return 1;
190                    else if (pixels1 > pixels2) return -1;
191                    else return 0;
192                }
193            };
194
195    private static Camera.Size findBestPreviewSizeValue(
196            final Camera.Parameters parameters, int width, int height) {
197        if (height > width) {
198            final int temp = width;
199            width = height;
200            height = temp;
201        }
202
203        final float screenAspectRatio = (float) width / (float) height;
204
205        final List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
206        if (rawSupportedSizes == null) return parameters.getPreviewSize();
207
208        // sort by size, descending
209        final List<Camera.Size> supportedPreviewSizes =
210                new ArrayList<Camera.Size>(rawSupportedSizes);
211        Collections.sort(supportedPreviewSizes, numPixelComparator);
212
213        Camera.Size bestSize = null;
214        float diff = Float.POSITIVE_INFINITY;
215
216        for (final Camera.Size supportedPreviewSize : supportedPreviewSizes) {
217            final int realWidth = supportedPreviewSize.width;
218            final int realHeight = supportedPreviewSize.height;
219            final int realPixels = realWidth * realHeight;
220            if (realPixels < MIN_PREVIEW_PIXELS || realPixels > MAX_PREVIEW_PIXELS) continue;
221
222            final boolean isCandidatePortrait = realWidth < realHeight;
223            final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
224            final int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
225            if (maybeFlippedWidth == width && maybeFlippedHeight == height)
226                return supportedPreviewSize;
227
228            final float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
229            final float newDiff = Math.abs(aspectRatio - screenAspectRatio);
230            if (newDiff < diff) {
231                bestSize = supportedPreviewSize;
232                diff = newDiff;
233            }
234        }
235
236        if (bestSize != null) return bestSize;
237        else return parameters.getPreviewSize();
238    }
239
240    @SuppressLint("InlinedApi")
241    private static void setDesiredCameraParameters(
242            final Camera camera,
243            final Camera.Size cameraResolution,
244            final boolean continuousAutoFocus) {
245        final Camera.Parameters parameters = camera.getParameters();
246        if (parameters == null) return;
247
248        final List<String> supportedFocusModes = parameters.getSupportedFocusModes();
249        final String focusMode =
250                continuousAutoFocus
251                        ? findValue(
252                                supportedFocusModes,
253                                Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
254                                Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
255                                Camera.Parameters.FOCUS_MODE_AUTO,
256                                Camera.Parameters.FOCUS_MODE_MACRO)
257                        : findValue(
258                                supportedFocusModes,
259                                Camera.Parameters.FOCUS_MODE_AUTO,
260                                Camera.Parameters.FOCUS_MODE_MACRO);
261        if (focusMode != null) parameters.setFocusMode(focusMode);
262
263        parameters.setPreviewSize(cameraResolution.width, cameraResolution.height);
264
265        camera.setParameters(parameters);
266    }
267
268    public void requestPreviewFrame(final PreviewCallback callback) {
269        try {
270            camera.setOneShotPreviewCallback(callback);
271        } catch (final RuntimeException x) {
272            Log.d(Config.LOGTAG, "problem requesting preview frame, callback won't be called", x);
273        }
274    }
275
276    public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) {
277        return new PlanarYUVLuminanceSource(
278                data,
279                cameraResolution.width,
280                cameraResolution.height,
281                (int) framePreview.left,
282                (int) framePreview.top,
283                (int) framePreview.width(),
284                (int) framePreview.height(),
285                false);
286    }
287
288    public void setTorch(final boolean enabled) {
289        if (enabled != getTorchEnabled(camera)) setTorchEnabled(camera, enabled);
290    }
291
292    private static boolean getTorchEnabled(final Camera camera) {
293        final Camera.Parameters parameters = camera.getParameters();
294        if (parameters != null) {
295            final String flashMode = camera.getParameters().getFlashMode();
296            return (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)
297                    || Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
298        }
299
300        return false;
301    }
302
303    private static void setTorchEnabled(final Camera camera, final boolean enabled) {
304        final Camera.Parameters parameters = camera.getParameters();
305
306        final List<String> supportedFlashModes = parameters.getSupportedFlashModes();
307        if (supportedFlashModes != null) {
308            final String flashMode;
309            if (enabled)
310                flashMode =
311                        findValue(
312                                supportedFlashModes,
313                                Camera.Parameters.FLASH_MODE_TORCH,
314                                Camera.Parameters.FLASH_MODE_ON);
315            else flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
316
317            if (flashMode != null) {
318                camera.cancelAutoFocus(); // autofocus can cause conflict
319
320                parameters.setFlashMode(flashMode);
321                camera.setParameters(parameters);
322            }
323        }
324    }
325
326    private static String findValue(final Collection<String> values, final String... valuesToFind) {
327        for (final String valueToFind : valuesToFind)
328            if (values.contains(valueToFind)) return valueToFind;
329
330        return null;
331    }
332}