2 * Copyright (C) 2008 ZXing authors
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.google.zxing.client.android;
19 import com.google.zxing.ResultPoint;
21 import android.content.Context;
22 import android.graphics.PixelFormat;
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.hardware.Camera;
26 import android.os.Build;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.util.Log;
30 import android.view.Display;
31 import android.view.SurfaceHolder;
32 import android.view.WindowManager;
34 import java.io.IOException;
35 import java.util.regex.Pattern;
38 * This object wraps the Camera service object and expects to be the only one talking to it. The
39 * implementation encapsulates the steps needed to take preview-sized images, which are used for
40 * both preview and decoding.
42 * @author dswitkin@google.com (Daniel Switkin)
44 final class CameraManager {
46 private static final String TAG = "CameraManager";
48 private static final int MIN_FRAME_WIDTH = 240;
49 private static final int MIN_FRAME_HEIGHT = 240;
50 private static final int MAX_FRAME_WIDTH = 480;
51 private static final int MAX_FRAME_HEIGHT = 360;
53 private static final int TEN_DESIRED_ZOOM = 27;
54 private static final int DESIRED_SHARPNESS = 30;
56 private static final Pattern COMMA_PATTERN = Pattern.compile(",");
58 private static CameraManager cameraManager;
60 private Camera camera;
61 private final Context context;
62 private Point screenResolution;
63 private Point cameraResolution;
64 private Rect framingRect;
65 private Handler previewHandler;
66 private int previewMessage;
67 private Handler autoFocusHandler;
68 private int autoFocusMessage;
69 private boolean initialized;
70 private boolean previewing;
71 private int previewFormat;
72 private String previewFormatString;
73 private final boolean useOneShotPreviewCallback;
76 * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
77 * clear the handler so it will only receive one message.
79 private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
80 public void onPreviewFrame(byte[] data, Camera camera) {
81 if (!useOneShotPreviewCallback) {
82 camera.setPreviewCallback(null);
84 if (previewHandler != null) {
85 Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
86 cameraResolution.y, data);
87 message.sendToTarget();
88 previewHandler = null;
94 * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
96 private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
97 public void onAutoFocus(boolean success, Camera camera) {
98 if (autoFocusHandler != null) {
99 Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
100 // Simulate continuous autofocus by sending a focus request every 1.5 seconds.
101 autoFocusHandler.sendMessageDelayed(message, 1500L);
102 autoFocusHandler = null;
108 * Initializes this static object with the Context of the calling Activity.
110 * @param context The Activity which wants to use the camera.
112 public static void init(Context context) {
113 if (cameraManager == null) {
114 cameraManager = new CameraManager(context);
119 * Gets the CameraManager singleton instance.
121 * @return A reference to the CameraManager singleton.
123 public static CameraManager get() {
124 return cameraManager;
127 private CameraManager(Context context) {
128 this.context = context;
133 // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
134 // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
135 // the more efficient one shot callback, as the older one can swamp the system and cause it
136 // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
137 useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE;
141 * Opens the camera driver and initializes the hardware parameters.
143 * @param holder The surface object which the camera will draw preview frames into.
144 * @throws IOException Indicates the camera driver failed to open.
146 public void openDriver(SurfaceHolder holder) throws IOException {
147 if (camera == null) {
148 camera = Camera.open();
149 if (camera == null) {
150 throw new IOException();
152 camera.setPreviewDisplay(holder);
156 getScreenResolution();
159 setCameraParameters();
160 FlashlightManager.enableFlashlight();
165 * Closes the camera driver if still in use.
167 public void closeDriver() {
168 if (camera != null) {
169 FlashlightManager.disableFlashlight();
176 * Asks the camera hardware to begin drawing preview frames to the screen.
178 public void startPreview() {
179 if (camera != null && !previewing) {
180 camera.startPreview();
186 * Tells the camera to stop drawing preview frames.
188 public void stopPreview() {
189 if (camera != null && previewing) {
190 if (!useOneShotPreviewCallback) {
191 camera.setPreviewCallback(null);
193 camera.stopPreview();
194 previewHandler = null;
195 autoFocusHandler = null;
201 * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
202 * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
205 * @param handler The handler to send the message to.
206 * @param message The what field of the message to be sent.
208 public void requestPreviewFrame(Handler handler, int message) {
209 if (camera != null && previewing) {
210 previewHandler = handler;
211 previewMessage = message;
212 if (useOneShotPreviewCallback) {
213 camera.setOneShotPreviewCallback(previewCallback);
215 camera.setPreviewCallback(previewCallback);
221 * Asks the camera hardware to perform an autofocus.
223 * @param handler The Handler to notify when the autofocus completes.
224 * @param message The message to deliver.
226 public void requestAutoFocus(Handler handler, int message) {
227 if (camera != null && previewing) {
228 autoFocusHandler = handler;
229 autoFocusMessage = message;
230 camera.autoFocus(autoFocusCallback);
235 * Calculates the framing rect which the UI should draw to show the user where to place the
236 * barcode. This target helps with alignment as well as forces the user to hold the device
237 * far enough away to ensure the image will be in focus.
239 * @return The rectangle to draw on screen in window coordinates.
241 public Rect getFramingRect() {
242 if (framingRect == null) {
243 if (camera == null) {
246 int width = cameraResolution.x * 3 / 4;
247 if (width < MIN_FRAME_WIDTH) {
248 width = MIN_FRAME_WIDTH;
249 } else if (width > MAX_FRAME_WIDTH) {
250 width = MAX_FRAME_WIDTH;
252 int height = cameraResolution.y * 3 / 4;
253 if (height < MIN_FRAME_HEIGHT) {
254 height = MIN_FRAME_HEIGHT;
255 } else if (height > MAX_FRAME_HEIGHT) {
256 height = MAX_FRAME_HEIGHT;
258 int leftOffset = (cameraResolution.x - width) / 2;
259 int topOffset = (cameraResolution.y - height) / 2;
260 framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
261 Log.v(TAG, "Calculated framing rect: " + framingRect);
267 * Converts the result points from still resolution coordinates to screen coordinates.
269 * @param points The points returned by the Reader subclass through Result.getResultPoints().
270 * @return An array of Points scaled to the size of the framing rect and offset appropriately
271 * so they can be drawn in screen coordinates.
273 public Point[] convertResultPoints(ResultPoint[] points) {
274 Rect frame = getFramingRect();
275 int count = points.length;
276 Point[] output = new Point[count];
277 for (int x = 0; x < count; x++) {
278 output[x] = new Point();
279 output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
280 output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
286 * A factory method to build the appropriate LuminanceSource object based on the format
287 * of the preview buffers, as described by Camera.Parameters.
289 * @param data A preview frame.
290 * @param width The width of the image.
291 * @param height The height of the image.
292 * @return A PlanarYUVLuminanceSource instance.
294 public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
295 Rect rect = getFramingRect();
296 switch (previewFormat) {
297 // This is the standard Android format which all devices are REQUIRED to support.
298 // In theory, it's the only one we should ever care about.
299 case PixelFormat.YCbCr_420_SP:
300 // This format has never been seen in the wild, but is compatible as we only care
301 // about the Y channel, so allow it.
302 case PixelFormat.YCbCr_422_SP:
303 return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
304 rect.width(), rect.height());
306 // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
307 // Fortunately, it too has all the Y data up front, so we can read it.
308 if ("yuv420p".equals(previewFormatString)) {
309 return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
310 rect.width(), rect.height());
313 throw new IllegalArgumentException("Unsupported picture format: " +
314 previewFormat + '/' + previewFormatString);
318 * Sets the camera up to take preview images which are used for both preview and decoding.
319 * We detect the preview format here so that buildLuminanceSource() can build an appropriate
320 * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
321 * and the planar Y can be used for barcode scanning without a copy in some cases.
323 private void setCameraParameters() {
324 Camera.Parameters parameters = camera.getParameters();
325 previewFormat = parameters.getPreviewFormat();
326 previewFormatString = parameters.get("preview-format");
327 Log.v(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
329 cameraResolution = getCameraResolution(parameters);
330 Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
331 parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
333 setFlash(parameters);
335 //setSharpness(parameters);
337 camera.setParameters(parameters);
340 private Point getScreenResolution() {
341 if (screenResolution == null) {
342 WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
343 Display display = manager.getDefaultDisplay();
344 screenResolution = new Point(display.getWidth(), display.getHeight());
346 return screenResolution;
349 private Point getCameraResolution(Camera.Parameters parameters) {
351 String previewSizeValueString = parameters.get("preview-size-values");
352 // saw this on Xperia
353 if (previewSizeValueString == null) {
354 previewSizeValueString = parameters.get("preview-size-value");
357 Point cameraResolution = null;
359 if (previewSizeValueString != null) {
360 Log.v(TAG, "preview-size parameter: " + previewSizeValueString);
361 cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);
364 if (cameraResolution == null) {
365 // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
366 cameraResolution = new Point(
367 (screenResolution.x >> 3) << 3,
368 (screenResolution.y >> 3) << 3);
371 return cameraResolution;
374 private static Point findBestPreviewSizeValue(String previewSizeValueString, Point screenResolution) {
377 int diff = Integer.MAX_VALUE;
378 for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
380 previewSize = previewSize.trim();
381 int dimPosition = previewSize.indexOf('x');
382 if (dimPosition < 0) {
383 Log.w(TAG, "Bad preview-size: " + previewSize);
390 newX = Integer.parseInt(previewSize.substring(0, dimPosition));
391 newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
392 } catch (NumberFormatException nfe) {
393 Log.w(TAG, "Bad preview-size: " + previewSize);
397 int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
402 } else if (newDiff < diff) {
410 if (bestX > 0 && bestY > 0) {
411 return new Point(bestX, bestY);
416 private void setFlash(Camera.Parameters parameters) {
417 // FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
418 parameters.set("flash-value", 2);
419 // This is the standard setting to turn the flash off that all devices should honor.
420 parameters.set("flash-mode", "off");
423 private void setZoom(Camera.Parameters parameters) {
425 String zoomSupportedString = parameters.get("zoom-supported");
426 if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) {
430 int tenDesiredZoom = TEN_DESIRED_ZOOM;
432 String maxZoomString = parameters.get("max-zoom");
433 if (maxZoomString != null) {
435 int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString));
436 if (tenDesiredZoom > tenMaxZoom) {
437 tenDesiredZoom = tenMaxZoom;
439 } catch (NumberFormatException nfe) {
440 Log.w(TAG, "Bad max-zoom: " + maxZoomString);
444 String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max");
445 if (takingPictureZoomMaxString != null) {
447 int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString);
448 if (tenDesiredZoom > tenMaxZoom) {
449 tenDesiredZoom = tenMaxZoom;
451 } catch (NumberFormatException nfe) {
452 Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString);
457 // Set zoom. This helps encourage the user to pull back.
458 // Some devices like the Behold have a zoom parameter
459 if (maxZoomString != null) {
460 parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0));
463 // Most devices, like the Hero, appear to expose this zoom parameter.
464 // It takes on values like "27" which appears to mean 2.7x zoom
465 if (takingPictureZoomMaxString != null) {
466 parameters.set("taking-picture-zoom", tenDesiredZoom);
471 private void setSharpness(Camera.Parameters parameters) {
473 int desiredSharpness = DESIRED_SHARPNESS;
475 String maxSharpnessString = parameters.get("sharpness-max");
476 if (maxSharpnessString != null) {
478 int maxSharpness = Integer.parseInt(maxSharpnessString);
479 if (desiredSharpness > maxSharpness) {
480 desiredSharpness = maxSharpness;
482 } catch (NumberFormatException nfe) {
483 Log.w(TAG, "Bad sharpness-max: " + maxSharpnessString);
487 parameters.set("sharpness", desiredSharpness);