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;
 28
 29import com.google.zxing.PlanarYUVLuminanceSource;
 30
 31import java.io.IOException;
 32import java.util.ArrayList;
 33import java.util.Collection;
 34import java.util.Collections;
 35import java.util.Comparator;
 36import java.util.List;
 37
 38import eu.siacs.conversations.Config;
 39
 40/**
 41 * @author Andreas Schildbach
 42 */
 43@SuppressWarnings("deprecation")
 44public final class CameraManager {
 45    private static final int MIN_FRAME_SIZE = 240;
 46    private static final int MAX_FRAME_SIZE = 600;
 47    private static final int MIN_PREVIEW_PIXELS = 470 * 320; // normal screen
 48    private static final int MAX_PREVIEW_PIXELS = 1280 * 720;
 49
 50    private Camera camera;
 51    private final CameraInfo cameraInfo = new CameraInfo();
 52    private Camera.Size cameraResolution;
 53    private Rect frame;
 54    private RectF framePreview;
 55
 56    public Rect getFrame() {
 57        return frame;
 58    }
 59
 60    public RectF getFramePreview() {
 61        return framePreview;
 62    }
 63
 64    public int getFacing() {
 65        return cameraInfo.facing;
 66    }
 67
 68    public int getOrientation() {
 69        return cameraInfo.orientation;
 70    }
 71
 72    public Camera open(final TextureView textureView, final int displayOrientation, 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
 84            throw new IllegalStateException("facing: " + cameraInfo.facing);
 85
 86        camera.setPreviewTexture(textureView.getSurfaceTexture());
 87
 88        final int width = textureView.getWidth();
 89        final int height = textureView.getHeight();
 90
 91        final Camera.Parameters parameters = camera.getParameters();
 92
 93        cameraResolution = findBestPreviewSizeValue(parameters, width, height);
 94
 95        final int rawSize = Math.min(width * 2 / 3, height * 2 / 3);
 96        final int frameSize = Math.max(MIN_FRAME_SIZE, Math.min(MAX_FRAME_SIZE, rawSize));
 97
 98        final int leftOffset = (width - frameSize) / 2;
 99        final int topOffset = (height - frameSize) / 2;
100        frame = new Rect(leftOffset, topOffset, leftOffset + frameSize, topOffset + frameSize);
101
102        float widthFactor;
103        float heightFactor;
104        Rect orientedFrame;
105        boolean isTexturePortrait = width < height;
106        boolean isCameraPortrait = cameraResolution.width < cameraResolution.height;
107        if (isTexturePortrait == isCameraPortrait) {
108            widthFactor = (float)cameraResolution.width / width;
109            heightFactor = (float)cameraResolution.height / height;
110            orientedFrame = new Rect(frame);
111        } else {
112            widthFactor = (float)cameraResolution.width / height;
113            heightFactor = (float)cameraResolution.height / width;
114            // Swap X and Y coordinates to flip frame to the same orientation as cameraResolution
115            orientedFrame = new Rect(frame.top, frame.left, frame.bottom, frame.right);
116        }
117
118        framePreview = new RectF(orientedFrame.left * widthFactor, orientedFrame.top * heightFactor,
119                orientedFrame.right * widthFactor, orientedFrame.bottom * heightFactor);
120
121        final String savedParameters = parameters == null ? null : parameters.flatten();
122
123        try {
124            setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
125        } catch (final RuntimeException x) {
126            if (savedParameters != null) {
127                final Camera.Parameters parameters2 = camera.getParameters();
128                parameters2.unflatten(savedParameters);
129                try {
130                    camera.setParameters(parameters2);
131                    setDesiredCameraParameters(camera, cameraResolution, continuousAutoFocus);
132                } catch (final RuntimeException x2) {
133                    Log.d(Config.LOGTAG,"problem setting camera parameters", x2);
134                }
135            }
136        }
137
138        try {
139            camera.startPreview();
140            return camera;
141        } catch (final RuntimeException x) {
142            Log.w(Config.LOGTAG,"something went wrong while starting camera preview", x);
143            camera.release();
144            throw x;
145        }
146    }
147
148    private int determineCameraId() {
149        final int cameraCount = Camera.getNumberOfCameras();
150        final CameraInfo cameraInfo = new CameraInfo();
151
152        // prefer back-facing camera
153        for (int i = 0; i < cameraCount; i++) {
154            Camera.getCameraInfo(i, cameraInfo);
155            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
156                return i;
157        }
158
159        // fall back to front-facing camera
160        for (int i = 0; i < cameraCount; i++) {
161            Camera.getCameraInfo(i, cameraInfo);
162            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
163                return i;
164        }
165
166        return -1;
167    }
168
169    public void close() {
170        if (camera != null) {
171            try {
172                camera.stopPreview();
173            } catch (final RuntimeException x) {
174                Log.w(Config.LOGTAG,"something went wrong while stopping camera preview", x);
175            }
176
177            camera.release();
178        }
179    }
180
181    private static final Comparator<Camera.Size> numPixelComparator = new Comparator<Camera.Size>() {
182        @Override
183        public int compare(final Camera.Size size1, final Camera.Size size2) {
184            final int pixels1 = size1.height * size1.width;
185            final int pixels2 = size2.height * size2.width;
186
187            if (pixels1 < pixels2)
188                return 1;
189            else if (pixels1 > pixels2)
190                return -1;
191            else
192                return 0;
193        }
194    };
195
196    private static Camera.Size findBestPreviewSizeValue(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)
207            return parameters.getPreviewSize();
208
209        // sort by size, descending
210        final List<Camera.Size> supportedPreviewSizes = 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)
221                continue;
222
223            final boolean isCandidatePortrait = realWidth < realHeight;
224            final int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
225            final int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
226            if (maybeFlippedWidth == width && maybeFlippedHeight == height)
227                return supportedPreviewSize;
228
229            final float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
230            final float newDiff = Math.abs(aspectRatio - screenAspectRatio);
231            if (newDiff < diff) {
232                bestSize = supportedPreviewSize;
233                diff = newDiff;
234            }
235        }
236
237        if (bestSize != null)
238            return bestSize;
239        else
240            return parameters.getPreviewSize();
241    }
242
243    @SuppressLint("InlinedApi")
244    private static void setDesiredCameraParameters(final Camera camera, final Camera.Size cameraResolution,
245            final boolean continuousAutoFocus) {
246        final Camera.Parameters parameters = camera.getParameters();
247        if (parameters == null)
248            return;
249
250        final List<String> supportedFocusModes = parameters.getSupportedFocusModes();
251        final String focusMode = continuousAutoFocus
252                ? findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
253                        Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, Camera.Parameters.FOCUS_MODE_AUTO,
254                        Camera.Parameters.FOCUS_MODE_MACRO)
255                : findValue(supportedFocusModes, Camera.Parameters.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_MACRO);
256        if (focusMode != null)
257            parameters.setFocusMode(focusMode);
258
259        parameters.setPreviewSize(cameraResolution.width, cameraResolution.height);
260
261        camera.setParameters(parameters);
262    }
263
264    public void requestPreviewFrame(final PreviewCallback callback) {
265        try {
266            camera.setOneShotPreviewCallback(callback);
267        } catch (final RuntimeException x) {
268            Log.d(Config.LOGTAG,"problem requesting preview frame, callback won't be called", x);
269        }
270    }
271
272    public PlanarYUVLuminanceSource buildLuminanceSource(final byte[] data) {
273        return new PlanarYUVLuminanceSource(data, cameraResolution.width, cameraResolution.height,
274                (int) framePreview.left, (int) framePreview.top, (int) framePreview.width(),
275                (int) framePreview.height(), false);
276    }
277
278    public void setTorch(final boolean enabled) {
279        if (enabled != getTorchEnabled(camera))
280            setTorchEnabled(camera, enabled);
281    }
282
283    private static boolean getTorchEnabled(final Camera camera) {
284        final Camera.Parameters parameters = camera.getParameters();
285        if (parameters != null) {
286            final String flashMode = camera.getParameters().getFlashMode();
287            return flashMode != null && (Camera.Parameters.FLASH_MODE_ON.equals(flashMode)
288                    || Camera.Parameters.FLASH_MODE_TORCH.equals(flashMode));
289        }
290
291        return false;
292    }
293
294    private static void setTorchEnabled(final Camera camera, final boolean enabled) {
295        final Camera.Parameters parameters = camera.getParameters();
296
297        final List<String> supportedFlashModes = parameters.getSupportedFlashModes();
298        if (supportedFlashModes != null) {
299            final String flashMode;
300            if (enabled)
301                flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_TORCH,
302                        Camera.Parameters.FLASH_MODE_ON);
303            else
304                flashMode = findValue(supportedFlashModes, Camera.Parameters.FLASH_MODE_OFF);
305
306            if (flashMode != null) {
307                camera.cancelAutoFocus(); // autofocus can cause conflict
308
309                parameters.setFlashMode(flashMode);
310                camera.setParameters(parameters);
311            }
312        }
313    }
314
315    private static String findValue(final Collection<String> values, final String... valuesToFind) {
316        for (final String valueToFind : valuesToFind)
317            if (values.contains(valueToFind))
318                return valueToFind;
319
320        return null;
321    }
322}