14

I'm implementing Camera 2 API in my project. I'm using TextureView and these line of codes to set the camera fullscreen preview size:

StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];

This seems to be the largest preview size that device support. I'm not sure if this size works with all devices and fit its device's aspect ratio without being stretched. Does anyone know?

pRaNaY
  • 24,642
  • 24
  • 96
  • 146
TrungNVT
  • 671
  • 2
  • 6
  • 8

4 Answers4

12

If your Camera resolutions , texture view and your device's display dimensions are not same then you have to adjust the dimensions. For that you have to put your TextureView inside of FrameLayout. Below Code is applicable to all the devices with various Display resolutions.

Take your Display Dimetions if you are previewing in full screen.Take int DSI_height, int DSI_width global variable.

    DisplayMetrics displayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    DSI_height = displayMetrics.heightPixels;
    DSI_width = displayMetrics.widthPixels;

select your required resolutions from Camera2 API and assign to Size imageDimension, Take private Size imageDimension globally and use

setAspectRatioTextureView(imageDimension.getHeight(),imageDimension.getWidth());

and use below logic

private void setAspectRatioTextureView(int ResolutionWidth , int ResolutionHeight )
    {
        if(ResolutionWidth > ResolutionHeight){
            int newWidth = DSI_width;
            int newHeight = ((DSI_width * ResolutionWidth)/ResolutionHeight);
            updateTextureViewSize(newWidth,newHeight);

        }else {
            int newWidth = DSI_width;
            int newHeight = ((DSI_width * ResolutionHeight)/ResolutionWidth);
            updateTextureViewSize(newWidth,newHeight);
        }

    }

    private void updateTextureViewSize(int viewWidth, int viewHeight) {
        Log.d(TAG, "TextureView Width : " + viewWidth + " TextureView Height : " + viewHeight);
        textureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
    }
Ronak Patel
  • 130
  • 1
  • 10
6

There might be edge cases where that approach would fail, but I don't have a perfect answer to your question why.

In contrast, I have a proper approach on how to implement a version that will most certainly work:

Looking at the Google API demos for the Camera 2, I found some sample code that should be helpful to you to make sure it will fit all screen sized correctly:

/**
 * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
 * is at least as large as the respective texture view size, and that is at most as large as the
 * respective max size, and whose aspect ratio matches with the specified value. If such size
 * doesn't exist, choose the largest one that is at most as large as the respective max size,
 * and whose aspect ratio matches with the specified value.
 *
 * @param choices           The list of sizes that the camera supports for the intended output
 *                          class
 * @param textureViewWidth  The width of the texture view relative to sensor coordinate
 * @param textureViewHeight The height of the texture view relative to sensor coordinate
 * @param maxWidth          The maximum width that can be chosen
 * @param maxHeight         The maximum height that can be chosen
 * @param aspectRatio       The aspect ratio
 * @return The optimal {@code Size}, or an arbitrary one if none were big enough
 */
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
        int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {

    // Collect the supported resolutions that are at least as big as the preview Surface
    List<Size> bigEnough = new ArrayList<>();
    // Collect the supported resolutions that are smaller than the preview Surface
    List<Size> notBigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
        if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                option.getHeight() == option.getWidth() * h / w) {
            if (option.getWidth() >= textureViewWidth &&
                option.getHeight() >= textureViewHeight) {
                bigEnough.add(option);
            } else {
                notBigEnough.add(option);
            }
        }
    }

    // Pick the smallest of those big enough. If there is no one big enough, pick the
    // largest of those not big enough.
    if (bigEnough.size() > 0) {
        return Collections.min(bigEnough, new CompareSizesByArea());
    } else if (notBigEnough.size() > 0) {
        return Collections.max(notBigEnough, new CompareSizesByArea());
    } else {
        Log.e(TAG, "Couldn't find any suitable preview size");
        return choices[0];
    }
}

Source

Also you should take a look at the whole Camera2BasicFragment.java and AutoFitTextureView.java classes for proper implementation.

anthonymonori
  • 1,754
  • 14
  • 36
  • I have some devices for testing, I used the google sample but it doesn't work well on some devices, the camera preview is still being stretched at fullscreen. But when i use ```map.getOutputSizes(SurfaceTexture.class)[0] ``` It works perfectly on all devices. – TrungNVT May 26 '16 at 09:27
  • @TrungNVT but then you must not b getting the full screen preview.. right? – pankaj khedekar May 17 '17 at 07:49
  • @TrungNVT using same map.getOutputSizes(SurfaceTexture.class)[0] but still preview is stretched in vertical and horizontal both any idea ? – Erum Jan 09 '20 at 11:22
3

I solved this problem via a different approach. I get the screen width and height and calculate how much wider or higher the preview would have to be to fill the whole screen and keep aspect ratio. It works pretty well for me without any distortions.

Add a class member variable:

public DisplayMetrics mMetrics = new DisplayMetrics();

Use the following as onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    if (0 == mRatioWidth || 0 == mRatioHeight) {
        setMeasuredDimension(width, height);
    } else {
        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(mMetrics);
        double ratio = (double)mRatioWidth / (double)mRatioHeight;
        double invertedRatio = (double)mRatioHeight / (double)mRatioWidth;
        double portraitHeight = width * invertedRatio;
        double portraitWidth = width * (mMetrics.heightPixels / portraitHeight);
        double landscapeWidth = height * ratio;
        double landscapeHeight = (mMetrics.widthPixels / landscapeWidth) * height;

        if (width < height * mRatioWidth / mRatioHeight) {
            setMeasuredDimension((int)portraitWidth, mMetrics.heightPixels);
        } else {
            setMeasuredDimension(mMetrics.widthPixels, (int)landscapeHeight);
        }
    }
}

Any feedback is greatly appreciated ;)

Best M

Michel
  • 303
  • 2
  • 15
2

Change the AutoFitTextureView.java file and set value like below:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    if (0 == mRatioWidth || 0 == mRatioHeight) {
        setMeasuredDimension(width, height);
    } else {
        if (width < height * mRatioWidth / mRatioHeight) {
            setMeasuredDimension(width, height);
            Log.d("rlijeolid1",String.valueOf(width)+"\t"+String.valueOf(height));
        } else {
            setMeasuredDimension(width , height);
            Log.d("rlijeolid2",String.valueOf(height * mRatioWidth / mRatioHeight)+"\t"+String.valueOf(height));
        }
    }
}
Soni_ji
  • 115
  • 1
  • 12
  • 3
    I really don't get it... You call `setMeasuredDimension(width, height)` in **any** case. You don't need to override it at all, which makes the `AutoFitTextureView` a normal `TextureView` – Rafael T Oct 09 '19 at 10:05