2

I have made a custom camera view in my application. Basically i need to do something for as long as the camera in my application is open, after focusing.

in my Camera.Parameters, i have used FOCUS_MODE_CONTINUOUS_PICTURE, and it works perfectly as intended. Now, i need a callback from this continuous autofocusing practice where i can take the current focused picture and do something with it.

An alternate approach was to hook a 'Timer' technique, a function that would fire after every certain time period. And then, i could mCamera.autofocus() inside it, take picture and do my work. Unfortunately, this proved a very bad technique, since autofocusing would pose complications, varying for different devices.

So, i was wondering, what would be the perfect solution to this.

UPDATE: After making an attempt with threads

What i want to do is, AutoFocus and take picture again and again as long as the app is in the foreground.

After a number of different attempts, this is the farthest i have been able to come, still not far enough.

Please see the runnable code:

public class OCRRunnable implements Runnable {

static final String TAG = "DBG_" + "OCRRunnable";

private final Object mPauseLockDummyObject;
private boolean mPaused;
private boolean mFinished;

Camera mCamera;

public OCRRunnable(Camera cameraParam) {
    mPauseLockDummyObject = new Object();
    mPaused = false;
    mFinished = false;

    mCamera = cameraParam;
}

@Override
public void run()
{
    if (mCamera != null) { // since the main activity may have been late opening the camera
        try {
            mCamera.autoFocus(new mAutoFocusCallback());
            Log.d(TAG, "run: mCamera.autofocus()");
        } catch (Exception e) {
            Log.e(TAG, "run: " + e.getMessage());
        }

        //sleep necessary //TODO: needs refinement
        //try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }

        //Runnable regulator
        synchronized (mPauseLockDummyObject) {
            while (mPaused) {
                try {
                    mPauseLockDummyObject.wait();
                } catch (InterruptedException e) {
                    Log.e(TAG, "run: " + e.getMessage());
                }
            }
        }
    }
}

/**
 * Call this on pause of the activity.
 */
public void onPause() {
    //Log.d(TAG, "onPause: called");
    synchronized (mPauseLockDummyObject) {
        mPaused = true;
    }
}

/**
 * Call this on resume of the activity
 */
public void onResume() {
    //Log.d(TAG, "onResume: called");
    synchronized (mPauseLockDummyObject) {
        mPaused = false;
        mPauseLockDummyObject.notifyAll();
    }
}

//////////////////////////////////////
protected class mAutoFocusCallback implements Camera.AutoFocusCallback {


    @Override
    public void onAutoFocus(boolean success, final Camera camera) {
        camera.takePicture(null, null, new Camera.PictureCallback() {

            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                Log.d(TAG, "onPictureTaken() called");

                /* NEED TO RUN THIS CODE PART REPETITIVELY */


                camera.cancelAutoFocus(); //be ready for next autofocus
                camera.startPreview(); //re-start cameraPreview since taking a picture stops it


                run(); //TODO


            }

        });
    }


}
}

Here is my relevant fragment:

public class ODFragment extends Fragment {

View rootView;
static final String TAG = "DBG_" + MainActivity.class.getName();

private Camera mCamera;
private CameraPreview mCameraPreview;
int ROIHeight;

FrameLayout frameLayout_cameraLens;
TextView words_container;

Thread ocrThread;
OCRRunnable ocrRunnable; //TODO: this may cause problems because mCamera is null as of this moment

public ODFragment() {}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    rootView = inflater.inflate(R.layout.fragment_od, container, false);

    //one time tasks
    words_container = (TextView) rootView.findViewById(R.id.words_container);
    setCameraViewDimensions();
    ocrThread = new Thread(ocrRunnable); //start it in onResume()





    return rootView;
}

@Override
public void onResume() {
    super.onResume();
    hookCamera();
}

@Override
public void onPause() {
    super.onPause();
    unhookCamera();
}

