diff --git a/fast-barcode-scanner-demo/src/main/java/dk/schaumburgit/fastbarcodescannerdemo/MainActivity.java b/fast-barcode-scanner-demo/src/main/java/dk/schaumburgit/fastbarcodescannerdemo/MainActivity.java
index fcb1ccc..f0b4bd1 100644
--- a/fast-barcode-scanner-demo/src/main/java/dk/schaumburgit/fastbarcodescannerdemo/MainActivity.java
+++ b/fast-barcode-scanner-demo/src/main/java/dk/schaumburgit/fastbarcodescannerdemo/MainActivity.java
@@ -89,14 +89,13 @@ private void startScan() {
mScanner = new FastBarcodeScanner(this, (TextureView)null);
//mScanner = new FastBarcodeScanner(this, mSurfaceView);
//mScanner.setScanningStateListener(this);
- mScanner.setBarcodeListener(this);
}
Button startButton = (Button)findViewById(R.id.start);
Button stopButton = (Button)findViewById(R.id.button3);
startButton.setEnabled(false);
- mScanner.StartScan();
+ mScanner.StartScan(this, null);
stopButton.setEnabled(true);
}
@@ -149,6 +148,11 @@ public void run() {
}
}
+ @Override
+ public void onError(Exception error) {
+
+ }
+
private static final int REQUEST_CAMERA_PERMISSION = 1;
private static final String FRAGMENT_DIALOG = "dialog";
diff --git a/fast-barcode-scanner/src/main/java/dk/schaumburgit/fastbarcodescanner/FastBarcodeScanner.java b/fast-barcode-scanner/src/main/java/dk/schaumburgit/fastbarcodescanner/FastBarcodeScanner.java
index d925c5d..39e8c52 100644
--- a/fast-barcode-scanner/src/main/java/dk/schaumburgit/fastbarcodescanner/FastBarcodeScanner.java
+++ b/fast-barcode-scanner/src/main/java/dk/schaumburgit/fastbarcodescanner/FastBarcodeScanner.java
@@ -17,27 +17,47 @@
import android.annotation.TargetApi;
import android.app.Activity;
-import android.hardware.camera2.CaptureResult;
-import android.media.Image;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.util.Log;
import android.view.SurfaceView;
import android.view.TextureView;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+
import java.security.InvalidParameterException;
import java.sql.Timestamp;
import java.util.Date;
+import java.util.EnumSet;
import dk.schaumburgit.stillsequencecamera.IStillSequenceCamera;
import dk.schaumburgit.stillsequencecamera.camera.StillSequenceCamera;
import dk.schaumburgit.stillsequencecamera.camera2.StillSequenceCamera2;
import dk.schaumburgit.trackingbarcodescanner.TrackingBarcodeScanner;
+/**
+ * The FastBarcodeScanner captures images from your front-facing camera at the fastest
+ * possible rate, scans them for barcodes and reports any changes to the caller
+ * via a listener callback.
+ *
+ * The image capture is done unobtrusively without any visible UI, using a background thread.
+ *
+ * For newer Android versions (Lollipop and later), the new, faster Camera2 API is supported.
+ * For older versions, FastBarcodeScanner falls back to using the older, slower camera API.
+ *
+ * When the Camera2 API is available, the FastBarcodeScanner can be created with a TextureView
+ * if on-screen preview is desired, or without for headless operation.
+ *
+ * For older Android versions, the FastBarcodeScanner *must* be created with a SurfaceView,
+ * and the SurfaceView *must* be visible on-screen. Setting the SurfaceView to 1x1 pixel
+ * will however make it effectively invisible.
+ *
+ * Regardless of Android version, the FastbarcodeScanner *must* be supplied with a reference
+ * to the current Activity (used for accessing e.g. the camera, and other system resources).
+ *
+ */
public class FastBarcodeScanner
- implements IStillSequenceCamera.OnImageAvailableListener//, IStillSequenceCamera.CameraStateChangeListener
{
/**
* Tag for the {@link Log}.
@@ -45,6 +65,10 @@ public class FastBarcodeScanner
private static final String TAG = "FastBarcodeScanner";
private Activity mActivity;
+ private Handler mBarcodeListenerHandler;
+ private HandlerThread mProcessingThread;
+ private Handler mProcessingHandler;
+
private Activity getActivity() {
return mActivity;
}
@@ -52,6 +76,26 @@ private Activity getActivity() {
private IStillSequenceCamera mImageSource;
private TrackingBarcodeScanner mBarcodeFinder;
+ /**
+ * Creates a headless FastBarcodeScanner (i.e. one without any UI)
+ *
+ * FastBarcodeScanner instances created using this constructor will use
+ * the new, efficient Camera2 API for controlling the camera.
+ *
+ * This boosts performance by a factor 5x - but it only works on Android
+ * Lollipop (API version 21) and later.
+ *
+ * As an alternative, consider using the #FastBarcodeScanner constructor
+ * which will create a FastBarcodeScanner working on older versions of
+ * Android too - albeit much less efficiently.
+ * @param activity Non null
+ */
+ @TargetApi(21)
+ public FastBarcodeScanner(Activity activity)
+ {
+ this(activity, (TextureView)null);
+ }
+
/**
* Creates a FastBarcodeScanner using the given TextureView for preview.
*
@@ -74,12 +118,19 @@ public FastBarcodeScanner(Activity activity, TextureView textureView) {
this.mActivity = activity;
this.mBarcodeFinder = new TrackingBarcodeScanner();
- this.mImageSource = new StillSequenceCamera2(activity, textureView, mBarcodeFinder.GetPreferredFormats(), 1024*768);
+ this.mImageSource = new StillSequenceCamera2(activity, textureView, mBarcodeFinder.getPreferredImageFormats(), 1024*768);
this.mImageSource.setup();
}
/**
+ * Creates a FastBarcodeScanner using the deprecated Camera API supported
+ * on Android versions prior to Lollipop (API level lower than 21).
*
+ * The created FastBarcodeScanner will display preview output in the supplied
+ * SurfaceView. This parameter *must* be non-null, and the referenced SurfaceView
+ * *must* be displayed on-screen, with a minimum size of 1x1 pixels. This is a
+ * non-negotiable requirement from the camera API (upgrade to API level 21 for
+ * true headless operation).
* @param activity Non-null
* @param surfaceView Non-null
*/
@@ -96,23 +147,90 @@ public FastBarcodeScanner(Activity activity, SurfaceView surfaceView) {
this.mImageSource.setup();
}
- public void StartScan()
+ /**
+ * Starts scanning on a background thread, calling the supplied listener whenever
+ * there's a *change* in the barcode seen (i.e. if 200 consecutive images contain
+ * the same barcode, only the first will generate a callback).
+ *
+ * "No barcode" is signalled with a null value via the callback.
+ *
+ * Example: After StartScan is called, the first 20 images contain no barcode, the
+ * next 200 have barcode A, the next 20 have nothing. This will generate the
+ * following callbacks:
+ *
+ * Frame#1: onBarcodeAvailable(null)
+ * Frame#21: onBarcodeAvailable("A")
+ * Frame#221: onBarcodeAvailable(null)
+ *
+ * @param listener A reference to the listener receiving the above mentioned callbacks
+ * @param callbackHandler Identifies the thread that the callbacks will be made on.
+ * Null means "use the thread that called StartScan()".
+ */
+ public void StartScan(BarcodeDetectedListener listener, Handler callbackHandler)
{
- mImageSource.start(this);
+ mBarcodeListenerHandler = callbackHandler;
+ if (mBarcodeListenerHandler == null)
+ mBarcodeListenerHandler = new Handler();
+ mBarcodeListener = listener;
+
+ mProcessingThread = new HandlerThread("FastBarcodeScanner processing thread");
+ mProcessingThread.start();
+ mProcessingHandler = new Handler(mProcessingThread.getLooper());
+
+ mImageSource.start(
+ new IStillSequenceCamera.OnImageAvailableListener()
+ {
+
+ @Override
+ public void onImageAvailable(int format, byte[] data, int width, int height) {
+ processImage(format, data, width, height);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ FastBarcodeScanner.this.onError(error);
+ }
+
+ },
+ mProcessingHandler
+ );
}
+ /**
+ * Stops the scanning process started by StartScan() and frees any shared system resources
+ * (e.g. the camera). StartScan() can always be called to restart.
+ *
+ * StopScan() and StartScan() are thus well suited for use from the onPause() and onResume()
+ * handlers of a calling application.
+ */
public void StopScan()
{
mImageSource.stop();
+
+ if (mProcessingThread != null) {
+ try {
+ mProcessingThread.quitSafely();
+ mProcessingThread.join();
+ mProcessingThread = null;
+ mProcessingHandler = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ mBarcodeListener = null;
+ mBarcodeListenerHandler = null;
}
+ /**
+ * Disposes irrevocably of all resources. This instance cannot be used after close() is called.
+ */
public void close()
{
this.mImageSource.close();
}
- @Override
- public void onImageAvailable(int format, byte[] bytes, int width, int height) {
+ private void processImage(int format, byte[] bytes, int width, int height) {
// Get the image data (we requested JPEG) into a byte buffer:
Date first = new Date();
@@ -120,11 +238,11 @@ public void onImageAvailable(int format, byte[] bytes, int width, int height) {
Date second = new Date();
try {
String newBarcode = mBarcodeFinder.find(format, width, height, bytes);
- Log.i(TAG, "Found barcode: " + newBarcode);
+ Log.v(TAG, "Found barcode: " + newBarcode);
Date third = new Date();
// Tell the world:
- callBarcodeListener(newBarcode);
+ onBarcodeFound(newBarcode);
Date fourth = new Date();
if (false)
Log.v(
@@ -154,44 +272,19 @@ public void onImageAvailable(int format, byte[] bytes, int width, int height) {
}
}
- private int nImagesProcessed = 0;
- private void saveImage(byte[] bytes) {
- File dir = getActivity().getExternalFilesDir(null);
- nImagesProcessed++;
- File saveAs = new File(dir, "qr" + nImagesProcessed + ".jpg");
-
- FileOutputStream output = null;
- try {
- output = new FileOutputStream(saveAs);
- output.write(bytes);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (null != output) {
- try {
- output.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
//*********************************************************************
//* Managing Barcode events:
//* ========================
+ //* Only bother the listener if a *new* barcode is detected.
//*
+ //* Furthermore,
//*********************************************************************
private BarcodeDetectedListener mBarcodeListener = null;
- public void setBarcodeListener(BarcodeDetectedListener listener)
- {
- mBarcodeListener = listener;
- }
-
private String mLastReportedBarcode = null;
private int mNoBarcodeCount = 0;
- private void callBarcodeListener(String barcode)
+ private final int NO_BARCODE_IGNORE_LIMIT = 5;
+ private void onBarcodeFound(String barcode)
{
//mBarcodeListener.onBarcodeAvailable(barcode);
//Log.d(TAG, "Scanned " + barcode);
@@ -201,10 +294,9 @@ private void callBarcodeListener(String barcode)
if (barcode == null)
{
mNoBarcodeCount++;
- if (mLastReportedBarcode != null && mNoBarcodeCount >= 5)
- {
+ if (mLastReportedBarcode != null && mNoBarcodeCount >= NO_BARCODE_IGNORE_LIMIT) {
mLastReportedBarcode = null;
- mBarcodeListener.onBarcodeAvailable(mLastReportedBarcode);
+ _onBarcode(mLastReportedBarcode);
}
}
else
@@ -213,12 +305,32 @@ private void callBarcodeListener(String barcode)
if (!barcode.equals(mLastReportedBarcode))
{
mLastReportedBarcode = barcode;
- if (mBarcodeListener != null)
- mBarcodeListener.onBarcodeAvailable(mLastReportedBarcode);
+ _onBarcode(mLastReportedBarcode);
}
}
}
+ private void _onBarcode(final String barcode)
+ {
+ if (mBarcodeListener != null) {
+ mBarcodeListenerHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mBarcodeListener.onBarcodeAvailable(barcode);
+ }
+ }
+ );
+ }
+
+ }
+
+ private void onError(Exception error)
+ {
+ if (mBarcodeListener != null)
+ mBarcodeListener.onError(error);
+ }
+
//*********************************************************************
//*********************************************************************
@@ -232,7 +344,7 @@ private void callBarcodeListener(String barcode)
*
*
* When no barcodes have been detected in 3 consecutive frames, onBarcodeAvailable
- * with a null barcode parameter.
+ * is called with a null barcode parameter ().
*
*/
public interface BarcodeDetectedListener {
@@ -242,6 +354,44 @@ public interface BarcodeDetectedListener {
* @param barcode the barcode detected.
*/
void onBarcodeAvailable(String barcode);
+
+ void onError(Exception error);
+ }
+
+ //*********************************************************************
+ // Pass-through properties for the barcode scanner
+ //*********************************************************************
+ public double getRelativeTrackingMargin() {
+ return mBarcodeFinder.getRelativeTrackingMargin();
}
+
+ public void setRelativeTrackingMargin(double relativeTrackingMargin) {
+ mBarcodeFinder.setRelativeTrackingMargin(relativeTrackingMargin);
+ }
+
+ public int getNoHitsBeforeTrackingLoss() {
+ return mBarcodeFinder.getNoHitsBeforeTrackingLoss();
+ }
+
+ public void setNoHitsBeforeTrackingLoss(int noHitsBeforeTrackingLoss) {
+ mBarcodeFinder.setNoHitsBeforeTrackingLoss(noHitsBeforeTrackingLoss);
+ }
+
+ public EnumSet getPossibleBarcodeFormats() {
+ return mBarcodeFinder.getPossibleBarcodeFormats();
+ }
+
+ public void setPossibleBarcodeFormats(EnumSet possibleFormats) {
+ mBarcodeFinder.setPossibleBarcodeFormats(possibleFormats);
+ }
+
+ public boolean isUseTracking() {
+ return mBarcodeFinder.isUseTracking();
+ }
+
+ public void setUseTracking(boolean useTracking) {
+ mBarcodeFinder.setUseTracking(useTracking);
+ }
+
}
diff --git a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/IStillSequenceCamera.java b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/IStillSequenceCamera.java
index 32c87ff..582474a 100644
--- a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/IStillSequenceCamera.java
+++ b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/IStillSequenceCamera.java
@@ -2,20 +2,24 @@
import android.app.Activity;
import android.media.Image;
+import android.os.Handler;
import android.view.TextureView;
+import java.io.IOException;
+
/**
* Created by Thomas Schaumburg on 21-11-2015.
*/
public interface IStillSequenceCamera {
void setup();
- void start(OnImageAvailableListener listener);
+ void start(OnImageAvailableListener listener, Handler callbackHandler);
void stop();
void close();
public interface OnImageAvailableListener
{
void onImageAvailable(int format, byte[] data, int width, int height);
+ void onError(Exception error);
}
}
diff --git a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera/StillSequenceCamera.java b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera/StillSequenceCamera.java
index 0791475..e183416 100644
--- a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera/StillSequenceCamera.java
+++ b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera/StillSequenceCamera.java
@@ -6,6 +6,7 @@
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PictureCallback;
import android.media.Image;
+import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
@@ -19,20 +20,42 @@
* Created by Thomas Schaumburg on 08-12-2015.
*/
public class StillSequenceCamera implements IStillSequenceCamera {
+ private static final String TAG = "StillSequenceCamera";
private int mCameraId = -1;
private Camera mCamera;
private final Activity mActivity;
private final SurfaceView mPreview;
private IStillSequenceCamera.OnImageAvailableListener mImageListener = null;
+ private Handler mCallbackHandler;
+ private final static int CLOSED = 0;
+ private final static int INITIALIZED = 1;
+ private final static int CAPTURING = 2;
+ private int mState = CLOSED;
public StillSequenceCamera(Activity activity, SurfaceView preview)
{
mActivity = activity;
mPreview = preview;
+ mState = CLOSED;
}
- public void setup() {
- if (mCamera != null)
+ /**
+ * Selects a back-facing camera, opens it and starts focusing.
+ *
+ * The #start() method can be called immediately when this method returns
+ *
+ * If setup() returns successfully, the StillSequenceCamera enters the INITIALIZED state.
+ *
+ * @throws IllegalStateException if the StillSequenceCamera is in any but the CLOSED state
+ * @throws UnsupportedOperationException if no back-facing camera is available
+ * @throws RuntimeException if opening the camera fails (for example, if the
+ * camera is in use by another process or device policy manager has
+ * disabled the camera).
+ */
+ public void setup()
+ throws UnsupportedOperationException, IllegalStateException
+ {
+ if (mCamera != null || mState != CLOSED)
throw new IllegalStateException("StillSequenceCamera.setup() can only be called on a new instance");
// Open a camera:
@@ -58,10 +81,32 @@ public void setup() {
pars.setPictureSize(1024, 768);
pars.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
mCamera.setParameters(pars);
+
+ mState = INITIALIZED;
}
- public void start(OnImageAvailableListener listener) {
+ /**
+ * Starts the preview (displaying it in the #SurfaceView provided in the constructor),
+ * and starts taking pictures as rapidly as possible.
+ *
+ * This continues until #stop() is called.
+ *
+ * If start() returns successfully, the StillSequenceCamera enters the CAPTURING state.
+ *
+ * @param listener Every time a picture is taken, this callback interface is called.
+ *
+ * @throws IllegalStateException if the StillSequenceCamera is in any but the INITIALIZED state
+ */
+ public void start(OnImageAvailableListener listener, Handler callbackHandler)
+ throws IllegalStateException
+ {
+ if (mState != INITIALIZED)
+ throw new IllegalStateException("StillSequenceCamera.start() can only be called in the INITIALIZED state");
+
mImageListener = listener;
+ mCallbackHandler = callbackHandler;
+ if (mCallbackHandler == null)
+ mCallbackHandler = new Handler();
if (mPreview.getHolder().getSurface() != null) {
try {
@@ -119,18 +164,63 @@ public void surfaceDestroyed(SurfaceHolder holder) {
);
// deprecated setting, but required on Android versions prior to 3.0
mPreview.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ mState = CAPTURING;
}
+ /**
+ * Stops the preview, and stops the capture of still images.
+ *
+ * If stop() returns successfully, the StillSequenceCamera enters the STOPPED state.
+ *
+ * @throws IllegalStateException if stop is called in any but the STARTED state
+ */
public void stop()
+ throws IllegalStateException
{
+ if (mState == CLOSED)
+ return;
+
+ if (mState != CAPTURING)
+ throw new IllegalStateException("StillSequenceCamera.stop() can only be called in the STARTED state");
+
mImageListener = null;
+ mCallbackHandler = null;
stopTakingPictures();
+
+ try {
+ mCamera.stopPreview();
+ } catch (Exception e) {
+ // ignore: tried to stop a non-existent preview
+ }
+
+ mState = INITIALIZED;
+ }
+
+ public void close() {
+ if (mState == CLOSED)
+ return;
+
+ if (mState == CAPTURING)
+ stop();
+
+ if (mState != INITIALIZED)
+ throw new IllegalStateException("StillSequenceCamera.stop() can only be called after start()");
+
+ mContinueTakingPictures = false;
+ if (mCamera != null) {
+ mCamera.release();
+ mCamera = null;
+ }
+ mCameraId = -1;
+ mImageListener = null;
+
+ mState = CLOSED;
}
private boolean mContinueTakingPictures = false;
- private static final String TAG = "StillSequenceCamera";
private void startTakingPictures()
+ throws IllegalStateException
{
if (mContinueTakingPictures)
return;
@@ -142,23 +232,32 @@ private void startTakingPictures()
takePicture();
}
- private void stopTakingPictures() {
+ private void stopTakingPictures()
+ {
mContinueTakingPictures = false;
}
- private void takePicture() {
+ private void takePicture()
+ {
mCamera.takePicture(
null,
null,
new PictureCallback() {
@Override
- public void onPictureTaken(byte[] jpegData, Camera camera) {
- Camera.Size size = camera.getParameters().getPictureSize();
+ public void onPictureTaken(final byte[] jpegData, Camera camera) {
+ final Camera.Size size = camera.getParameters().getPictureSize();
Log.i(TAG, "Captured JPEG " + jpegData.length + " bytes (" + size.width + "x" + size.height + ")");
if (mImageListener != null) {
- mImageListener.onImageAvailable(ImageFormat.JPEG, jpegData, size.width, size.height);
+ mCallbackHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mImageListener.onImageAvailable(ImageFormat.JPEG, jpegData, size.width, size.height);
+ }
+ }
+ );
}
if (mContinueTakingPictures) {
mCamera.startPreview();
@@ -168,15 +267,4 @@ public void onPictureTaken(byte[] jpegData, Camera camera) {
}
);
}
-
- public void close() {
- mContinueTakingPictures = false;
- if (mCamera != null) {
- mCamera.release();
- mCamera = null;
- }
- mCameraId = -1;
- mImageListener = null;
- }
-
}
diff --git a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/FocusManager.java b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/FocusManager.java
index e01a15f..366138a 100644
--- a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/FocusManager.java
+++ b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/FocusManager.java
@@ -20,6 +20,7 @@
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
+import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
@@ -50,6 +51,7 @@ public FocusManager(Activity activity, TextureView textureView)
private TextureView mTextureView;
private Size mPreviewSize;
private ImageReader mPreviewImageReader;
+ private FocusingStateMachine mStateMachine;
/**
* Sets up member variables related to the on-screen preview (if any).
@@ -109,10 +111,8 @@ public void onSurfaceTextureUpdated(SurfaceTexture texture) {
if (mTextureView.isAvailable())
configureTransform();
-
-
} else {
- mPreviewImageReader = ImageReader.newInstance(320, 240, ImageFormat.YUV_420_888, 2); //fps * 10 min
+ mPreviewImageReader = ImageReader.newInstance(320, 240, ImageFormat.YUV_420_888, 2);
mPreviewImageReader.setOnImageAvailableListener(
new ImageReader.OnImageAvailableListener() {
@Override
@@ -124,8 +124,6 @@ public void onImageAvailable(ImageReader reader) {
}, null);
mPreviewSurface = mPreviewImageReader.getSurface();
}
-
- return;
} catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
@@ -146,10 +144,11 @@ public void onImageAvailable(ImageReader reader) {
}
public void close() {
- }
+ if (mTextureView != null)
+ mTextureView.setSurfaceTextureListener(null);
- public Surface getSurface() {
- return mPreviewSurface;
+ if (mPreviewImageReader != null)
+ mPreviewImageReader.setOnImageAvailableListener(null, null);
}
/**
@@ -244,36 +243,12 @@ private void configureTransform() {
}
}
-
-
-
-
-
-
-
-
-
- /*
- * Camera states:
- */
- private static final int STATE_IDLE = 0;
- private static final int STATE_WAITING_LOCK = 1;
- private static final int STATE_WAITING_PRECAPTURE = 2;
- private static final int STATE_WAITING_NON_PRECAPTURE = 3;
- private static final int STATE_PICTURE_TAKEN = 4;
- private int mState = STATE_IDLE;
-
- /**
- * {@link CaptureRequest.Builder} for the camera preview
- */
- private CaptureRequest.Builder mPreviewRequestBuilder;
-
- /**
- * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
- */
- private CaptureRequest mPreviewRequest;
private Surface mPreviewSurface = null;
+ public Surface getSurface() {
+ return mPreviewSurface;
+ }
+
//*********************************************************************
//* The AF/AE state machine:
//* =========================
@@ -302,73 +277,33 @@ private void configureTransform() {
public static interface FocusListener
{
public void focusLocked();
+ public void error(Exception error);
}
private CameraCaptureSession mCameraCaptureSession;
- public void start(CameraCaptureSession cameraCaptureSession, Handler cameraHandler, FocusListener listener)
+
+ /**
+ *
+ * @param cameraCaptureSession
+ * @param callbackHandler the handler on which the listener should be invoked, or
+ * {@code null} to use the current thread's {@link android.os.Looper
+ * looper}.
+ * @param listener
+ */
+ public void start(CameraCaptureSession cameraCaptureSession, Handler callbackHandler, FocusListener listener)
{
mCameraCaptureSession = cameraCaptureSession;
// When the session is ready, we start displaying the preview.
try {
Log.i(TAG, "StartFocusing");
- CameraDevice camera = cameraCaptureSession.getDevice();
-
- // We set up a CaptureRequest.Builder with the output Surface.
- mPreviewRequestBuilder
- = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- mPreviewRequestBuilder.addTarget(getSurface());
-
- // Auto focus should be continuous for camera preview.
- mPreviewRequestBuilder.set(
- CaptureRequest.CONTROL_AF_MODE,
- CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
- );
-
- // Automatic exposure control, NO FLASH
- mPreviewRequestBuilder.set(
- CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON
- );
-
- FocusingStateMachine stateMachine = new FocusingStateMachine(cameraCaptureSession, cameraHandler, listener);
-
- // Now, we start streaming frames from the camera:
- mPreviewRequest = mPreviewRequestBuilder.build();
- cameraCaptureSession.setRepeatingRequest(
- mPreviewRequest,
- stateMachine,
- cameraHandler
- );
-
- // This is how to tell the camera to attempt a focus lock:
- mPreviewRequestBuilder.set(
- CaptureRequest.CONTROL_AF_TRIGGER,
- CameraMetadata.CONTROL_AF_TRIGGER_START
- );
-
- // Tell the state machine to wait for the focus lock.
- mState = STATE_WAITING_LOCK;
-
- // Send a single request to the camera with the focus-lock instruction:
- cameraCaptureSession.capture(
- mPreviewRequestBuilder.build(),
- stateMachine,
- cameraHandler
- );
-// } catch (CameraAccessException e) {
-// e.printStackTrace();
+ mStateMachine = new FocusingStateMachine(cameraCaptureSession, callbackHandler, listener);
+ mStateMachine.start(mPreviewSurface);
} catch (Exception e) {
if (cameraCaptureSession != null) {
- try {
- cameraCaptureSession.stopRepeating();
- } catch (CameraAccessException cae) {
- }
+ stop();//cameraCaptureSession.stopRepeating();
}
- mPreviewRequestBuilder = null;
- mPreviewRequest = null;
//mPreviewImageReader.setOnImageAvailableListener(null, null);
mPreviewImageReader = null;
- mState = STATE_IDLE;
e.printStackTrace();
}
}
@@ -377,8 +312,115 @@ public void start(CameraCaptureSession cameraCaptureSession, Handler cameraHandl
* Unlock the focus. This method should be called when still image capture sequence is
* finished.
*/
- public void stop(Handler cameraHandler) {
+ public void stop() {
try {
+ mStateMachine.close();
+ mStateMachine = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private class FocusingStateMachine extends CameraCaptureSession.CaptureCallback {
+ private final FocusListener mListener;
+ private final Handler mCallbackHandler;
+ private final CameraCaptureSession mCaptureSession;
+
+ private HandlerThread mFocusingStateMachineThread;
+ private Handler mFocusingStateMachineHandler;
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+
+ /*
+ * Focusing states:
+ */
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_WAITING_LOCK = 1;
+ private static final int STATE_WAITING_PRECAPTURE = 2;
+ private static final int STATE_WAITING_NON_PRECAPTURE = 3;
+ private static final int STATE_PICTURE_TAKEN = 4;
+ private int mState = STATE_IDLE;
+
+ FocusingStateMachine(CameraCaptureSession cameraCaptureSession, Handler callbackHandler, FocusListener listener) {
+ mState = STATE_IDLE;
+ mCaptureSession = cameraCaptureSession;
+ mListener = listener;
+ if (callbackHandler != null) {
+ mCallbackHandler = callbackHandler;
+ } else {
+ mCallbackHandler = new Handler();
+ }
+
+ mFocusingStateMachineThread = new HandlerThread("Camera Focus Background");
+ mFocusingStateMachineThread.start();
+ mFocusingStateMachineHandler = new Handler(mFocusingStateMachineThread.getLooper());
+
+ // We set up a CaptureRequest.Builder with the output Surface.
+ try {
+ mPreviewRequestBuilder
+ = mCaptureSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ }
+ catch (CameraAccessException cae)
+ {
+ onError(cae);
+ }
+ }
+
+ void start(Surface previewSurface)
+ {
+ if (mPreviewRequestBuilder == null)
+ throw new IllegalStateException("There was an error when setting up the FocusManager.FocusingStateMachine - did you ignore an exception from the constructor....?");
+
+ try {
+ mPreviewRequestBuilder.addTarget(previewSurface);
+
+ // Auto focus should be continuous for camera preview.
+ mPreviewRequestBuilder.set(
+ CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ );
+
+ // Automatic exposure control, NO FLASH
+ mPreviewRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON
+ );
+
+ // Now, we start streaming frames from the camera:
+ mCaptureSession.setRepeatingRequest(
+ mPreviewRequestBuilder.build(),
+ mStateMachine,
+ mFocusingStateMachineHandler
+ );
+
+ // This is how to tell the camera to attempt a focus lock:
+ mPreviewRequestBuilder.set(
+ CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_START
+ );
+
+ // Tell the state machine to wait for the focus lock.
+ mState = STATE_WAITING_LOCK;
+
+ // Send a single request to the camera with the focus-lock instruction:
+ mCaptureSession.capture(
+ mPreviewRequestBuilder.build(),
+ mStateMachine,
+ mFocusingStateMachineHandler
+ );
+ }
+ catch (CameraAccessException cae)
+ {
+ onError(cae);
+ }
+ catch (Exception e)
+ {
+ onError(e);
+ }
+ }
+
+ public void close()
+ {
+ mState = STATE_IDLE;
if (mPreviewRequestBuilder != null) {
mPreviewRequestBuilder.set(
CaptureRequest.CONTROL_AF_TRIGGER,
@@ -390,21 +432,25 @@ public void stop(Handler cameraHandler) {
);
if (mCameraCaptureSession != null) {
- // Send request to camera:
- mCameraCaptureSession.capture(
- mPreviewRequestBuilder.build(),
- null, // => no callbacks
- cameraHandler
- );
-
- mCameraCaptureSession.stopRepeating();
-
- //// TODO: is this used at all...?
- //mCameraCaptureSession.setRepeatingRequest(
- // mPreviewRequest,
- // null, // => no callbacks
- // cameraHandler
- //);
+ try {
+ // Send request to camera:
+ mCameraCaptureSession.capture(
+ mPreviewRequestBuilder.build(),
+ null, // => no callbacks
+ null // no callbacks => callback handler irrelevant
+ );
+
+ mCameraCaptureSession.stopRepeating();
+
+ //// TODO: is this used at all...?
+ //mCameraCaptureSession.setRepeatingRequest(
+ // mPreviewRequest,
+ // null, // => no callbacks
+ // cameraHandler
+ //);
+ } catch (CameraAccessException cae) {
+ onError(cae);
+ }
mCameraCaptureSession = null;
}
@@ -412,21 +458,16 @@ public void stop(Handler cameraHandler) {
mPreviewRequestBuilder = null;
}
- mState = STATE_IDLE;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- private class FocusingStateMachine extends CameraCaptureSession.CaptureCallback {
- private FocusListener mListener = null;
- private Handler mCameraHandler = null;
- private CameraCaptureSession mCaptureSession = null;
-
- FocusingStateMachine(CameraCaptureSession cameraCaptureSession, Handler cameraHandler, FocusListener listener) {
- mCaptureSession = cameraCaptureSession;
- mListener = listener;
- mCameraHandler = cameraHandler;
+ if (mFocusingStateMachineThread != null) {
+ try {
+ mFocusingStateMachineThread.quitSafely();
+ mFocusingStateMachineThread.join();
+ mFocusingStateMachineThread = null;
+ mFocusingStateMachineHandler = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
}
@Override
@@ -464,7 +505,7 @@ private void process(CaptureResult result) {
onFocusLocked();
mState = STATE_PICTURE_TAKEN;
} else {
- runPrecaptureSequence(mCaptureSession, mCameraHandler);
+ runPrecaptureSequence();
}
}
break;
@@ -494,7 +535,7 @@ private void process(CaptureResult result) {
/**
* Run the precapture sequence for capturing a still image.
*/
- private void runPrecaptureSequence(CameraCaptureSession captureSession, Handler cameraHandler) {
+ private void runPrecaptureSequence() {
try {
// This is how to tell the camera to trigger.
mPreviewRequestBuilder.set(
@@ -503,10 +544,10 @@ private void runPrecaptureSequence(CameraCaptureSession captureSession, Handler
);
// Tell #mCaptureCallback to wait for the precapture sequence to be set.
mState = STATE_WAITING_PRECAPTURE;
- captureSession.capture(
+ mCaptureSession.capture(
mPreviewRequestBuilder.build(),
null, // the repeating request will generate the necessary callbacks
- cameraHandler
+ null // no callback => no handler needed
);
} catch (CameraAccessException e) {
e.printStackTrace();
@@ -515,11 +556,40 @@ private void runPrecaptureSequence(CameraCaptureSession captureSession, Handler
private void onFocusLocked()
{
- Log.i(TAG, "lock");
+ Log.i(TAG, "focus lock");
try {
mCaptureSession.stopRepeating();
- if (mListener != null)
- mListener.focusLocked();
+ if (mListener != null) {
+ mCallbackHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mListener.focusLocked();
+ }
+ }
+ );
+ }
+ } catch (CameraAccessException e)
+ {
+ throw new UnsupportedOperationException("Camera access required");
+ }
+ }
+
+ private void onError(final Exception error)
+ {
+ Log.e(TAG, "focusing error");
+ try {
+ mCaptureSession.stopRepeating();
+ if (mListener != null) {
+ mCallbackHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mListener.error(error);
+ }
+ }
+ );
+ }
} catch (CameraAccessException e)
{
throw new UnsupportedOperationException("Camera access required");
diff --git a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/StillSequenceCamera2.java b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/StillSequenceCamera2.java
index 8cf6418..c3b3d97 100644
--- a/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/StillSequenceCamera2.java
+++ b/still-sequence-camera/src/main/java/dk/schaumburgit/stillsequencecamera/camera2/StillSequenceCamera2.java
@@ -46,20 +46,49 @@ public class StillSequenceCamera2 implements IStillSequenceCamera {
private FocusManager mFocusManager;
private CaptureManager mImageCapture;
+ private final static int CLOSED = 0;
+ private final static int INITIALIZED = 1;
+ private final static int CAPTURING = 2;
+ private final static int CHANGING = 3;
+ private final static int FOCUSING = 4;
+ private final static int FAILED = 5;
+ private final static int ERROR = 6;
+ private int mState = CLOSED;
+
/**
+ * Creates a headless #StillSequenceCamera2
*
- * @param activity
+ * @param activity The activity associated with the calling app.
*/
public StillSequenceCamera2(Activity activity)
{
this(activity, null, new int[] {ImageFormat.JPEG}, 1024*768);
}
+ /**
+ * Creates a headless #StillSequenceCamera2
+ *
+ * @param activity The activity associated with the calling app.
+ * @param prioritizedImageFormats The preferred formats to capture images in
+ * (see #ImageFormat for values)
+ * @param minPixels The preferred minimum number of pixels in the captured images
+ * (i.e. width*height)
+ */
public StillSequenceCamera2(Activity activity, int[] prioritizedImageFormats, int minPixels)
{
this(activity, null, prioritizedImageFormats, minPixels);
}
+ /**
+ * Creates a #StillSequenceCamera2 with a preview
+ *
+ * @param activity The activity associated with the calling app.
+ * @param prioritizedImageFormats The preferred formats to capture images in
+ * (see #ImageFormat for values)
+ * @param minPixels The preferred minimum number of pixels in the captured images
+ * (i.e. width*height)
+ * @param textureView The #TextureView to display the preview in (use null for headless scanning)
+ */
public StillSequenceCamera2(Activity activity, TextureView textureView, int[] prioritizedImageFormats, int minPixels)
{
if (activity==null)
@@ -72,10 +101,25 @@ public StillSequenceCamera2(Activity activity, TextureView textureView, int[] pr
mFocusManager = new FocusManager(activity, textureView);
mImageCapture = new CaptureManager(activity, prioritizedImageFormats, minPixels);
+
+ mState = CLOSED;
}
+ /**
+ * Chooses a back-facing camera satisfying the requirements from the constructor (i.e. format
+ * and resolution).
+ *
+ *
+ *
+ * @throws IllegalStateException if the StillSequenceCamera2 is in any but the CLOSED state.
+ */
@Override
- public void setup() {
+ public void setup()
+ throws IllegalStateException
+ {
+ if (mState != CLOSED)
+ throw new IllegalStateException("StillSequenceCamera2.setup() can only be called in the CLOSED state");
+
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
@@ -91,20 +135,10 @@ public void setup() {
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
-
- map = characteristics.get(
- CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- if (map == null) {
- continue;
- }
-
mCameraId = cameraId;
}
-
mImageCapture.setup(mCameraId);
mFocusManager.setup(mCameraId);
-
- return;
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
@@ -113,49 +147,27 @@ public void setup() {
Log.e(TAG, "Camera2 API is not supported");
throw new UnsupportedOperationException("Camera2 API is not supported");
}
+
+ mState = INITIALIZED;
}
@Override
- public void start(final OnImageAvailableListener listener) {
- // This will send the camera into the Setup state, from which it will eventually
- // move into the Focusing and then Capturing states:
-
- //if (mTextureView == null) {
- // openCamera();
- //} else if (mTextureView.isAvailable()) {
- // // When the screen is turned off and turned back on, the SurfaceTexture is already
- // // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
- // // a camera and start preview from here (otherwise, we wait until the surface is ready in
- // // the SurfaceTextureListener).
- // openCamera();
- // configureTransform();
- //} else {
- // mTextureView.setSurfaceTextureListener(
- // new TextureView.SurfaceTextureListener() {
- // @Override
- // public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
- // openCamera();
- // }
- // @Override
- // public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
- // configureTransform();
- // }
- // @Override
- // public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
- // return true;
- // }
- // @Override
- // public void onSurfaceTextureUpdated(SurfaceTexture texture) {
- // }
- // }
- // );
- //}
+ public void start(final OnImageAvailableListener listener, Handler callbackHandler)
+ {
+ if (mState != INITIALIZED)
+ throw new IllegalStateException("StillSequenceCamera2.start() can only be called in the INITIALIZED state");
+
+ if (callbackHandler == null)
+ callbackHandler = new Handler();
+ final Handler _callbackHandler = callbackHandler;
mFocusThread = new HandlerThread("CameraBackground");
mFocusThread.start();
mFocusHandler = new Handler(mFocusThread.getLooper());
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
+
+ mState = CHANGING;
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
@@ -184,6 +196,7 @@ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
}
Log.d(TAG, "CameraDevice configured");
mCaptureSession = cameraCaptureSession;
+ mState = FOCUSING;
mFocusManager.start(
mCaptureSession,
mFocusHandler,
@@ -191,8 +204,24 @@ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
@Override
public void focusLocked() {
//startCapturePhase();
- mImageCapture.start(mCaptureSession, mFocusHandler, listener);
- mFocusManager.stop(mFocusHandler);
+ mState = CAPTURING;
+ mImageCapture.start(mCaptureSession, _callbackHandler, listener);
+ mFocusManager.stop();
+ }
+
+ @Override
+ public void error(final Exception error) {
+ mState = ERROR;
+ mFocusManager.stop();
+ if (listener != null)
+ _callbackHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ listener.onError(error);
+ }
+ }
+ );
}
}
);
@@ -201,12 +230,23 @@ public void focusLocked() {
@Override
public void onConfigureFailed(
CameraCaptureSession cameraCaptureSession) {
+ mState = ERROR;
Log.e(TAG, "Failed");
+ if (listener != null)
+ mFocusHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ listener.onError(null);
+ }
+ }
+ );
}
},
null
);
} catch (CameraAccessException e) {
+ mState = FAILED;
throw new UnsupportedOperationException("Camera access required");
}
}
@@ -246,12 +286,21 @@ public void onError(CameraDevice cameraDevice, int error) {
}
@Override
- public void stop() {
+ public void stop()
+ {
+ if (mState == CLOSED)
+ return;
+
+ if (mState != CAPTURING)
+ throw new IllegalStateException("StillSequenceCamera2.stop() can only be called in the STARTED state");
+
+ mState = CHANGING;
+
new Thread(new Runnable() {
public void run() {
try {
- mFocusManager.stop(mFocusHandler);
+ mFocusManager.stop();
mImageCapture.stop();
mCameraOpenCloseLock.acquire();
if (mCaptureSession != null) {
@@ -278,13 +327,25 @@ public void run() {
e.printStackTrace();
}
}
+ mState = INITIALIZED;
}
}).start();
}
@Override
public void close() {
+ if (mState == CLOSED)
+ return;
+
+ if (mState == CAPTURING)
+ stop();
+
+ if (mState != INITIALIZED)
+ throw new IllegalStateException("StillSequenceCamera2.close() can only be called in the INITIALIZED state");
+
mFocusManager.close();
mImageCapture.close();
+
+ mState = CLOSED;
}
}
diff --git a/tracking-barcode-scanner/src/main/java/dk/schaumburgit/trackingbarcodescanner/TrackingBarcodeScanner.java b/tracking-barcode-scanner/src/main/java/dk/schaumburgit/trackingbarcodescanner/TrackingBarcodeScanner.java
index f95b641..70add76 100644
--- a/tracking-barcode-scanner/src/main/java/dk/schaumburgit/trackingbarcodescanner/TrackingBarcodeScanner.java
+++ b/tracking-barcode-scanner/src/main/java/dk/schaumburgit/trackingbarcodescanner/TrackingBarcodeScanner.java
@@ -25,19 +25,52 @@
import java.util.EnumSet;
import java.util.Hashtable;
+/**
+ * The TrackingBarcodeScanner class looks for barcodes in the images supplied by the called.
+ *
+ * What distinguishes it from other barcode scanners is the tracking algorithm used. The tracking
+ * algorithm is optimized for use with sequential images (like the frames in a video recording).
+ * Once it has found a barcode in one area, it will look in the same area first in the next frame
+ * - only if that fails will it look in the entire image.
+ *
+ * This optimization yields speed-ups of 2-5 times (i.e. finding a barcode falls from 100ms to
+ * 20ms) in optimum situations (meaning where the barcode moves relatively little between frames).
+ *
+ * In situations where the images are completely unrelated, the tracking may actually cause a
+ * slowdown, due to the time wasted on the initial scan.
+ *
+ * The tracking algorithm may be switched on and off dynamically, so you can experiment with it's
+ * applicability for you scenario (see UseTracking bellow).
+ *
+ * The tracking algorithm is affected by the following properties:
+ *
+ * UseTracking (boolean): switches the entire tracking on or off (default: true)
+ *
+ * RelativeTrackingMargin (double): Specifies the relative margin to around the previous hit when
+ * running the initial tracking scan. Large values allow relatively large movements between frames,
+ * at the cost of lowered performance. Smaller values are faster, but allow less movement before
+ * tracking is lost (default 1.0)
+ *
+ * NoHitsBeforeTrackingLoss (int): Due to bad frames (e.g. motion blur), no-hit scans may
+ * occasionally occur. This parameter specifies how many consecutive bad frames will cause
+ * a tracking loss (default 5).
+ *
+ * PreferredImageFormats (readonly, int[]): Specifies the image formats supported by
+ * TrackingBarcodeScanner - using values from the ImageFormats enum - in order of preference
+ * (default {YUV_420_888, JPEG})
+ */
public class TrackingBarcodeScanner {
- private double mRelativeTrackingMargin = 1.0;
- private int mNoHitsBeforeTrackingLoss = 5;
- private EnumSet mPossibleFormats = EnumSet.of(BarcodeFormat.QR_CODE);
+
/**
* Tag for the {@link Log}.
*/
private static final String TAG = "BarcodeFinder";
- private static final int[] mPreferredFormats = {ImageFormat.YUV_420_888, ImageFormat.JPEG};
- public int[] GetPreferredFormats()
- {
- return mPreferredFormats;
- }
+
+ private static final int[] mPreferredImageFormats = {ImageFormat.YUV_420_888, ImageFormat.JPEG};
+ private boolean mUseTracking = true;
+ private double mRelativeTrackingMargin = 1.0;
+ private int mNoHitsBeforeTrackingLoss = 5;
+ private EnumSet mPossibleBarcodeFormats = EnumSet.of(BarcodeFormat.QR_CODE);
private QRCodeReader mReader = new QRCodeReader();
private Hashtable mDecodeHints;
@@ -45,7 +78,7 @@ public TrackingBarcodeScanner()
{
mDecodeHints = new Hashtable();
mDecodeHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
- mDecodeHints.put(DecodeHintType.POSSIBLE_FORMATS, mPossibleFormats);
+ mDecodeHints.put(DecodeHintType.POSSIBLE_FORMATS, mPossibleBarcodeFormats);
}
public Date a;
@@ -64,7 +97,8 @@ public String find(int imageFormat, int w, int h, byte[] bytes)
case ImageFormat.JPEG:
// from JPEG
// =========
- // ZXing doesn't accept a JPEG-encoded byte array, so we let Java decode intoa Bitmap:
+ // ZXing doesn't accept a JPEG-encoded byte array, so we let Java
+ // decode into a Bitmap:
Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
b = new Date();
int width = bm.getWidth();
@@ -99,13 +133,15 @@ public String find(int imageFormat, int w, int h, byte[] bytes)
try {
Result r = null;
- // First try where we found the barcode before (much quicker that way):
- if (mLatestMatch != null) {
- Geometry.Rectangle crop = mLatestMatch.normalize(0, 0, bitmap.getWidth(), bitmap.getHeight());
- //Log.d(TAG, "CROP: looking in (" + crop.x + ", " + crop.y + ", " + crop.width + ", " + crop.height + ")");
- int left = crop.x;
- int top = crop.y;
- r = doFind(bitmap.crop(left, top, crop.width, crop.height));
+ if (mUseTracking) {
+ // First try where we found the barcode before (much quicker that way):
+ if (mLatestMatch != null) {
+ Geometry.Rectangle crop = mLatestMatch.normalize(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ //Log.d(TAG, "CROP: looking in (" + crop.x + ", " + crop.y + ", " + crop.width + ", " + crop.height + ")");
+ int left = crop.x;
+ int top = crop.y;
+ r = doFind(bitmap.crop(left, top, crop.width, crop.height));
+ }
}
d = new Date();
@@ -187,5 +223,43 @@ private void rememberMatch(Result r)
//Log.d(TAG, "CROP: (" + match.x + ", " + match.y + ", " + match.width + ", " + match.height + ")");
}
}
+
+ public double getRelativeTrackingMargin() {
+ return mRelativeTrackingMargin;
+ }
+
+ public void setRelativeTrackingMargin(double mRelativeTrackingMargin) {
+ this.mRelativeTrackingMargin = mRelativeTrackingMargin;
+ }
+
+ public int getNoHitsBeforeTrackingLoss() {
+ return mNoHitsBeforeTrackingLoss;
+ }
+
+ public void setNoHitsBeforeTrackingLoss(int mNoHitsBeforeTrackingLoss) {
+ this.mNoHitsBeforeTrackingLoss = mNoHitsBeforeTrackingLoss;
+ }
+
+ public EnumSet getPossibleBarcodeFormats() {
+ return mPossibleBarcodeFormats;
+ }
+
+ public void setPossibleBarcodeFormats(EnumSet mPossibleFormats) {
+ this.mPossibleBarcodeFormats = mPossibleFormats;
+ mDecodeHints.put(DecodeHintType.POSSIBLE_FORMATS, mPossibleBarcodeFormats);
+ }
+
+ public boolean isUseTracking() {
+ return mUseTracking;
+ }
+
+ public void setUseTracking(boolean useTracking) {
+ this.mUseTracking = useTracking;
+ }
+
+ public int[] getPreferredImageFormats()
+ {
+ return mPreferredImageFormats;
+ }
}