import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.WindowManager;
import java.io.IOException;
+import java.util.regex.Pattern;
/**
* This object wraps the Camera service object and expects to be the only one talking to it. The
* @author dswitkin@google.com (Daniel Switkin)
*/
final class CameraManager {
+
private static final String TAG = "CameraManager";
+
private static final int MIN_FRAME_WIDTH = 240;
private static final int MIN_FRAME_HEIGHT = 240;
private static final int MAX_FRAME_WIDTH = 480;
private static final int MAX_FRAME_HEIGHT = 360;
+ private static final Pattern COMMA_PATTERN = Pattern.compile(",");
+
private static CameraManager cameraManager;
+
private Camera camera;
private final Context context;
private Point screenResolution;
private boolean previewing;
private int previewFormat;
private String previewFormatString;
+ private boolean useOneShotPreviewCallback;
/**
* Preview frames are delivered here, which we pass on to the registered handler. Make sure to
*/
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
- camera.setPreviewCallback(null);
+ if (!useOneShotPreviewCallback) {
+ camera.setPreviewCallback(null);
+ }
if (previewHandler != null) {
Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
}
};
+ /**
+ * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
+ */
private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
if (autoFocusHandler != null) {
}
};
+ /**
+ * Initializes this static object with the Context of the calling Activity.
+ *
+ * @param context The Activity which wants to use the camera.
+ */
public static void init(Context context) {
if (cameraManager == null) {
cameraManager = new CameraManager(context);
}
}
+ /**
+ * Gets the CameraManager singleton instance.
+ *
+ * @return A reference to the CameraManager singleton.
+ */
public static CameraManager get() {
return cameraManager;
}
camera = null;
initialized = false;
previewing = false;
+
+ // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
+ // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
+ // the more efficient one shot callback, as the older one can swamp the system and cause it
+ // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
+ useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE;
}
+ /**
+ * Opens the camera driver and initializes the hardware parameters.
+ *
+ * @param holder The surface object which the camera will draw preview frames into.
+ * @throws IOException Indicates the camera driver failed to open.
+ */
public void openDriver(SurfaceHolder holder) throws IOException {
if (camera == null) {
camera = Camera.open();
+ if (camera == null) {
+ throw new IOException();
+ }
camera.setPreviewDisplay(holder);
if (!initialized) {
}
setCameraParameters();
+ FlashlightManager.enableFlashlight();
}
}
+ /**
+ * Closes the camera driver if still in use.
+ */
public void closeDriver() {
if (camera != null) {
+ FlashlightManager.disableFlashlight();
camera.release();
camera = null;
}
}
+ /**
+ * Asks the camera hardware to begin drawing preview frames to the screen.
+ */
public void startPreview() {
if (camera != null && !previewing) {
camera.startPreview();
}
}
+ /**
+ * Tells the camera to stop drawing preview frames.
+ */
public void stopPreview() {
if (camera != null && previewing) {
- camera.setPreviewCallback(null);
+ if (!useOneShotPreviewCallback) {
+ camera.setPreviewCallback(null);
+ }
camera.stopPreview();
previewHandler = null;
autoFocusHandler = null;
if (camera != null && previewing) {
previewHandler = handler;
previewMessage = message;
- camera.setPreviewCallback(previewCallback);
+ if (useOneShotPreviewCallback) {
+ camera.setOneShotPreviewCallback(previewCallback);
+ } else {
+ camera.setPreviewCallback(previewCallback);
+ }
}
}
+ /**
+ * Asks the camera hardware to perform an autofocus.
+ *
+ * @param handler The Handler to notify when the autofocus completes.
+ * @param message The message to deliver.
+ */
public void requestAutoFocus(Handler handler, int message) {
if (camera != null && previewing) {
autoFocusHandler = handler;
*/
public Rect getFramingRect() {
if (framingRect == null) {
+ if (camera == null) {
+ return null;
+ }
int width = cameraResolution.x * 3 / 4;
if (width < MIN_FRAME_WIDTH) {
width = MIN_FRAME_WIDTH;
* @param data A preview frame.
* @param width The width of the image.
* @param height The height of the image.
- * @return A BaseLuminanceSource subclass.
+ * @return A PlanarYUVLuminanceSource instance.
*/
- public BaseLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
+ public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRect();
switch (previewFormat) {
+ // This is the standard Android format which all devices are REQUIRED to support.
+ // In theory, it's the only one we should ever care about.
case PixelFormat.YCbCr_420_SP:
+ // This format has never been seen in the wild, but is compatible as we only care
+ // about the Y channel, so allow it.
case PixelFormat.YCbCr_422_SP:
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height());
default:
- // There's no PixelFormat constant for this buffer format yet.
- if (previewFormatString.equals("yuv422i-yuyv")) {
- return new InterleavedYUV422LuminanceSource(data, width, height, rect.left, rect.top,
- rect.width(), rect.height());
+ // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
+ // Fortunately, it too has all the Y data up front, so we can read it.
+ if ("yuv420p".equals(previewFormatString)) {
+ return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
+ rect.width(), rect.height());
}
- break;
}
- return null;
+ throw new IllegalArgumentException("Unsupported picture format: " +
+ previewFormat + '/' + previewFormatString);
}
/**
- * Sets the camera up to take preview images which are used for both preview and decoding. We're
- * counting on the default YUV420 semi-planar data. If that changes in the future, we'll need to
- * specify it explicitly with setPreviewFormat().
+ * Sets the camera up to take preview images which are used for both preview and decoding.
+ * We detect the preview format here so that buildLuminanceSource() can build an appropriate
+ * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
+ * and the planar Y can be used for barcode scanning without a copy in some cases.
*/
private void setCameraParameters() {
Camera.Parameters parameters = camera.getParameters();
- Camera.Size size = parameters.getPreviewSize();
- Log.v(TAG, "Default preview size: " + size.width + ", " + size.height);
previewFormat = parameters.getPreviewFormat();
previewFormatString = parameters.get("preview-format");
- Log.v(TAG, "Default preview format: " + previewFormat);
-
- // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
- // TODO: A better solution would be to request the supported preview resolutions
- // and pick the best match, but this parameter is not standardized in Cupcake.
- cameraResolution = new Point();
- cameraResolution.x = (screenResolution.x >> 3) << 3;
- cameraResolution.y = (screenResolution.y >> 3) << 3;
+ Log.v(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
+
+ cameraResolution = getCameraResolution(parameters);
Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
// This is the standard setting to turn the flash off that all devices should honor.
parameters.set("flash-mode", "off");
+ // Set zoom to 2x if available. This helps encourage the user to pull back.
+ // Some devices like the Behold have a zoom parameter
+ parameters.set("zoom", "2.0");
+ // Most devices, like the Hero, appear to expose this zoom parameter.
+ // (I think) This means 2.0x
+ parameters.set("taking-picture-zoom", "20");
+
camera.setParameters(parameters);
}
return screenResolution;
}
+ private Point getCameraResolution(Camera.Parameters parameters) {
+
+ String previewSizeValueString = parameters.get("preview-size-values");
+ // saw this on Xperia
+ if (previewSizeValueString == null) {
+ previewSizeValueString = parameters.get("preview-size-value");
+ }
+
+ Point cameraResolution = null;
+
+ if (previewSizeValueString != null) {
+ Log.v(TAG, "preview-size parameter: " + previewSizeValueString);
+ cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);
+ }
+
+ if (cameraResolution == null) {
+ // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
+ cameraResolution = new Point(
+ (screenResolution.x >> 3) << 3,
+ (screenResolution.y >> 3) << 3);
+ }
+
+ return cameraResolution;
+ }
+
+ private static Point findBestPreviewSizeValue(String previewSizeValueString, Point screenResolution) {
+ int bestX = 0;
+ int bestY = 0;
+ int diff = Integer.MAX_VALUE;
+ for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
+
+ previewSize = previewSize.trim();
+ int dimPosition = previewSize.indexOf('x');
+ if (dimPosition < 0) {
+ Log.w(TAG, "Bad preview-size");
+ continue;
+ }
+
+ int newX;
+ int newY;
+ try {
+ newX = Integer.parseInt(previewSize.substring(0, dimPosition));
+ newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, "Bad preview-size");
+ continue;
+ }
+
+ int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
+ if (newDiff == 0) {
+ bestX = newX;
+ bestY = newY;
+ break;
+ } else if (newDiff < diff) {
+ bestX = newX;
+ bestY = newY;
+ diff = newDiff;
+ }
+
+ }
+
+ if (bestX > 0 && bestY > 0) {
+ return new Point(bestX, bestY);
+ }
+ return null;
+ }
+
}