6

So, we are trying to make an app that takes photos as you rotate the device (approximately every 3 angles). However, using camera2, as we rotate the device:

  1. Every time it takes a photo, the preview lags
  2. After a few images, onCaptureFailed triggers, with code 0

I'm still trying to figure out Camera2 (it seems many people think its super great, so I'm trying to get my head around it, but its rather complicated). I downloaded the RAW example (https://github.com/googlesamples/android-Camera2Raw) and modified it to work as described above.

Previously, we used the deprecated camera api, which works 100% on a Samsung s8 - BUT, fails on older devices as the rate of taking images was too fast to handle. However, I saw that these older devices DO in fact allow one to take BURST photos - so surely camera 2 allows one to somehow access this functionality?

Just to be clear: I do not want to take burst photos, as this will cause images to be taken while the angle has not changed yet. Here is the code I needed to change:

 // Gets called whenever the sensor gives me a value. calculates if it is time to take the next photo
 private void updateAngle(double angle) {
    mCurrentAngle = angle;
    double photoAngle = angle - mOffset < 0 ? CONST_360 + angle - mOffset : angle - mOffset;

    if (photoAngle < mPrevAngle) {
        tvZAxis.setText("You are going the wrong way");
        Log.w("BROKEN", mPrevAngle + "  " + photoAngle);
        return;
    }

    if (mStart) {
        double diff = photoAngle - mPrevAngle;
        if (diff > 300) {
            tvZAxis.setText("You have gone the wrong way. ");
            return;
        }

        if (diff > MAX_ALLOWED_ANGLE) {
            tvZAxis.setText("You are going too fast, go back a bit");
            return;
        } else if (diff > 1) {

            if (diff >= 3) {
                tvZAxis.setText("Do not go faster");
            }

            if (photoAngle > 356 && diff < 7) {
                photoAngle = CONST_360;
            }
            mPrevAngle = photoAngle;

            int prog = Math.max(0, (int) ((photoAngle / CONST_360) * 100.f));
            mProgress = prog;
            mProgressBar.setProgress(Math.max(0, prog));
            takePicture();
           // We USED to use old camer API:    mCamera.takePicture(null, null, mPicture);
        }
    }
}

And the callback, I just changed the file names to be able to distinguish between each angled photo:

/**
 * A {@link CameraCaptureSession.CaptureCallback} that handles the still JPEG and RAW capture
 * request.
 */
private final CameraCaptureSession.CaptureCallback mCaptureCallback
        = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
                                 long timestamp, long frameNumber) {
        mPhotoCount++;
        String currentDateTime = generateTimestamp();
        File rawFile = new File(Environment.
                getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
                "RAW_" + mPhotoCount + ".dng");
        File jpegFile = new File(Environment.
                getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
                "JPEG_" + mPhotoCount + ".jpg");

        // Look up the ImageSaverBuilder for this request and update it with the file name
        // based on the capture start time.
        ImageSaver.ImageSaverBuilder jpegBuilder;
        ImageSaver.ImageSaverBuilder rawBuilder;
        int requestId = (int) request.getTag();
        synchronized (mCameraStateLock) {
            jpegBuilder = mJpegResultQueue.get(requestId);
            rawBuilder = mRawResultQueue.get(requestId);
        }

        if (jpegBuilder != null) jpegBuilder.setFile(jpegFile);
        if (rawBuilder != null) rawBuilder.setFile(rawFile);
    }

And, for good measure, here is the takePicture() method.

/**
 * Initiate a still image capture.
 * <p/>
 * This function sends a capture request that initiates a pre-capture sequence in our state
 * machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no
 * longer moving, waits for auto-exposure to choose a good exposure value, and waits for
 * auto-white-balance to converge.
 */
