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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.zxing.client.androidtest" package="com.google.zxing.client.androidtest"
android:versionName="1.11" android:versionName="1.12"
android:versionCode="3"> android:versionCode="4">
<!-- We require Cupcake (Android 1.5) or later. -->
<uses-sdk android:minSdkVersion="3"/> <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" <application android:label="@string/app_name"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:debuggable="true"> android:debuggable="true">

View file

@ -20,8 +20,10 @@ import android.content.Context;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.util.Log;
import android.view.Display; import android.view.Display;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.WindowManager; 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 * 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 * implementation encapsulates the steps needed to take preview-sized images, which are used for
* both preview and decoding. * both preview and decoding.
*
* @author dswitkin@google.com (Daniel Switkin)
*/ */
final class CameraManager { final class CameraManager {
private static final String TAG = "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 static CameraManager cameraManager;
private Camera mCamera; private Camera camera;
private final Context mContext; private final Context context;
private Point mScreenResolution; private Point screenResolution;
private Rect mFramingRect; private Point cameraResolution;
private Handler mPreviewHandler; private Rect framingRect;
private int mPreviewMessage; private Handler previewHandler;
private Handler mAutoFocusHandler; private int previewMessage;
private int mAutoFocusMessage; private Handler autoFocusHandler;
private boolean mPreviewing; 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) { * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
mCameraManager = new CameraManager(context); * clear the handler so it will only receive one message.
mCameraManager.getScreenResolution(); */
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() { public static CameraManager get() {
return mCameraManager; return cameraManager;
} }
private CameraManager(Context context) { private CameraManager(Context context) {
mContext = context; this.context = context;
mCamera = null; camera = null;
mPreviewing = false; 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 { public String openDriver(SurfaceHolder holder, boolean getParameters) throws IOException {
String result = null; String result = null;
if (mCamera == null) { if (camera == null) {
mCamera = Camera.open(); camera = Camera.open();
mCamera.setPreviewDisplay(holder); camera.setPreviewDisplay(holder);
if (!initialized) {
initialized = true;
getScreenResolution();
}
if (getParameters) { if (getParameters) {
result = collectCameraParameters(); result = collectCameraParameters();
} }
@ -79,114 +155,141 @@ final class CameraManager {
return result; return result;
} }
/**
* Closes the camera driver if still in use.
*/
public void closeDriver() { public void closeDriver() {
if (mCamera != null) { if (camera != null) {
mCamera.release(); camera.release();
mCamera = null; camera = 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;
} }
} }
/** /**
* A single preview frame will be returned to the handler supplied. The data will arrive as * Asks the camera hardware to begin drawing preview frames to the screen.
* byte[] in the message.obj field, with width and height encoded as message.arg1 and */
* message.arg2, respectively. 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 handler The handler to send the message to.
* @param message The what field of the message to be sent. * @param message The what field of the message to be sent.
*/ */
public void requestPreviewFrame(Handler handler, int message) { public void requestPreviewFrame(Handler handler, int message) {
if (mCamera != null && mPreviewing) { if (camera != null && previewing) {
mPreviewHandler = handler; previewHandler = handler;
mPreviewMessage = message; previewMessage = message;
mCamera.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) { public void requestAutoFocus(Handler handler, int message) {
if (mCamera != null && mPreviewing) { if (camera != null && previewing) {
mAutoFocusHandler = handler; autoFocusHandler = handler;
mAutoFocusMessage = message; autoFocusMessage = message;
mCamera.autoFocus(autoFocusCallback); camera.autoFocus(autoFocusCallback);
} }
} }
/** /**
* Calculates the framing rect which the UI should draw to show the user where to place the * 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 * barcode. This target helps with alignment as well as forces the user to hold the device
* frame the shot too tightly. This target helps with alignment as well as forces the user to * far enough away to ensure the image will be in focus.
* hold the device far enough away to ensure the image will be in focus.
* *
* @return The rectangle to draw on screen in window coordinates. * @return The rectangle to draw on screen in window coordinates.
*/ */
public Rect getFramingRect() { public Rect getFramingRect() {
if (mFramingRect == null) { if (framingRect == null) {
int width = mScreenResolution.x * 3 / 4; if (camera == null) {
int height = mScreenResolution.y * 3 / 4; return null;
int leftOffset = (mScreenResolution.x - width) / 2; }
int topOffset = (mScreenResolution.y - height) / 2; int width = cameraResolution.x * 3 / 4;
mFramingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); 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 mFramingRect; return framingRect;
} }
/** /**
* Preview frames are delivered here, which we pass on to the registered handler. Make sure to * Sets the camera up to take preview images which are used for both preview and decoding.
* clear the handler so it will only receive one message. * 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,
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { * and the planar Y can be used for barcode scanning without a copy in some cases.
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().
*/ */
private void setCameraParameters() { private void setCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters(); Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y); Camera.Size size = parameters.getPreviewSize();
mCamera.setParameters(parameters); 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() { private String collectCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters(); Camera.Parameters parameters = camera.getParameters();
String[] params = parameters.flatten().split(";"); String[] params = parameters.flatten().split(";");
StringBuffer result = new StringBuffer(); StringBuffer result = new StringBuffer();
result.append("Default camera parameters:"); result.append("Default camera parameters:");
@ -199,12 +302,11 @@ final class CameraManager {
} }
private Point getScreenResolution() { private Point getScreenResolution() {
if (mScreenResolution == null) { if (screenResolution == null) {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay(); Display display = manager.getDefaultDisplay();
mScreenResolution = new Point(display.getWidth(), display.getHeight()); 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() { protected void onResume() {
super.onResume(); super.onResume();
if (mSaveThread == null && !mGetCameraParameters) { if (mSaveThread == null && !mGetCameraParameters) {
mSaveThread = new SaveThread(this, CameraManager.get().getFramingRect()); mSaveThread = new SaveThread(this);
mSaveThread.start(); mSaveThread.start();
} }
} }

View file

@ -37,11 +37,9 @@ final class SaveThread extends Thread {
public Handler mHandler; public Handler mHandler;
private final CameraTestActivity mActivity; private final CameraTestActivity mActivity;
private final Rect mFramingRect;
SaveThread(CameraTestActivity activity, Rect framingRect) { SaveThread(CameraTestActivity activity) {
mActivity = activity; mActivity = activity;
mFramingRect = framingRect;
} }
@Override @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. // 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) { private void save(byte[] data, int width, int height) {
int framingWidth = mFramingRect.width(); final Rect framingRect = CameraManager.get().getFramingRect();
int framingHeight = mFramingRect.height(); int framingWidth = framingRect.width();
int framingHeight = framingRect.height();
if (framingWidth > width || framingHeight > height) { if (framingWidth > width || framingHeight > height) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
int leftOffset = mFramingRect.left; int leftOffset = framingRect.left;
int topOffset = mFramingRect.top; int topOffset = framingRect.top;
int[] colors = new int[framingWidth * framingHeight]; int[] colors = new int[framingWidth * framingHeight];
for (int y = 0; y < framingHeight; y++) { for (int y = 0; y < framingHeight; y++) {