7

I am trying to save image sequences with fixed framerates (preferably up to 30) on an android device with FULL capability for camera2 (Galaxy S7), but I am unable to a) get a steady framerate, b) reach even 20fps (with jpeg encoding). I already included the suggestions from Android camera2 capture burst is too slow.

The minimum frame duration for JPEG is 33.33 milliseconds (for resolutions below 1920x1080) according to

characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputMinFrameDuration(ImageFormat.JPEG, size);

and the stallduration is 0ms for every size (similar for YUV_420_888).

My capture builder looks as follows:

captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, _exp_time);
captureBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);

captureBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, _iso_value);

captureBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, _foc_dist);
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CONTROL_AF_MODE_OFF);

captureBuilder.set(CaptureRequest.CONTROL_AWB_MODE, _wb_value);

// https://stackoverflow.com/questions/29265126/android-camera2-capture-burst-is-too-slow
captureBuilder.set(CaptureRequest.EDGE_MODE,CaptureRequest.EDGE_MODE_OFF); 
captureBuilder.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE, CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_OFF);
captureBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_OFF);
captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);

// Orientation
int rotation = getWindowManager().getDefaultDisplay().getRotation();           
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,ORIENTATIONS.get(rotation));

Focus distance is set to 0.0 (inf), iso is set to 100, exposure-time 5ms. Whitebalance can be set to OFF/AUTO/ANY VALUE, it does not impact the times below.

I start the capture session with the following command:

session.setRepeatingRequest(_capReq.build(), captureListener, mBackgroundHandler);

Note: It does not make a difference if I request RepeatingRequest or RepeatingBurst..

In the preview (only texture surface attached), everything is at 30fps. However, as soon as I attach an image reader (listener running on HandlerThread) which I instantiate like follows (without saving, only measuring time between frames):

reader = ImageReader.newInstance(_img_width, _img_height, ImageFormat.JPEG, 2);
reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);

With time-measuring code:

ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader myreader) {
        Image image = null;

        image = myreader.acquireNextImage();
        if (image == null) {
            return;
        }
        long curr = image.getTimestamp();
        Log.d("curr- _last_ts", "" + ((curr - last_ts) / 1000000) + " ms");
        last_ts = curr;
        image.close();
    }
}

I get periodically repeating time differences like this:

99 ms - 66 ms - 66 ms - 99 ms - 66 ms - 66 ms ...

I do not understand why these take double or triple the time that the stream configuration map advertised for jpeg? The exposure time is well below the frame duration of 33ms. Is there some other internal processing happening that I am not aware of?

I tried the same for the YUV_420_888 format, which resulted in constant time-differences of 33ms. The problem I have here is that the cellphone lacks the bandwidth to store the images fast enough (I tried the method described in How to save a YUV_420_888 image?). If you know of any method to compress or encode these images fast enough myself, please let me know.

Edit: From the documentation of getOutputStallDuration: "In other words, using a repeating YUV request would result in a steady frame rate (let's say it's 30 FPS). If a single JPEG request is submitted periodically, the frame rate will stay at 30 FPS (as long as we wait for the previous JPEG to return each time). If we try to submit a repeating YUV + JPEG request, then the frame rate will drop from 30 FPS." Does this imply that I need to periodically request a single capture()?

Edit2: From https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.html: "The necessary information for the application, given the model above, is provided via the android.scaler.streamConfigurationMap field using getOutputMinFrameDuration(int, Size). These are used to determine the maximum frame rate / minimum frame duration that is possible for a given stream configuration.

Specifically, the application can use the following rules to determine the minimum frame duration it can request from the camera device:

Let the set of currently configured input/output streams be called S. Find the minimum frame durations for each stream in S, by looking it up in android.scaler.streamConfigurationMap using getOutputMinFrameDuration(int, Size) (with its respective size/format). Let this set of frame durations be called F. For any given request R, the minimum frame duration allowed for R is the maximum out of all values in F. Let the streams used in R be called S_r. If none of the streams in S_r have a stall time (listed in getOutputStallDuration(int, Size) using its respective size/format), then the frame duration in F determines the steady state frame rate that the application will get if it uses R as a repeating request."

Community
  • 1
  • 1
TobiasWeis
  • 491
  • 6
  • 14
  • If you're capturing as JPEG, you need to wait for the encoder to compress each frame. Capturing as YUV_420_888 takes the raw camera image without compression. You could potentially try running JPEG compression on other threads, but then you will probably find that you overwhelm the thermal limits of the CPU in under a minute of running. Are you sure that even the hardware JPEG encoder is capable of running at 30fps with the chosen resolution? It sounds like it isn't. Your options seem to be: lower the resolution or framerate. – BitBank Jan 10 '17 at 17:07
  • Please see Edit2, it was too long for a comment. From this statement and the above values my understanding is that 30fps jpeg should be possible. – TobiasWeis Jan 10 '17 at 23:34
  • It sounds like you read the docs correctly, but it's not really clear if you can do full framerate JPEG captures. I think your experience with it shows that it won't deliver the desired framerate. – BitBank Jan 11 '17 at 00:09

2 Answers2

2

The JPEG output is by way not the fastest way to fetch frames. You can accomplish this a lot faster by drawing the frames directly onto a Quad using OpenGL.

For burst capture, a faster solution would be capturing the images to RAM without encoding them, then encoding and saving them asynchronously.

On this website you can find a lot of excellent code related to android multimedia in general.

This specific program uses OpenGL to fetch the pixel data from an MPEG video. It's not difficult to use the camera as input instead of a video. You can basically use the texture used in the CodecOutputSurface class from the mentioned program as output texture for your capture request.

Emiswelt
  • 3,909
  • 1
  • 38
  • 56
  • What exactly do you mean by "You can accomplish this a lot faster ..." ? This will not change the time needed to acquire or save images, right? It's just a way to show them faster, or am I missing something? The idea with RAM and manual encoding might be fine for bursts, but I am looking for a way to capture sequences at stable framerates for a longer duration of time (at least 10 min). – TobiasWeis Jan 17 '17 at 10:20
  • 1
    This is about getting the RGB data in your application as fast as possible for further processing or custom saving. Decoding YUV and re-encoding as JPEG is slow. I've been using the above approach with a 3rd party JPEG encoder yielding very good results. However, if you want to save a sequence of images for a long time, why don't you record a video? It's basically made for that use case, keeping performance and memory consumption in mind. – Emiswelt Jan 17 '17 at 12:30
2

A possible solution I found consists of using and dumping YUV without encoding it as JPEG in combination with a micro Sd-card that is able to save up to 95Mb per second. (I had the misconception that YUV images would be larger, so with a cellphone that has full support for the camera2-pipeline, the write speed should be the limiting factor.

With this setup, I was able to achieve the following stable rates:

  • 1920x1080, 15fps (approx. 4Mb * 15 == 60Mb/sec)
  • 960x720, 30fps. (approx. 1.5Mb * 30 == 45Mb/sec)

I then encode the images offline from YUV to PNG using a python script.

TobiasWeis
  • 491
  • 6
  • 14