dm77 / barcodescanner

Barcode Scanner Libraries for Android
Other
5.46k stars 1.42k forks source link

ZBar Scanner - slow stopping of camera on some devices (with proposed patch) #99

Closed lemon-juice closed 8 years ago

lemon-juice commented 9 years ago

I have implemented your library in my application to scan QR codes and it is very good. However, I've come across one problem with the fact that after a code has been scanned (just before mResultHandler.handleResult() is called) your library stops the camera before returning to my code. In my application the handleResult() method needs to check if the scanned code is correct (has correct format) and only if it is I can accept it - otherwise I need to continue running the scanner until a correct code has been scanned.

First, the way I did it was that if the code was incorrect I restarted the scanner with mScannerView.startCamera() - no practical problem with that apart from a short camera freeze.

If the code was correct the procedure was like this:

  1. Play a short beep sound
  2. Save the scanned code in a shared preferences file
  3. Go to another activity which showed confirmation screen

I tested it on two phones - LG Swift L5 II performed very well, it took about 0.5 second from QR code recognition to seeing the confirmation screen. But then I tested it with Samsung Galaxy Xcover 3 and the same procedure was very slow - about 2.5 seconds. What I discovered was that in the Samsung stopping the camera before running handleResult() contributed to the delay - I saw the camera screen frozen for a while before the app proceeded to the confirmation screen. I have no idea why I experienced the delay in the Samsung since it is a newer and more powerful phone than the LG.

I managed to solve the problem and get rid of the delay by modifying your library not to stop the camera - the camera is only stopped in my app when leaving the scanner activity onPause() and now I was able to reduce the delay from 2.5 to about 0.5 second. Apart from not stopping the camera on code recognition my modifications were made to not continue with camera.setOneShotPreviewCallback() once the qr code was verified. Overall I think it would be good if your library did not stop the camera and left the control to the programmer.

What I did was I removed the stopCamera() in ZBarScannerView.java and modified the ResultHandler to return a boolean - if true is returned then ZBarScannerView is instructed to continue scanning (in case my app didn't accept the code), if it is false then ZBarScannerView will not continue scanning (if my app accepted the code and no longer needs the scanner and the camera to work). Here is my patch:

diff --git "a/ZBarScannerView.java" "b/ZBarScannerView.java"
--- "a/ZBarScannerView.java"
+++ "b/ZBarScannerView.java"
@@ -21,7 +21,7 @@ import me.dm7.barcodescanner.zbar.Result;

 public class ZBarScannerView extends BarcodeScannerView {
     public interface ResultHandler {
-        public void handleResult(Result rawResult);
+        public boolean handleResult(Result rawResult);
     }

     static {
@@ -94,7 +94,6 @@ public class ZBarScannerView extends BarcodeScannerView {
         int result = mScanner.scanImage(barcode);

         if (result != 0) {
-            stopCamera();
             if(mResultHandler != null) {
                 SymbolSet syms = mScanner.getResults();
                 Result rawResult = new Result();
@@ -106,7 +105,11 @@ public class ZBarScannerView extends BarcodeScannerView {
                         break;
                     }
                 }
-                mResultHandler.handleResult(rawResult);
+                Boolean continueScanning = mResultHandler.handleResult(rawResult);
+
+                if (continueScanning) {
+                    camera.setOneShotPreviewCallback(this);
+                }
             }
         } else {
             camera.setOneShotPreviewCallback(this);

Of course, this may not be the best solution to change your library in this way because it is not backwards compatible - now handleResult() needs to return a boolean and the camera is not stopped so old code witll not work well with these changes. But you could implement some other way of achieving the same result, for example with a configuration variable and a different ResultHandler that will not stop the camera so an app developer can choose the alternative behaviour.

dm77 commented 9 years ago

Thanks for the detailed explanation. Performance is definitely an issue that I would like to fix. I am hoping that by fixing #1 your issue here will also be resolved.

shiSHARK commented 8 years ago

Even better optimization will be to move the Camera call on separate thread. Because initialization and releasing of the camera are slow operations. On my Nexus 5 they take about 1 second and Resuming and Pausing the Activity is slow. What I did additional to lemon-juice improvement is: BarcodeScannerView class -

    public void stopCamera() {
        if(mCamera != null) {
            mPreview.setCamera(null, null);
            new AsyncTask<Void,Void,Void>(){
                @Override
                protected Void doInBackground(Void... params) {
                    if(mCamera != null) {
                        mCamera.cancelAutoFocus();
                        mCamera.setOneShotPreviewCallback(null);
                        mCamera.stopPreview();
                        mCamera.release();
                        mCamera = null;
                    }
                    return null;
                }
            }.execute();
        }
    } 

and

CameraPreview class -

    public void setCamera(Camera camera, Camera.PreviewCallback previewCallback) {
        mCamera = camera;
        mPreviewCallback = previewCallback;
        mAutoFocusHandler = new Handler();
        if(mCamera == null){
            mPreviewing = false;
        }
    }
dm77 commented 8 years ago

Fixed in v1.8.4. Basically Camera is opened in a HandlerThread. And the onPreviewFrame method no longer closes the camera. It only stops the camera preview. So in your handleResult method, if you would like to resume scanning you can do: mScannerView.resumeCameraPreview(this);