1 /* 2 * Copyright (C) 2008 ZXing authors 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17 package com.google.zxing.client.android.camera; 18 19 import android.content.Context; 20 import android.graphics.Point; 21 import android.graphics.Rect; 22 import android.hardware.Camera; 23 import android.os.Handler; 24 import android.util.Log; 25 import android.view.SurfaceHolder; 26 import com.google.zxing.PlanarYUVLuminanceSource; 27 import com.google.zxing.client.android.camera.open.OpenCamera; 28 import com.google.zxing.client.android.camera.open.OpenCameraInterface; 29 30 import java.io.IOException; 31 32 /** 33 * This object wraps the Camera service object and expects to be the only one talking to it. The 34 * implementation encapsulates the steps needed to take preview-sized images, which are used for 35 * both preview and decoding. 36 * 37 * @author [email protected] (Daniel Switkin) 38 */ 39 @SuppressWarnings("deprecation") // camera APIs 40 public final class CameraManager { 41 42 private static final String TAG = CameraManager.class.getSimpleName(); 43 44 private static final int MIN_FRAME_WIDTH = 240; 45 private static final int MIN_FRAME_HEIGHT = 240; 46 private static final int MAX_FRAME_WIDTH = 1200; // = 5/8 * 1920 47 private static final int MAX_FRAME_HEIGHT = 675; // = 5/8 * 1080 48 49 private final Context context; 50 private final CameraConfigurationManager configManager; 51 private OpenCamera camera; 52 private AutoFocusManager autoFocusManager; 53 private Rect framingRect; 54 private Rect framingRectInPreview; 55 private boolean initialized; 56 private boolean previewing; 57 private int requestedCameraId = OpenCameraInterface.NO_REQUESTED_CAMERA; 58 private int requestedFramingRectWidth; 59 private int requestedFramingRectHeight; 60 /** 61 * Preview frames are delivered here, which we pass on to the registered handler. Make sure to 62 * clear the handler so it will only receive one message. 63 */ 64 private final PreviewCallback previewCallback; 65 CameraManager(Context context)66 public CameraManager(Context context) { 67 this.context = context; 68 this.configManager = new CameraConfigurationManager(context); 69 previewCallback = new PreviewCallback(configManager); 70 } 71 72 /** 73 * Opens the camera driver and initializes the hardware parameters. 74 * 75 * @param holder The surface object which the camera will draw preview frames into. 76 * @throws IOException Indicates the camera driver failed to open. 77 */ openDriver(SurfaceHolder holder)78 public synchronized void openDriver(SurfaceHolder holder) throws IOException { 79 OpenCamera theCamera = camera; 80 if (theCamera == null) { 81 theCamera = OpenCameraInterface.open(requestedCameraId); 82 if (theCamera == null) { 83 throw new IOException("Camera.open() failed to return object from driver"); 84 } 85 camera = theCamera; 86 } 87 88 if (!initialized) { 89 initialized = true; 90 configManager.initFromCameraParameters(theCamera); 91 if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) { 92 setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight); 93 requestedFramingRectWidth = 0; 94 requestedFramingRectHeight = 0; 95 } 96 } 97 98 Camera cameraObject = theCamera.getCamera(); 99 Camera.Parameters parameters = cameraObject.getParameters(); 100 String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily 101 try { 102 configManager.setDesiredCameraParameters(theCamera, false); 103 } catch (RuntimeException re) { 104 // Driver failed 105 Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters"); 106 Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened); 107 // Reset: 108 if (parametersFlattened != null) { 109 parameters = cameraObject.getParameters(); 110 parameters.unflatten(parametersFlattened); 111 try { 112 cameraObject.setParameters(parameters); 113 configManager.setDesiredCameraParameters(theCamera, true); 114 } catch (RuntimeException re2) { 115 // Well, darn. Give up 116 Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration"); 117 } 118 } 119 } 120 cameraObject.setPreviewDisplay(holder); 121 122 } 123 isOpen()124 public synchronized boolean isOpen() { 125 return camera != null; 126 } 127 128 /** 129 * Closes the camera driver if still in use. 130 */ closeDriver()131 public synchronized void closeDriver() { 132 if (camera != null) { 133 camera.getCamera().release(); 134 camera = null; 135 // Make sure to clear these each time we close the camera, so that any scanning rect 136 // requested by intent is forgotten. 137 framingRect = null; 138 framingRectInPreview = null; 139 } 140 } 141 142 /** 143 * Asks the camera hardware to begin drawing preview frames to the screen. 144 */ startPreview()145 public synchronized void startPreview() { 146 OpenCamera theCamera = camera; 147 if (theCamera != null && !previewing) { 148 theCamera.getCamera().startPreview(); 149 previewing = true; 150 autoFocusManager = new AutoFocusManager(context, theCamera.getCamera()); 151 } 152 } 153 154 /** 155 * Tells the camera to stop drawing preview frames. 156 */ stopPreview()157 public synchronized void stopPreview() { 158 if (autoFocusManager != null) { 159 autoFocusManager.stop(); 160 autoFocusManager = null; 161 } 162 if (camera != null && previewing) { 163 camera.getCamera().stopPreview(); 164 previewCallback.setHandler(null, 0); 165 previewing = false; 166 } 167 } 168 169 /** 170 * Convenience method for {@link com.google.zxing.client.android.CaptureActivity} 171 * 172 * @param newSetting if {@code true}, light should be turned on if currently off. And vice versa. 173 */ setTorch(boolean newSetting)174 public synchronized void setTorch(boolean newSetting) { 175 OpenCamera theCamera = camera; 176 if (theCamera != null && newSetting != configManager.getTorchState(theCamera.getCamera())) { 177 boolean wasAutoFocusManager = autoFocusManager != null; 178 if (wasAutoFocusManager) { 179 autoFocusManager.stop(); 180 autoFocusManager = null; 181 } 182 configManager.setTorch(theCamera.getCamera(), newSetting); 183 if (wasAutoFocusManager) { 184 autoFocusManager = new AutoFocusManager(context, theCamera.getCamera()); 185 autoFocusManager.start(); 186 } 187 } 188 } 189 190 /** 191 * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] 192 * in the message.obj field, with width and height encoded as message.arg1 and message.arg2, 193 * respectively. 194 * 195 * @param handler The handler to send the message to. 196 * @param message The what field of the message to be sent. 197 */ requestPreviewFrame(Handler handler, int message)198 public synchronized void requestPreviewFrame(Handler handler, int message) { 199 OpenCamera theCamera = camera; 200 if (theCamera != null && previewing) { 201 previewCallback.setHandler(handler, message); 202 theCamera.getCamera().setOneShotPreviewCallback(previewCallback); 203 } 204 } 205 206 /** 207 * Calculates the framing rect which the UI should draw to show the user where to place the 208 * barcode. This target helps with alignment as well as forces the user to hold the device 209 * far enough away to ensure the image will be in focus. 210 * 211 * @return The rectangle to draw on screen in window coordinates. 212 */ getFramingRect()213 public synchronized Rect getFramingRect() { 214 if (framingRect == null) { 215 if (camera == null) { 216 return null; 217 } 218 Point screenResolution = configManager.getScreenResolution(); 219 if (screenResolution == null) { 220 // Called early, before init even finished 221 return null; 222 } 223 224 int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH); 225 int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT); 226 227 int leftOffset = (screenResolution.x - width) / 2; 228 int topOffset = (screenResolution.y - height) / 2; 229 framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); 230 } 231 return framingRect; 232 } 233 findDesiredDimensionInRange(int resolution, int hardMin, int hardMax)234 private static int findDesiredDimensionInRange(int resolution, int hardMin, int hardMax) { 235 int dim = 5 * resolution / 8; // Target 5/8 of each dimension 236 if (dim < hardMin) { 237 return hardMin; 238 } 239 return Math.min(dim, hardMax); 240 } 241 242 /** 243 * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, 244 * not UI / screen. 245 * 246 * @return {@link Rect} expressing barcode scan area in terms of the preview size 247 */ getFramingRectInPreview()248 public synchronized Rect getFramingRectInPreview() { 249 if (framingRectInPreview == null) { 250 Rect framingRect = getFramingRect(); 251 if (framingRect == null) { 252 return null; 253 } 254 Rect rect = new Rect(framingRect); 255 Point cameraResolution = configManager.getCameraResolution(); 256 Point screenResolution = configManager.getScreenResolution(); 257 if (cameraResolution == null || screenResolution == null) { 258 // Called early, before init even finished 259 return null; 260 } 261 rect.left = rect.left * cameraResolution.x / screenResolution.x; 262 rect.right = rect.right * cameraResolution.x / screenResolution.x; 263 rect.top = rect.top * cameraResolution.y / screenResolution.y; 264 rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; 265 framingRectInPreview = rect; 266 } 267 return framingRectInPreview; 268 } 269 270 271 /** 272 * Allows third party apps to specify the camera ID, rather than determine 273 * it automatically based on available cameras and their orientation. 274 * 275 * @param cameraId camera ID of the camera to use. A negative value means "no preference". 276 */ setManualCameraId(int cameraId)277 public synchronized void setManualCameraId(int cameraId) { 278 requestedCameraId = cameraId; 279 } 280 281 /** 282 * Allows third party apps to specify the scanning rectangle dimensions, rather than determine 283 * them automatically based on screen resolution. 284 * 285 * @param width The width in pixels to scan. 286 * @param height The height in pixels to scan. 287 */ setManualFramingRect(int width, int height)288 public synchronized void setManualFramingRect(int width, int height) { 289 if (initialized) { 290 Point screenResolution = configManager.getScreenResolution(); 291 if (width > screenResolution.x) { 292 width = screenResolution.x; 293 } 294 if (height > screenResolution.y) { 295 height = screenResolution.y; 296 } 297 int leftOffset = (screenResolution.x - width) / 2; 298 int topOffset = (screenResolution.y - height) / 2; 299 framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); 300 Log.d(TAG, "Calculated manual framing rect: " + framingRect); 301 framingRectInPreview = null; 302 } else { 303 requestedFramingRectWidth = width; 304 requestedFramingRectHeight = height; 305 } 306 } 307 308 /** 309 * A factory method to build the appropriate LuminanceSource object based on the format 310 * of the preview buffers, as described by Camera.Parameters. 311 * 312 * @param data A preview frame. 313 * @param width The width of the image. 314 * @param height The height of the image. 315 * @return A PlanarYUVLuminanceSource instance. 316 */ buildLuminanceSource(byte[] data, int width, int height)317 public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { 318 Rect rect = getFramingRectInPreview(); 319 if (rect == null) { 320 return null; 321 } 322 // Go ahead and assume it's YUV rather than die. 323 return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, 324 rect.width(), rect.height(), false); 325 } 326 327 } 328