3

I am working on a modification of Google's Camera2 API example for Android, found here: https://github.com/googlesamples/android-Camera2Basic

I am uploading captured images to Cloudinary, and obviously need to do so in a background thread so the UI isn't blocked.

The problem I'm running into, however, is that the UI actually is blocked when the image is uploaded even though from what I understand, it shouldn't be, because the Handler is created with the Looper from a background thread like so:

private void startBackgroundThread() {
    mBackgroundThread = new HandlerThread("CameraBackground");
    mBackgroundThread.start();
    mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}

The ImageSaver class, which is responsible for writing the captured image to disk, is as follows:

private static class ImageSaver implements Runnable {
    /**
     * The JPEG image
     */
    private final Image mImage;
    /**
     * The file we save the image into.
     */
    private final File mFile;

    public ImageSaver(Image image, File file ) {
        mImage = image;
        mFile = file;
    }

    @Override
    public void run() {
        ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        FileOutputStream output = null;
        try {
            output = new FileOutputStream(mFile);
            output.write(bytes);
            InputStream is = new ByteArrayInputStream(bytes);
            Map uploadResult = CloudinaryManager.getInstance().uploader().upload(is, ObjectUtils.asMap(
                    "api_key", CloudinaryManager.CLOUDINARY_API_KEY,
                    "api_secret", CloudinaryManager.CLOUDINARY_SECRET_KEY
            ));
            System.out.println("result");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            mImage.close();
            if (null != output) {
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

The ImageSaver is added to the Handler here:

 private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
        = new ImageReader.OnImageAvailableListener() {

    @Override
    public void onImageAvailable(ImageReader reader) {
        mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
    }
};

I would appreciate any help or advice to point me in the right direction.

Jeff Fennell
  • 41
  • 1
  • 5

2 Answers2

1

I believe it's because there is a lock in the background being used by the camera ... Since you acquiring an Image from the ImageReader, I suspect, it is holding a lock until you are done with the resource ... So as a suggestion, I would fill up the byte array inside the onImageAvailable, close the image you acquired, and send the byte array to the AsyncTask to execute saving

Arjun
  • 322
  • 4
  • 20
0

I had the same issue and after some investigation and testing, discovered it is not actually freezing the UI, it is freezing the camera preview which on many camera apps give the same impression.

If you take a look at the method unLockFocus() you can see that it sets the camera back to the normal state of preview.

Looking at the point at which this is called you can see it is not until the image has been saved:

           .
           .
           .
           CameraCaptureSession.CaptureCallback CaptureCallback
                    = new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    showToast("Saved: " + mFile);
                    Log.d(TAG, mFile.toString());
                    unlockFocus();
                }
            };

By calling this at an earlier point in the camera save sequence the preview is enabled and the UI appears unlocked again much earlier.

I have experimented and it appears to work if it is called just after acquiring the image and before saving it - I also removed the original call to unLockFocus in the captureCallback. Note that I have not done any proper testing of race conditions etc so I would strongly advise experimenting yourself to make sure your case works (I will update this if I do verify it more):

    /**
     * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
     * still image is ready to be saved.
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.d(TAG,"onImageAvailable");

            //Get the image
            Image cameraImage = reader.acquireNextImage();

            //Now unlock the focus so the UI does not look locked - note that this is a much earlier point than in the
            //original Camera2Basic example from google as the original place was causing the preview to lock during any
            //image manipulation and saving.
            unlockFocus();

            //Save the image file in the background - note check you have permissions granted by user or this will cause an exception.
            mBackgroundHandler.post(new ImageSaver(getActivity().getApplicationContext(), cameraImage, outputPicFile);

        }

    };
Mick
  • 24,231
  • 1
  • 54
  • 120