4

I want to take 1fps still captures using Camera 2 API's setRepeatingRequest() mode.

I set the CONTROL_AE_MODE to CONTROL_AE_MODE_OFF and SENSOR_FRAME_DURATION to 1. However I still receive a very high frame rate of 20fps with the below code.

I tried changing the capture request template from TEMPLATE_PREVIEW to TEMPLATE_STILL_CAPTURE without any luck. How do I achieve 1fps using setRepeatingRequest()?

CaptureRequest.Builder requestBuilder
            = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    requestBuilder.addTarget(imageReader.getSurface());
        cameraDevice.createCaptureSession(Collections.singletonList(imageReader.getSurface()),
            new CameraCaptureSession.StateCallback() {

                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    // The camera is already closed
                    if (cameraDevice == null) {
                        return;
                    }

                    captureSession = cameraCaptureSession;
                    try {
                        requestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
                        requestBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, 1L);

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

                            @Override
                            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                    @NonNull CaptureRequest request,
                                    @NonNull TotalCaptureResult result) {
                            }
                        };

                        captureSession.setRepeatingRequest(
                                requestBuilder.build(), captureCallback, callbackHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(
                        @NonNull CameraCaptureSession cameraCaptureSession) {
                }
            }, null);
} catch (CameraAccessException e) {
    e.printStackTrace();
}
avenger
  • 131
  • 2
  • 11

2 Answers2

1

First of all, the unit in SENSOR_FRAME_DURATION is in nanoseconds, according to the docs

So try with requestBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, 1000000L);

In addition, you can do it less elegantly using a CountDownTimer, and taking a photo each 1 sec. I know this is bad, because you are initializing all the camera stuff each seconds, but it's a solution that just works. An example would be:

new CountDownTimer(5000,1000){
    @Override
    public void onFinish() { (...) }

    @Override
    public void onTick(long millisUntilFinished) {
        // TODO take picture
    }
}.start();

Another third solution would be using Camera.Parameters [setPreviewFpsRange](https://developer.android.com/reference/android/hardware/Camera.Parameters.html#setPreviewFpsRange(int, int)), but it would require Camera API instead of Camera2. But it's just another possibility

webo80
  • 3,365
  • 5
  • 35
  • 52
  • unfortunately, setPreviewFpsRange() won't make it any easier, because you can only choose one of the supported ranges. Most devices don't list 1 fps as supported frame rate. – Alex Cohn Oct 17 '17 at 13:44
  • @AlexCohn you are totally right. But the simple approach to use correctly SENSOR_FRAME_DURATION must do the trick – webo80 Oct 17 '17 at 14:04
  • Not necessarily. I am not sure what [SENSOR_INFO_MAX_FRAME_DURATION](https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#SENSOR_INFO_MAX_FRAME_DURATION) will tell. It is guaranteed to be at least 100ms for FULL capability devices, but there are no guarantees for other devices, it may even be **null**. – Alex Cohn Oct 17 '17 at 16:52
  • SENSOR_FRAME_DURATION doesn't work. I guess there is no easy way to do this using setRepeatingRequest(). I might just do repeated capture() calls – avenger Oct 18 '17 at 06:01
  • 1
    Just note that for 1fps = 1000000000L. – jimbo Oct 25 '17 at 06:02
  • @jimbo to avoid such problems better to use `TimeUnit.SECONDS.toNanos(1)` – CAMOBAP Apr 19 '19 at 13:17
0

Your control over how often will onCaptureCompleted() be invoked is limited. But you have full control over what you do in that callback.

If your camera produces 20 fps, you can skip 19 callbacks and capture the twentieth. If you are not sure that the frame rate is stable enough, you can keep the timestamp (using SystemClock.elapsedRealtime()) for the last picture you used, and ignore all callbacks until one second after that. You can even play with sleep() to improve precision if you really need it.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • Can you shed more light on how to use timestamp. Can we compare the timestamp in onCaptureCompleted with the timestamp of image that we get in onImageAvailable listener? – Shivam Pokhriyal Jul 16 '19 at 13:53
  • Unfortunately, I cannot guarantee that [Image.getTimestamp()](https://developer.android.com/reference/android/media/Image.html#getTimestamp()) can be compared to timestamp you receive from SystemClock: [*"The timestamps for the images from different sources may have different timebases therefore may not be comparable. The specific meaning and timebase of the timestamp depend on the source providing images"*](https://developer.android.com/reference/android/media/Image.html#getTimestamp()) – Alex Cohn Jul 17 '19 at 14:22
  • So is there any way we can tag the images we capture with some specific CaptureRequest parameters to distinguish them while capturing a lot of images with different parameters? – Shivam Pokhriyal Jul 18 '19 at 10:20
  • That's a tricky question. I believe that you can reference the latest available CaptureRequest parameters when an onImageAvailable() arrives, but there may be no guarantee. Some tricky HAL could deliver onCaptureCompleted() a bit later than you expect. I mean, if you have 5 FPS, or even 10 FPS, you are most likely safe that onCaptureCompleted() and onImageAvailable() that arrive within a reasonable period of time, correspond to one another. But if you have, say, 60 FPS sequence, you should not trust this correspondence anymore. – Alex Cohn Jul 18 '19 at 13:36
  • Yes, I experienced the same. I don't exactly remember the FPS I was using, but onCaptureCompleted and onImageAvailable weren't synced. I tried using timestamp to identify the images, but they don't match correctly. – Shivam Pokhriyal Jul 18 '19 at 13:54