ZXing with Android quick start.

ZXing(https://code.google.com/p/zxing/) is a great library for barcode image processing, which can be used for Android apps development. There are many tutorials for integrating it, but i have not found proper one so far(don’t think that copying the whole source code of example app to your project is such a great idea to do http://damianflannery.wordpress.com/2011/06/13/integrate-zxing-barcode-scanner-into-your-android-app-natively-using-eclipse/). You’ll need ZXing core lib in project for it to work.

The simple usage of this lib can be described in this steps:

  1. Get data from preview callback
  2. Convert it to PlanarYUVLuminanceSource
  3. Binarize PlanarYUVLuminanceSource and use MultiFormatReader to get results.

That’s it! Sound simple, isn’t it? Of course there is some more work to be done (there is no need to use full sized image, small rectangle area will do the trick), but those are main steps.

Capture activity is as simple as it can be and is used only to create camera instance and start previewing camera data. It also request frame for the first time for ZXing to try find barcode on it.

public class CaptureActivity extends Activity {
    private CameraPreviewView cameraPreview;
    private CameraManager cameraManager;
    private Handler captureHandler;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_capture);
        // Create an instance of Camera
        cameraManager = new CameraManager();
        captureHandler = new CaptureHandler(cameraManager, this, new OnDecoded());
        //requesting next frame for decoding
        cameraManager.requestNextFrame(new PreviewCallback(captureHandler, cameraManager));
        cameraPreview = (CameraPreviewView) findViewById(R.id.camera_preview);
        cameraPreview.setCameraManager(cameraManager);
        ((BoundingView) findViewById(R.id.bounding_view)).setCameraManager(cameraManager);
    }
    ...
}

Camera instance focuses with Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, so there is no need to implement focusing by hands. But there is no need to try to find barcode on preview every time app retrieves camera preview(this is too bad for performance), so it’s a good idea to run one decoding at a time. It can be done with Camera.setOneShotPreviewCallback(Camera.PreviewCallback). If decoding is successful, than there is no need to setOneShotPreviewCallback again, otherwise setOneShotPreviewCallback must be called again.

Here is example of Camera.PreviewCallback implementation:

public class PreviewCallback implements Camera.PreviewCallback {
    ...
    private final MultiFormatReader multiFormatReader = new MultiFormatReader();
    ...
    private CameraManager cameraManager;
    ...
    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        Camera.Size previewSize = camera.getParameters().getPreviewSize();
        new DecodeAsyncTask(previewSize.width, previewSize.height).execute(bytes);
    }
    /**
     * Asynchronous task for decoding and finding barcode
     */
    private class DecodeAsyncTask extends AsyncTask<byte[], Void, Result> {
        private int width;
        private int height;
        /**
         * @param width  Width of image
         * @param height Height of image
         */
        private DecodeAsyncTask(int width, int height) {
            this.width = width;
            this.height = height;
        }
        ...
        @Override
        protected Result doInBackground(byte[]... datas) {
            if (!cameraManager.hasCamera()) {
                return null;
            }
            Result rawResult = null;
            final PlanarYUVLuminanceSource source =
                    cameraManager.buildLuminanceSource(datas[0], width,
                            height, cameraManager.getBoundingRect());
            if (source != null) {
                BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
                try {
                    rawResult = multiFormatReader.decodeWithState(bitmap);
                } catch (ReaderException re) {
                    // nothing to do here
                } finally {
                    multiFormatReader.reset();
                }
            }
            return rawResult;
        }
    }
}

MultiFormatReader is in ZXing lib and is used to decoding different barcode formats at the same time.

And building PlanarYUVLuminanceSource is pretty simple too:

return new PlanarYUVLuminanceSource(data, height, width, boundingRect.top, boundingRect.left, boundingRect.height(), boundingRect.width(), false);

where boundingRect is rectangle area of image to search barcode on.

Source of this example simple app:
https://github.com/ShyykoSerhiy/ZXingQuickStart

About these ads