private void takePicture() {
    synchronized (mCameraStateLock) {
        mPendingUserCaptures++;

        // If we already triggered a pre-capture sequence, or are in a state where we cannot
        // do this, return immediately.
        if (mState != STATE_PREVIEW) {
            return;
        }

        try {
            // Trigger an auto-focus run if camera is capable. If the camera is already focused,
            // this should do nothing.
            if (!mNoAFRun) {
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                        CameraMetadata.CONTROL_AF_TRIGGER_START);
            }

            // If this is not a legacy device, we can also trigger an auto-exposure metering
            // run.
            if (!isLegacyLocked()) {
                // Tell the camera to lock focus.
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                        CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
            }

            // Update state machine to wait for auto-focus, auto-exposure, and
            // auto-white-balance (aka. "3A") to converge.
            mState = STATE_WAITING_FOR_3A_CONVERGENCE;

            // Start a timer for the pre-capture sequence.
            startTimerLocked();

            // Replace the existing repeating request with one with updated 3A triggers.
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

I feel like I'm missing something super obvious. Will update the post (and hopefully the solution) as I trod along.

EDIT: Ok since asking this question, there have been some major changes. It mostly works now - but, depending on the device, it sometimes blurs as the device tries to focus.

TakePicture has been replaced with this:

 private void captureStillPictureLocked() {
    try {

        if (null == mCameraDevice) {
            return;
        }
        // This is the CaptureRequest.Builder that we use to take a picture.
        final CaptureRequest.Builder captureBuilder =
                mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);

        captureBuilder.addTarget(mJpegImageReader.get().getSurface());

        // Use the same AE and AF modes as the preview.
        setup3AControlsLocked(captureBuilder);

        // Set orientation.
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
                sensorToDeviceRotation(mCharacteristics, rotation));

        // Set request tag to easily track results in callbacks.
        captureBuilder.setTag(mRequestCounter.getAndIncrement());

        CaptureRequest request = captureBuilder.build();

        // Create an ImageSaverBuilder in which to collect results, and add it to the queue
        // of active requests.
        ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(this)
                .setCharacteristics(mCharacteristics);

        mJpegResultQueue.put((int) request.getTag(), jpegBuilder);

        mCaptureSession.capture(request, mCaptureCallback, mBackgroundHandler);

    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

and

 private final CameraCaptureSession.CaptureCallback mCaptureCallback
        = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
                                 long timestamp, long frameNumber) {
        mPhotoCount++;
        File jpegFile = new File(Environment.
                getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
                "JPEG_" + mPhotoCount + ".jpg");

        vals.add(jpegFile.getAbsolutePath());

        // Look up the ImageSaverBuilder for this request and update it with the file name
        // based on the capture start time.
        ImageSaver.ImageSaverBuilder jpegBuilder;
        int requestId = (int) request.getTag();
        synchronized (mCameraStateLock) {
            jpegBuilder = mJpegResultQueue.get(requestId);
        }

        if (jpegBuilder != null) jpegBuilder.setFile(jpegFile);
    }

    @Override
    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                                   TotalCaptureResult result) {
        int requestId = (int) request.getTag();
        ImageSaver.ImageSaverBuilder jpegBuilder;
        StringBuilder sb = new StringBuilder();

        // Look up the ImageSaverBuilder for this request and update it with the CaptureResult
        synchronized (mCameraStateLock) {
            jpegBuilder = mJpegResultQueue.get(requestId);

            if (jpegBuilder != null) {
                jpegBuilder.setResult(result);
                sb.append("Saving JPEG as: ");
                sb.append(jpegBuilder.getSaveLocation());
            }

            // If we have all the results necessary, save the image to a file in the background.
            handleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue);
        }

        if (mProgress >= 100) {
            Intent returnData = new Intent();
            returnData.putExtra("photoAmount", mPhotoCount);
            returnData.putExtra("paths", vals);
            setResult(RESULT_OK,returnData);
            finish();
        }
    }

    @Override
    public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
                                CaptureFailure failure) {
        int requestId = (int) request.getTag();
        synchronized (mCameraStateLock) {
            mJpegResultQueue.remove(requestId);
        }
    }

};

Honestly, I have had to put this on the back burner for a while though. Will update the question once I'm back on this (cause I've forgotten what everything does :( ), hopefully by then I've actually figured it out on my own and can just post a solution!

Zee
  • 1,592
  • 12
  • 26
  • Can u add mPreCaptureCallback code as well or is it wrongly named as mCaptureCallback here? – Sachin Kasaraddi Jul 16 '19 at 08:34
  • @SachinKasaraddi Sorry, I've modified the code heavily since this question. Somewhere along the line, I've removed mPreCaptureCallback entirely. I'll update the question with the latest code (also kind of forgot about the question - it wasn't getting much attention). – Zee Jul 16 '19 at 11:10

0 Answers0