安卓相机2预览大小和设备宽高比问题

14

我正在我的项目中实现相机2 API。我正在使用TextureView和以下代码来设置相机全屏预览大小:

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

看起来这是设备支持的最大预览尺寸。我不确定这个尺寸是否适用于所有设备,并且是否符合其设备的纵横比而不被拉伸。有人知道吗?

4个回答

12
如果您的相机分辨率、纹理视图和设备的显示尺寸不同,则需要调整尺寸。为此,您必须将TextureView放置在FrameLayout中。下面的代码适用于具有不同显示分辨率的所有设备。
如果您在全屏预览,请获取您的显示尺寸。使用int DSI_heightint DSI_width全局变量。
    DisplayMetrics displayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
    DSI_height = displayMetrics.heightPixels;
    DSI_width = displayMetrics.widthPixels;

从Camera2 API中选择所需的分辨率并分配给Size imageDimension,将private Size imageDimension全局使用。

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

并使用以下逻辑

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));
    }

1
我已经在我的应用程序中实现了相同的逻辑。您可以在此处检查它:https://play.google.com/store/apps/details?id=ronakpatel1311.androidcameraapi2 - Ronak Patel
2
请您能否发布完整的代码?我不明白在哪里调用setAspectRatioTextureView - iMDroid
仍然被拉伸。 - iMDroid
@iMDroid 当您更改分辨率后,请再次执行关闭相机,然后再次打开它。 - Ronak Patel
但正如你所说,我在“manager.openCamera()”之前调用它,因此关闭它没有任何意义。 - iMDroid
如果相机预览不是全屏,这仍然有效吗? - rharding

6

可能会有一些边缘情况,这种方法会失败,但我没有一个完美的答案来回答你的问题为什么。

相比之下,我有一个正确的方法来实现一个肯定能工作的版本:

查看Google API Camera 2演示,我找到了一些示例代码,应该对您有所帮助,以确保它在所有屏幕大小上都适合。

/**
 * 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];
    }
}

来源

同时,您应该查看整个Camera2BasicFragment.javaAutoFitTextureView.java类以进行适当的实现。


我有一些用于测试的设备,我使用了谷歌提供的示例,但在某些设备上它不能很好地工作,相机预览仍然会被拉伸至全屏。但是,当我使用map.getOutputSizes(SurfaceTexture.class)[0]时,它可以在所有设备上完美运行。 - TrungNVT
@TrungNVT 但是你应该没有得到全屏预览,对吧? - pankaj khedekar
@TrungNVT 使用相同的 map.getOutputSizes(SurfaceTexture.class)[0],但预览仍然在垂直和水平方向上被拉伸,有什么想法吗? - Erum

3

我通过不同的方法解决了这个问题。我获取屏幕的宽度和高度,并计算出预览需要多宽或多高才能填满整个屏幕并保持纵横比例。这种方法对我来说非常有效,没有任何失真。

添加一个类成员变量:

public DisplayMetrics mMetrics = new DisplayMetrics();

请使用以下代码作为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);
        }
    }
}

任何反馈都非常受欢迎;)
最好的M

你的函数 setMeasuredDimension 定义在哪里? - JCutting8

2

修改AutoFitTextureView.java文件,将值设置如下:

@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));
        }
    }
}

3
无论什么情况,都要调用setMeasuredDimension(width, height)。你根本不需要覆盖它,这使得AutoFitTextureView成为一个普通的TextureView - Rafael T

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接