Android Camera 2 预览尺寸和设备纵横比

Posted

技术标签:

【中文标题】Android Camera 2 预览尺寸和设备纵横比【英文标题】:Android Camera 2 preview size and devices aspect ratio 【发布时间】:2016-09-22 21:38:47 【问题描述】:

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

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

这似乎是设备支持的最大预览尺寸。我不确定此尺寸是否适用于所有设备并适合其设备的纵横比而不会被拉伸。有人知道吗?

【问题讨论】:

【参考方案1】:

如果您的相机分辨率、纹理视图和设备的显示尺寸不同,则您必须调整尺寸。为此,您必须将 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));
    

【讨论】:

我在我的应用程序中实现了同样的逻辑。你可以在这里查看:play.google.com/store/apps/… 能否请您发布整个代码.. 我不明白在哪里打电话setAspectRatioTextureView 还在拉扯。 @iMDroid 更改分辨率后,更改后再次执行关闭相机,然后再次打开。 但正如你所说,我在 'manager.openCamera()' 之前调用它,所以关闭它没有任何意义。【参考方案2】:

可能存在这种方法会失败的极端情况,但我对你的问题没有完美的答案。

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

查看Google API demos for the Camera 2,我发现了一些示例代码,它们应该对您有所帮助,以确保它能够正确适应所有屏幕尺寸:

/**
 * Given @code choices of @code Sizes 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

您还应该查看整个 Camera2BasicFragment.java 和 AutoFitTextureView.java 类以正确实施。

【讨论】:

我有一些设备进行测试,我使用了谷歌示例,但它在某些设备上效果不佳,相机预览仍在全屏拉伸。但是当我使用map.getOutputSizes(SurfaceTexture.class)[0] 时,它可以在所有设备上完美运行。 @TrungNVT 但是你不能获得全屏预览......对吗? @TrungNVT 使用相同的 map.getOutputSizes(SurfaceTexture.class)[0] 但预览仍然在垂直和水平方向拉伸有什么想法吗?【参考方案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);
        
    

非常感谢任何反馈;)

最佳男主

【讨论】:

你的函数setMeasuredDimension是在哪里定义的?【参考方案4】:

更改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));
        
    

【讨论】:

我真的不明白...你在 any 的情况下打电话给setMeasuredDimension(width, height)。您根本不需要覆盖它,这使得AutoFitTextureView 成为普通的TextureView

以上是关于Android Camera 2 预览尺寸和设备纵横比的主要内容,如果未能解决你的问题,请参考以下文章

如何拍摄与预览尺寸相同纵横比的照片?

Android Camera2 以 4:3 纵横比预览和捕捉,但宽度 > 高度

即使匹配视频大小和预览大小纵横比,MediaRecorder 也会失败

确保相机预览大小/纵横比与生成的视频相匹配

4:3 平板电脑上的 Android 相机预览纵横比

计算最佳相机预览尺寸的正确方法保持纵横比