private void hookCamera() {
    try {
        // 1. open camera
        mCamera = Camera.open();
        // 2. initialize cameraPreview
        mCameraPreview = new CameraPreview(this.getActivity(), mCamera);
        // 3. add view to frameLayout
        frameLayout_cameraLens.addView(mCameraPreview);
        mCamera.startPreview();
        // 4. hook camera related listeners and threads
        ocrRunnable = new OCRRunnable(mCamera);
        ocrThread = new Thread(ocrRunnable);
        ocrThread.start();
        ocrRunnable.onResume();
    } catch (Exception e) {
        e.printStackTrace();
        Log.d(TAG, "Could not Camera.open(): " + e.getMessage());
    }

}

private void unhookCamera() {
    try {
        // -4. unhook camera related listeners ans threads
        ocrRunnable.onPause();
        ocrThread = null;
        ocrRunnable = null;
        // -3. remove view from frameLayout
        frameLayout_cameraLens.removeView(mCameraPreview);
        // -2. destroy cameraPreview
        mCameraPreview = null;
        // -1. close camera
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private void setCameraViewDimensions() {
    //calculate and set dimensions of cameraLens
    DisplayMetrics displaymetrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
    int width = displaymetrics.widthPixels;
    int height = (int) (width * 1.3333333333333);
    //Log.d(TAG, "frameLayout_cameraLens dimensions: "+height+"x"+width);
    frameLayout_cameraLens = (FrameLayout) rootView.findViewById(R.id.frameLayout_cameraLens);
    frameLayout_cameraLens.getLayoutParams().width = width;
    frameLayout_cameraLens.getLayoutParams().height = height;
    frameLayout_cameraLens.requestLayout();

    //set height of ROI
    ROIHeight = height / 5;
    LinearLayout linearLayout = (LinearLayout) rootView.findViewById(R.id.ROI);
    linearLayout.getLayoutParams().height = ROIHeight;
    linearLayout.requestLayout();
}

}

Some points:

  • I think camera.autofocus() happens in a separate thread itself. That is why instead of putting a while(true) loop in run(), i have called run() in the end of the mAutoFocusCallback
  • Error at this point is that the execution comes to Log.d(TAG, "run: mCamera.autofocus()"); exactly once. Additionally, Log.d(TAG, "onPictureTaken() called"); not called even once.

Here is my relevant log:

05-29 12:51:58.460 W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
05-29 12:51:58.573 D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
05-29 12:51:58.655 W/FragmentManager: moveToState: Fragment state for VTFragment{fd65ec0 #0 id=0x7f0c006d android:switcher:2131492973:1} not updated inline; expected state 3 found 2
05-29 12:51:58.962 D/DBG_CameraPreview: CameraPreview() initialized
05-29 12:51:59.079 D/DBG_OCRRunnable: run: mCamera.autofocus()
05-29 12:51:59.097 I/Adreno-EGL: <qeglDrvAPI_eglInitialize:379>: EGL 1.4 QUALCOMM build: Nondeterministic_AU_msm8974_LA.BF.1.1.3_RB1__release_AU (I3f4bae6ca5)
                             OpenGL ES Shader Compiler Version: E031.29.00.00
                             Build Date: 02/14/16 Sun
                             Local Branch: mybranch18261495
                             Remote Branch: quic/LA.BF.1.1.3_rb1.10
                             Local Patches: NONE
                             Reconstruct Branch: NOTHING
05-29 12:51:59.101 I/OpenGLRenderer: Initialized EGL, version 1.4
05-29 12:51:59.366 I/Choreographer: Skipped 46 frames!  The application may be doing too much work on its main thread.
05-29 12:51:59.556 I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@c66e1c1 time:44964952
Abdul Wasae
  • 3,614
  • 4
  • 34
  • 56

1 Answers1

1

Use mCamera.autofocus(callback) - and the callback will be triggered when the focus is ready. This is your chance to take the picture. In onPictureTaken() you will probably call mCamera.cancelAutoFocus(); mCamera.autofocus(callback); to allow the camera re-focus again.

Naturally, you may choose not to take picture every time that the camera gets focused.

To improve performance of your camera app, make sure to call Camera.open() on a background HandlerThread - otherwise the focus and picture callback will block the UI thread. This may be acceptable when these callback happen once in a while, but in your scenario they will be activated pretty often.

Update Let me present the suggested loop more clear (in pseudocode):

cameraInit() {
  Open camera, set FOCUS_MODE_CONTINUOUS_PICTURE
  camera.autofocus(autofocusCallback)
}

autofocusCallback() {
  if (it_is_time_to_take_picture) {
    takePicture(pictureCallback)
  }
  else {
    autofocusAgain()
  }
}

pictureCallback.onPictureTaken(image) {
  autofocusAgain()
  save image
}

autofocusAgain() {
  camera.cancelAutoFocus()
  wait(100ms)
  camera.autofocus(autofocusCallback)
}
Community
  • 1
  • 1
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • Hello, thank you for your answer. My requirement is that i not have to call mCamera.autofocus(callback) on my own in a scheduled function, because it has it's complications. The good thing about FOCUS_MODE_CONTINUOUS_PICTURE parameter is that it takes care of repeated autofocusing for as long as the camera is open. Is there no way that this parameter could automatically invoke a similar callback? – Abdul Wasae May 24 '16 at 16:39
  • No. What kind of complications does the call to `autofocus()` present? – Alex Cohn May 24 '16 at 20:20
  • for example if i schedule the function to fire every 3 seconds, there might be a bad camera device out there whose previous autofocus attempt hadn't finished meanwhile. this causes an exception. If i handle the exception, then there will be a wait of 6 seconds to the next autofocus. If i be risk averse and increase the interval to 7 or 8 seconds, then this time is too much to be useful. the camera.paramter way of autofocusing is very good and fast – Abdul Wasae May 24 '16 at 20:38
  • 1
    I suggest that you follow the loop I explain in the updated answer. There is no risk in it, because calling `cancelAutoFocus()` does not actually force your camera out of focus, it only unlocks the autofocus process. – Alex Cohn May 24 '16 at 20:43
  • Alright thank you very very much! i'll try in your footsteps – Abdul Wasae May 26 '16 at 14:40
  • Hello sir @alex-cohn . Could you be kind and look at the updated question at your convenience? – Abdul Wasae May 29 '16 at 12:53
  • 1
    If you use `logcat -v threadtime`, you will see exactly which thread executes which line that you print to log. Is `takePicture()` ever called, or `autoFocus()` never succeeds? Why do you need all this synchronization? I believe that the safest path is to release camera onPause(), and open it onResume(). Unfortunately, you don't show the code that opens the camera - and this is critical, because if you don't open the camera on a background handler thread, the camera callbacks will all happen on the UI thread. – Alex Cohn May 29 '16 at 19:39
  • my code likely never makes it to takepicture() because the governing thread exits. I often see takepicture() log only when i debug (which gives it time to run) Also, please have a look at the fragment code i have added. Also, thanks a lot for helping out – Abdul Wasae May 29 '16 at 19:46
  • oh ok i get it @alex-cohn. The thread that is responsible for mCamera.open() shall govern the autoFocusCallback and onPictureTakenCallback(), even if i invoke mCamera.autofocus() in a separate thread. I try and make necessary alterations then, and update – Abdul Wasae May 29 '16 at 19:48
  • Is this an expected/documented behavior? – Andrei Mărcuţ May 29 '16 at 19:53
  • 1
    Yes, it's entirely documented, see http://stackoverflow.com/questions/18149964/best-use-of-handlerthread-over-other-similar-classes/19154438#19154438. – Alex Cohn May 30 '16 at 03:45
  • Mr. @AlexCohn, if i simply edit my code above such that the camera.open() part is made to happen in the OCRRunnable class, will it sufficiently solve the problem? – Abdul Wasae May 30 '16 at 12:08
  • Also, i understand that i must mCamera.open() in a non-UI-Thread. But does it matter in which thread i define the 'Camera mCamera;' ? – Abdul Wasae May 30 '16 at 12:32
  • 1
    Yes, it matters which thread to use: please open the link I posted here twice! – Alex Cohn May 30 '16 at 16:36
  • @AlexCohn i have studied the post on that link. It is very helpful. It, however, does not clearly suggest which thread to define Camera variable :( – Abdul Wasae May 30 '16 at 16:51
  • 1
    You should open a new HandlerThread and open the Camera on that thread. You can see an example here: https://android.googlesource.com/platform/packages/apps/Camera/+/android-4.1.1_r4/src/com/android/camera/CameraManager.java – Alex Cohn May 30 '16 at 18:00