1

I have a real-time video processing app in Android that captures from camera and display the preview to a TextureView.

The issue is that I want to capture the camera preview at 2k, but screen size is 1080, so TextureView size is max of 1080.

I want to be able to get, TextureView.getBitmap() and get a 2k image, but because TextureView is maxed to the screen size the image is only 1080px.

I found that I could overwrite the TextureView onMeasure() method to force the TextureView size to 2k. This works, however because the screen is 1080, the phone only shows the middle of the 2k image, how to display the whole image?

My 2nd attempt I made the layout size of the TextureView 0 so it was hidden, and added another ImageView to the layout, then from the listener onImageAvailable() I convert the Image to a Bitmap and set it in the ImageView. This works, but is very slow, get a delayed image instead of live camera preview.

I think I need to be able to zoom out the TextureView, or perhaps have 2 texture views and copy from one to the other. But can't get it to work as desired.

Please, any help or suggestions.

protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = CameraConnectionFragment.MINIMUM_PREVIEW_SIZE; height = CameraConnectionFragment.MINIMUM_PREVIEW_SIZE; setMeasuredDimension(width, height); }

The code is similar to, https://github.com/miyosuda/TensorFlowAndroidDemo/tree/master/app/src/main/java/org/tensorflow/demo

The camera code is,

private void openCamera(final int width, final int height) {
        setUpCameraOutputs(width, height);
        configureTransform(width, height);
        final Activity activity = getActivity();
        final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
        manager.openCamera(cameraId, stateCallback, backgroundHandler);
    }

Camera preview code is,

private void createCameraPreviewSession() {
        try {
            final SurfaceTexture texture = textureView.getSurfaceTexture();
            assert texture != null;

            // We configure the size of default buffer to be the size of camera preview we want.
            texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());

            // This is the output Surface we need to start preview.
            final Surface surface = new Surface(texture);

            // We set up a CaptureRequest.Builder with the output Surface.
            previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            previewRequestBuilder.addTarget(surface);

            // Create the reader for the preview frames.
            previewReader =
                ImageReader.newInstance(
                    previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);

            previewReader.setOnImageAvailableListener(imageListener, backgroundHandler);
            previewRequestBuilder.addTarget(previewReader.getSurface());
            zoom(zoom, activity, false);
            // Here, we create a CameraCaptureSession for camera preview.
            cameraDevice.createCaptureSession(
                Arrays.asList(surface, previewReader.getSurface()),
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(final CameraCaptureSession cameraCaptureSession) {
                        // The camera is already closed
                        if (null == cameraDevice) {
                            return;
                        }

                        // When the session is ready, we start displaying the preview.
                        captureSession = cameraCaptureSession;
                        try {
                            // Auto focus should be continuous for camera preview.
                            previewRequestBuilder.set(
                                CaptureRequest.CONTROL_AF_MODE,
                                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            // Flash is automatically enabled when necessary.

                            previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
                            if (isTorchOn) {
                                previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
                            } else {
                                previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
                            }
                            // Finally, we start displaying the camera preview.
                            previewRequest = previewRequestBuilder.build();
                            captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
                        } catch (final CameraAccessException exception) {
                            LOGGER.e(exception, "Exception!");
                        } catch (Exception exception) {
                            Log.wtf("Camera preview", exception);
                        }
                    }
                },
                null);
        } catch (final Exception e) {
            LOGGER.e(e, "Exception!");
        }
James
  • 17,965
  • 11
  • 91
  • 146

2 Answers2

1

You are doing weird things. Generally, you have no control over the max and min preview size. Method

CameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
val supportedSizes: Size[] = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                              .getOutputSizes(SurfaceTexture::class.java)

will return the complete list of all the sized you can use. It is an implementation limitation - you cannot overcome it for a specific textureView implementation of a specific device. All you can do is choose the correct aspect ratio.

Capture sizes will be different and usually much bigger and the list will be bigger also - the issue is that you won't be able to show that in the preview - the captured data will be visible only in the output file/stream.

Although! There are several approaches you can use here in order to achieve what you want.

  1. Modify the preview stream as you want - interpolate(there is plenty of info about that online) to make it larger both size- and quality-wise or just upscale losing quality. (it will still be displayed on the TextureView that is limited by its implementation)
  2. Implement your own version of texture view that does all this magic with OpenGL inside the TextureView - it will be much more performant but relatively harder to implement.
  3. Try to use the output capture stream(or even preview one) as a source for some TextureView that already knows how to show quality pictures - the one from some video player.
  4. Maybe something else.

Personally, I implemented the first two approaches a long time ago and I can say that they are possible and relatively easy to implement. I won't provide code or techniques because it is quite a big amount of code and I don't remember all the methods well enough.

The third approach is also possible and I have seen it in work but never implemented myself(only fixed bugs) (in my case it was implemented on the native side - so maybe you will need C for that.)

I know the answer may not be as direct as you wanted but the task is not that trivial also. Either way, hope it helps.

Pavlo Ostasha
  • 14,527
  • 11
  • 35
  • thanks for the suggestions, for #1, we cannot loos quality, that is the whole point of increasing the resolution, for #2 I will investigate, any hints would be appreciated – James Mar 23 '22 at 13:57
  • for #4, any way to extend TexttureView to display the image fitting to the screen, but be able to access it images as the full 2k, like https://github.com/miyosuda/TensorFlowAndroidDemo/blob/master/app/src/main/java/org/tensorflow/demo/AutoFitTextureView.java – James Mar 23 '22 at 13:59
  • @James I don't know dude. At the time I was googling something like `render video with opengl android` and used info from the links - one link was not enough so I used some combination of some of that info and my own tries and failures in order to achieve that. It is too thin a domain(at least in my line of work) for me to store that info in my memory) – Pavlo Ostasha Mar 23 '22 at 16:31
1

One possible solution i can think is to extend your custom TextureView and override the onMeasure() method as you suggested in your question to force the TextureView size to be 2k like:

@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(2000, 2000);
}

and make the TextureView scrollable in both directions (vertically and horizontally) something like:

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fillViewport="true">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <mypackage.name.MyTextureView
                    android:id="@+id/textureView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />

            </LinearLayout>
        </ScrollView>
    </LinearLayout>
</HorizontalScrollView>

And later retrieve the bitmap with textureView.getBitmap(); in 2k resolution.

MariosP
  • 8,300
  • 1
  • 9
  • 30
  • thanks, I will try that. Any idea how to instead zoom out the texture view so it fits in the screen, but getBitmap() still gives 2k? – James Mar 29 '22 at 13:39
  • @James another solution that i was thinking but i am not sure if its going to work is to stream the camera content into two TextureViews the same time, the one which will been displayed to the user (the zoom out/visible one) and the hidden one which has the 2k resolution. As last alternative if you are familiar with the OpenGL i think you can try the GLSurfaceView which controls everything it is drawn on the screen. – MariosP Mar 29 '22 at 14:58
  • I was thinking of the 2 textureviews as well. I tried having a texture hidden and an image view displayed, but was very slow. Maybe 2 texture views would be faster, but not sure you could preview to both in different resolutions at the same time. – James Mar 29 '22 at 15:21
  • @James I think you can check this: https://stackoverflow.com/questions/22529981/android-camera-preview-on-two-views-multi-lenses-camera-preview and this: https://stackoverflow.com/questions/15019036/how-to-create-multi-lenses-or-preview-using-one-camera-in-android both of them are making multiple previews from the same camera. – MariosP Mar 29 '22 at 16:19