28 thoughts on “ZXing with Android quick start.

  1. I have a ListView and there is an item in this ListView to scan qrcode. But when I click other item and then click scan item, it can not scan again.

    Could you help me to solve this issue ?
    I also run cameraManager.release(); in onPause() function.
    I tried to run cameraManager.release(); in onItemClicked, but it still can not rescan.

      • crash log:
        11-11 18:30:57.690: E/AndroidRuntime(30795): java.lang.RuntimeException: getParameters failed (empty parameters)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at android.hardware.Camera.native_getParameters(Native Method)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at android.hardware.Camera.getParameters(Camera.java:1422)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at com.shyyko.zxing.quick.camera.PreviewCallback.onPreviewFrame(PreviewCallback.java:47)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at android.hardware.Camera$EventHandler.handleMessage(Camera.java:752)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at android.os.Handler.dispatchMessage(Handler.java:99)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at android.os.Looper.loop(Looper.java:137)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at android.app.ActivityThread.main(ActivityThread.java:4745)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at java.lang.reflect.Method.invokeNative(Native Method)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at java.lang.reflect.Method.invoke(Method.java:511)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        11-11 18:30:57.690: E/AndroidRuntime(30795): at dalvik.system.NativeStart.main(Native Method)

      • Sorry, my bad. It looks like, that sometimes it may crash at
        Camera.Size previewSize = camera.getParameters().getPreviewSize();
        if camera was released. A possible way to fix :
        add if (cameraManager.getCamera != null) check

    • Quick fix is to handle RuntimeException with try catch. I know this is extremely bad thing to do, but it will do the trick for the moment. I’ll investigate into this problem on this weekends to find a proper solution.

  2. Thanks for the helpful code sample. I also experience the crash. Seems to happen much more when not in debug mode. You had some suggestions above. Have you update the code in github with these suggestions?

      • Thanks. I have also found that the application will crash on a Nexus 7 (2013) if the tablet is in portrait mode. If the tablet is in Landscape mode when the app starts it is working fine.

        11-14 18:54:01.740: E/AndroidRuntime(24777): Caused by: java.lang.ArrayIndexOutOfBoundsException: length=3110400; index=-1040570

  3. Thank you for this demo. I have three questions since I’m very new to Android development… in fact, I started just 24 hours ago when I installed ADT.

    1. What’s the purpose of PlanarYUVLuminanceSource in this implementation.

    2. I’ve got a fully working application with this code, but I’ve noticed that Garbage Collection is continual once the webcam starts up. I’m hoping there is a modification that will limit the amount of memory that is recycled by this application. Would I be correct that the rendering of the camera image has a lot to do with the memory usage? Specifically, the rotate90 method?

    3. I’m trying to move around the bounding box to put it into the top half of the preview screen instead of the middle. For some reason the I can’t seem to figure out a combination of BOUNDS_FRACTION and VERTICAL_HEIGHT_FRACTION that will move it up. :-) My math is failing me.. any help would be appreciated.

    Thanks again for such a good demo!

    • 1. HybridBinarizer to binarize image uses LuminanceSource as a parameter, that’s why we need to use PlanarYUVLuminanceSource (which is LuminanceSource(purpose of this class hierarchy is to abstract different bitmap implementations across platforms into a standard interface for requesting greyscale luminance values.) implemetation around an array of YUV data (android camera typically returns data in YUV color space)).
      2. Working with camera data always was “memory hog” task to do. Having for example 640*480 preview size gives you 640 * 480 * (ImageFormat.getBitsPerPixel(parameters.getPreviewFormat())) / 8 bytes and at least 640*480/1024 = 300 kb of memory usage in

      public void onPreviewFrame(byte[] bytes, Camera camera) {
      Camera.Size previewSize = camera.getParameters().getPreviewSize();
      new DecodeAsyncTask(previewSize.width, previewSize.height).execute(bytes);
      }

      and having processed “copies” of this data in PlanarYUVLuminanceSource and BinaryBitmap makes situation with memory even worse. As for rotate90 method, it actually doesn’t affect memory usage at all, as it modifies existing byte[] data array and doesn’t create new objects. Possible way to limit the amount of memory that is recycled is to make preview size smaller. You can find out which sizes are supported by device by calling android.hardware.Camera.Parameters.getSupportedPreviewSizes().
      P.S. I need to look in my code to give you a hint for the 3 question. Hope, i’ll have time soon:)

      • Thank you for your response, especially to question 2. In my app, I’m going to put the camera in the top half of the screen so I can put information acquired from the scan in the lower half. Making the preview smaller might be exactly what I need, as long it doesn’t affect the ability to decode barcodes. What about reducing the number of scans per second? Is that possible?

      • case R.id.decode_failed:
        cameraManager.requestNextFrame(new PreviewCallback(this, cameraManager));
        break;

        can be changed using
        case R.id.decode_failed:
        postDelayed(new Runnable(){
        @Override
        public void run() {
        //getting new frame
        cameraManager.requestNextFrame(new PreviewCallback(this, cameraManager));
        }
        }, 500); //500 milliseconds delay
        break;

        I didn’t tested this code, but it can give you a hint how it can be achieved

  4. it crash on samsung galaxy note 10.1

    java.lang.ArrayIndexOutOfBoundsException: src.length=375000 srcPos=1037184 dst.length=1152 dstPos=0 length=1152
    03-10 17:35:04.810: W/System.err(21462): at java.lang.System.arraycopy(Native Method)
    03-10 17:35:04.810: W/System.err(21462): at com.google.zxing.PlanarYUVLuminanceSource.getRow(Unknown Source)
    03-10 17:35:04.810: W/System.err(21462): at com.google.zxing.common.GlobalHistogramBinarizer.getBlackRow(Unknown Source)
    03-10 17:35:04.810: W/System.err(21462): at com.google.zxing.BinaryBitmap.getBlackRow(Unknown Source)
    03-10 17:35:04.810: W/System.err(21462): at com.google.zxing.oned.OneDReader.doDecode(Unknown Source)
    03-10 17:35:04.810: W/System.err(21462): at com.google.zxing.oned.OneDReader.decode(Unknown Source)
    03-10 17:35:04.810: W/System.err(21462): at com.google.zxing.MultiFormatReader.decodeInternal(Unknown Source)
    03-10 17:35:04.815: W/System.err(21462): at com.google.zxing.MultiFormatReader.decodeWithState(Unknown Source)
    03-10 17:35:04.815: W/System.err(21462): at com.shyyko.zxing.quick.camera.PreviewCallback$DecodeAsyncTask.doInBackground(PreviewCallback.java:115)
    03-10 17:35:04.815: W/System.err(21462): at com.shyyko.zxing.quick.camera.PreviewCallback$DecodeAsyncTask.doInBackground(PreviewCallback.java:1)
    03-10 17:35:04.815: W/System.err(21462): at android.os.AsyncTask$2.call(AsyncTask.java:287)
    03-10 17:35:04.815: W/System.err(21462): at java.util.concurrent.FutureTask.run(FutureTask.java:234)
    03-10 17:35:04.815: W/System.err(21462): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
    03-10 17:35:04.815: W/System.err(21462): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
    03-10 17:35:04.815: W/System.err(21462): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
    03-10 17:35:04.815: W/System.err(21462): at java.lang.Thread.run(Thread.java:841)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s