Updated ZXing Test with all of the CameraManager fixes from Barcode Scanner, as well as making it high-dpi capable, and bumped the version to 1.12.

git-svn-id: https://zxing.googlecode.com/svn/trunk@1102 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2009-11-06 15:55:00 +00:00
parent c47cf9752e
commit ac2ea432c0
4 changed files with 223 additions and 115 deletions

View file

@ -16,9 +16,16 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.zxing.client.androidtest"
android:versionName="1.11"
android:versionCode="3">
android:versionName="1.12"
android:versionCode="4">
<!-- We require Cupcake (Android 1.5) or later. -->
<uses-sdk android:minSdkVersion="3"/>
<!-- Donut-specific flags which allow us to run on large and high dpi screens. -->
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>
<application android:label="@string/app_name"
android:icon="@drawable/icon"
android:debuggable="true">

View file

@ -20,8 +20,10 @@ import android.content.Context;
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.Display;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@ -32,45 +34,119 @@ import java.io.IOException;
* This object wraps the Camera service object and expects to be the only one talking to it. The
* implementation encapsulates the steps needed to take preview-sized images, which are used for
* both preview and decoding.
*
* @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 CameraManager mCameraManager;
private Camera mCamera;
private final Context mContext;
private Point mScreenResolution;
private Rect mFramingRect;
private Handler mPreviewHandler;
private int mPreviewMessage;
private Handler mAutoFocusHandler;
private int mAutoFocusMessage;
private boolean mPreviewing;
private static CameraManager cameraManager;
private Camera camera;
private final Context context;
private Point screenResolution;
private Point cameraResolution;
private Rect framingRect;
private Handler previewHandler;
private int previewMessage;
private Handler autoFocusHandler;
private int autoFocusMessage;
private boolean initialized;
private boolean previewing;
private int previewFormat;
private String previewFormatString;
private boolean useOneShotPreviewCallback;
public static synchronized void init(Context context) {
if (mCameraManager == null) {
mCameraManager = new CameraManager(context);
mCameraManager.getScreenResolution();
/**
* Preview frames are delivered here, which we pass on to the registered handler. Make sure to
* clear the handler so it will only receive one message.
*/
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
if (previewHandler != null) {
Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
message.sendToTarget();
previewHandler = null;
}
}
};
/**
* 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) {
Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
// Barcode Scanner needs to insert a delay here because it does continuous focus,
// but this test app does not, so send the message immediately.
message.sendToTarget();
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 mCameraManager;
return cameraManager;
}
private CameraManager(Context context) {
mContext = context;
mCamera = null;
mPreviewing = false;
this.context = context;
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.
if (Integer.parseInt(Build.VERSION.SDK) <= Build.VERSION_CODES.CUPCAKE) {
useOneShotPreviewCallback = false;
} else {
useOneShotPreviewCallback = true;
}
}
// Throws IOException added to accommodate Android 1.5.
/**
* 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 String openDriver(SurfaceHolder holder, boolean getParameters) throws IOException {
String result = null;
if (mCamera == null) {
mCamera = Camera.open();
mCamera.setPreviewDisplay(holder);
if (camera == null) {
camera = Camera.open();
camera.setPreviewDisplay(holder);
if (!initialized) {
initialized = true;
getScreenResolution();
}
if (getParameters) {
result = collectCameraParameters();
}
@ -79,114 +155,141 @@ final class CameraManager {
return result;
}
/**
* Closes the camera driver if still in use.
*/
public void closeDriver() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
public void startPreview() {
if (mCamera != null && !mPreviewing) {
mCamera.startPreview();
mPreviewing = true;
}
}
public void stopPreview() {
if (mCamera != null && mPreviewing) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mPreviewHandler = null;
mAutoFocusHandler = null;
mPreviewing = false;
if (camera != null) {
camera.release();
camera = null;
}
}
/**
* A single preview frame will be returned to the handler supplied. The data will arrive as
* byte[] in the message.obj field, with width and height encoded as message.arg1 and
* message.arg2, respectively.
* Asks the camera hardware to begin drawing preview frames to the screen.
*/
public void startPreview() {
if (camera != null && !previewing) {
camera.startPreview();
previewing = true;
}
}
/**
* Tells the camera to stop drawing preview frames.
*/
public void stopPreview() {
if (camera != null && previewing) {
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
camera.stopPreview();
previewHandler = null;
autoFocusHandler = null;
previewing = false;
}
}
/**
* A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
* in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
* respectively.
*
* @param handler The handler to send the message to.
* @param message The what field of the message to be sent.
*/
public void requestPreviewFrame(Handler handler, int message) {
if (mCamera != null && mPreviewing) {
mPreviewHandler = handler;
mPreviewMessage = message;
mCamera.setPreviewCallback(previewCallback);
if (camera != null && previewing) {
previewHandler = handler;
previewMessage = message;
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 (mCamera != null && mPreviewing) {
mAutoFocusHandler = handler;
mAutoFocusMessage = message;
mCamera.autoFocus(autoFocusCallback);
if (camera != null && previewing) {
autoFocusHandler = handler;
autoFocusMessage = message;
camera.autoFocus(autoFocusCallback);
}
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. The actual captured image should be a bit larger than indicated because they might
* frame the shot too tightly. This target helps with alignment as well as forces the user to
* hold the device far enough away to ensure the image will be in focus.
* barcode. This target helps with alignment as well as forces the user to hold the device
* far enough away to ensure the image will be in focus.
*
* @return The rectangle to draw on screen in window coordinates.
*/
public Rect getFramingRect() {
if (mFramingRect == null) {
int width = mScreenResolution.x * 3 / 4;
int height = mScreenResolution.y * 3 / 4;
int leftOffset = (mScreenResolution.x - width) / 2;
int topOffset = (mScreenResolution.y - height) / 2;
mFramingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
if (framingRect == null) {
if (camera == null) {
return null;
}
return mFramingRect;
int width = cameraResolution.x * 3 / 4;
if (width < MIN_FRAME_WIDTH) {
width = MIN_FRAME_WIDTH;
} else if (width > MAX_FRAME_WIDTH) {
width = MAX_FRAME_WIDTH;
}
int height = cameraResolution.y * 3 / 4;
if (height < MIN_FRAME_HEIGHT) {
height = MIN_FRAME_HEIGHT;
} else if (height > MAX_FRAME_HEIGHT) {
height = MAX_FRAME_HEIGHT;
}
int leftOffset = (cameraResolution.x - width) / 2;
int topOffset = (cameraResolution.y - height) / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
Log.v(TAG, "Calculated framing rect: " + framingRect);
}
return framingRect;
}
/**
* Preview frames are delivered here, which we pass on to the registered handler. Make sure to
* clear the handler so it will only receive one message.
*/
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
if (mPreviewHandler != null) {
mCamera.setPreviewCallback(null);
Message message = mPreviewHandler.obtainMessage(mPreviewMessage,
mScreenResolution.x, mScreenResolution.y, data);
message.sendToTarget();
mPreviewHandler = null;
}
}
};
private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
if (mAutoFocusHandler != null) {
Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success);
// The Barcodes app needs to insert a delay here because it does continuous focus,
// but this test app does not, so send the message immediately.
message.sendToTarget();
mAutoFocusHandler = null;
}
}
};
/**
* 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 = mCamera.getParameters();
parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y);
mCamera.setParameters(parameters);
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 + '/' + previewFormatString);
// 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, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
// FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
parameters.set("flash-value", 2);
// This is the standard setting to turn the flash off that all devices should honor.
parameters.set("flash-mode", "off");
camera.setParameters(parameters);
}
private String collectCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters();
Camera.Parameters parameters = camera.getParameters();
String[] params = parameters.flatten().split(";");
StringBuffer result = new StringBuffer();
result.append("Default camera parameters:");
@ -199,12 +302,11 @@ final class CameraManager {
}
private Point getScreenResolution() {
if (mScreenResolution == null) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
mScreenResolution = new Point(display.getWidth(), display.getHeight());
if (screenResolution == null) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
screenResolution = new Point(display.getWidth(), display.getHeight());
}
return mScreenResolution;
return screenResolution;
}
}

View file

@ -69,7 +69,7 @@ public final class CameraTestActivity extends Activity implements SurfaceHolder.
protected void onResume() {
super.onResume();
if (mSaveThread == null && !mGetCameraParameters) {
mSaveThread = new SaveThread(this, CameraManager.get().getFramingRect());
mSaveThread = new SaveThread(this);
mSaveThread.start();
}
}

View file

@ -37,11 +37,9 @@ final class SaveThread extends Thread {
public Handler mHandler;
private final CameraTestActivity mActivity;
private final Rect mFramingRect;
SaveThread(CameraTestActivity activity, Rect framingRect) {
SaveThread(CameraTestActivity activity) {
mActivity = activity;
mFramingRect = framingRect;
}
@Override
@ -65,14 +63,15 @@ final class SaveThread extends Thread {
// Save the center rectangle of the Y channel as a greyscale PNG to the SD card.
private void save(byte[] data, int width, int height) {
int framingWidth = mFramingRect.width();
int framingHeight = mFramingRect.height();
final Rect framingRect = CameraManager.get().getFramingRect();
int framingWidth = framingRect.width();
int framingHeight = framingRect.height();
if (framingWidth > width || framingHeight > height) {
throw new IllegalArgumentException();
}
int leftOffset = mFramingRect.left;
int topOffset = mFramingRect.top;
int leftOffset = framingRect.left;
int topOffset = framingRect.top;
int[] colors = new int[framingWidth * framingHeight];
for (int y = 0; y < framingHeight; y++) {