关于TextureView显示预览内容变形的总结

前言

这个问题其实在做项目的时候没有遇到过,因为项目代码配流已经很成熟一般不会出现什么问题,但是我之前在学习Camera2的时候,自己写的demo在修改预览比例的时候就会出现预览的内容发生形变的现象。

当时在网上咔咔乱找的语句把那个问题给解决了,但是问题的根因还是没有太理解,正好手握公司的项目代码,正好可以对比学习一下,可以看看问题的根因,以及需要注意的点。

由于涉及到公司代码,所以这篇博客不会以代码为主,而是以分析图解的形式进行编写。

正式开始

这次的文档分析一切以以下这篇帖子作为学习基础

如何解决TextureView显示内容变形的问题 - 简书 (jianshu.com)

原理探寻

根据该篇帖子的理论知识,进行分析项目代码与学习代码之间操作的区别。

当然啦,这篇帖子也会比较简单说一下我看完上面那篇文章的理解,喜欢速通的小伙伴也可以直接看我这篇文章,但是不包看得懂噢。 

首先我们来看看安卓相机从预览到HAL层配流的整个过程,图片也是用的那个帖子中的。主要太直观了,大家伙都好好看看。

HAL层的配流一般都属于BSP那边,而且如果出错,一般相机是点亮不了的,应用层一般都无法排查到什么,但是对于APP、Camera Service我们都可以清晰的查看处理,所以一会的分析也是通过这两个层面进行分析。

观察整个图解,TextureView作为SurfaceTexture和View的结合,把SurfaceTexture中的buffer内容copy到自己的Surface上显示。

TextureView显示的内容是SurfaceTexture创建的output Surface的内容,而SurfaceTexture所创建的Surface的内容来自Camera的output Stream。

其实我们可以简化一下整个预览的流过程,毕竟有些部分是不需要我们去操作的,具体如下图:

看这幅图,有必要对size1进行一个解释一下:首先TextureView是 SurfaceTexture和View的结合,这里的size1其实是指view的尺寸大小,简单理解就是我们在布局文件设置的长宽size。而size2则是SurfaceTexture的size大小。

如果希望TextureView显示内容不变形的话,size1、size2、size3应该比例大小都要一致,当然都相等是最好的,但是比例大小一样至少可以保证拉伸是成比例的,最后的显示效果就不会变形。

其实从多维的角度去理解这三个size会比较直观。

 整一个预览流程流的原理便探索清楚了,接下来我们就可以根据这个原理去思考解决方案。

解决方案

其实整个解决方案围绕的点就是size1、size2、size3尽量做到比例一致。所以这时候我们就要围绕我们代码中主要决定这三个值的语句,多加注意他们的赋值。

//获得textureview的SurfaceTexture
SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
//设置SurfaceTexture的默认大小,即是刚刚上文一直说的size2
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
//把SurfaceTexture中的buffer内容copy到自己的Surface上显示。
mPreviewSurface = new Surface(surfaceTexture);
//通过Java代码创建重复流,这时候就相当于通知底层去创建预览流,也正是这时候决定了size3,传进去的mPreviewSurface决定了size3的大小
mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, jpegImageReaderSurface), new CameraCaptureSession.StateCallback() {

看上去这样好像就能保证了size2与size3相同,其实不然。底层创建预览流并不是传进去的previewSize(上面示例代码中的第二行代码的那个变量size)多大,就创建多大的预览流size3。mCameraDevice.createCaptureSession其实就是通知cameraservice创建output stream,而这个outputstream也是从特定的map中去创建,如果你随便传递一个previewsize,outputstream只会在

cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)

这个map中选择一个跟你previewSize最相近的一个size,所以previewsize就不能随便赋值了,我们在选择previewsize的时候也是需要在CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP中选一个,这样才能最大限度的保证size2与size3一致。

自此size2与size3按照这个逻辑去进行赋值,就能保证他们是一致的。

但是很多时候我们自己的学习代码中出现预览变形的现象都会是size1与size2比例不一致导致的,因为很多时候我们自己的学习代码都是参考网络上的demo,size2与size3的确定基本都是固定的,获取map中size作为previewsize,所以一般不会出现什么问题。

这时候我们其实就需要根据size2动态的修改textureview的view的size大小了。这个动态修改很多种方式实现,其实本质上就是根据比例重新计算textureview的长宽,在项目代码中会进行相对复杂的操作,毕竟是商业嘛,复杂点正常。我这边可以提供一个学习代码的实现方式,通过自定义一个view去动态修改尺寸。

public class AutoFitTextureView extends TextureView {

    private static final String TAG = "AutoFitTextureView";
    private int mRatioWidth = 0;
    private int mRatioHeight = 0;
    public AutoFitTextureView(@NonNull Context context) {
        super(context);
    }

    public AutoFitTextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoFitTextureView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public AutoFitTextureView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (mRatioWidth==0||mRatioHeight==0){
            setMeasuredDimension(width,height);
        }else {
            //横屏
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            }
            //竖屏
            else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }

}

重点就在那个setAspectRatio方法在需要动态修改的时候进行 requestLayout方法,这时候会重新执行onMeasure方法,做到动态修改尺寸大小的效果。

项目代码中的解决方案

在学习代码中,previewsize是动态的选择的,通过逻辑代码选择最适合的size,这样可以做到最大的兼容,但是在原生相机的开发中,因为每一套代码都是为一个机型定制适配的,所以在项目代码中previewsize都是写死的,通过手机硬件条件选择了一个最合适的previewsize进行写死,这样就很好的解决了size2与size3是完全相等的,这时候项目代码又通过自定义预览控件,做到了动态根据size2去修改textureview的size(size1)。

总结

大体就是上面说的这些,其实解决相机预览问题只要抓住一个点就能轻松解决。size1=size2=size3!!!!

使用TextureView预览相机画面可以避免预览画面拉伸等问题,同时也可以提供更加灵活的界面设计。下面是使用TextureView预览相机画面的简单步骤: 1. 在布局文件中添加TextureView控件: ``` <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2. 在相机启动时,将TextureView与相机绑定: ``` // 获取TextureView控件 TextureView textureView = findViewById(R.id.texture_view); // 获取相机实例 Camera camera = Camera.open(); // 获取相机参数 Camera.Parameters parameters = camera.getParameters(); // 获取支持的预览尺寸 List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes(); Camera.Size previewSize = previewSizes.get(0); // 设置TextureView的尺寸 textureView.setAspectRatio(previewSize.width, previewSize.height); // 设置预览回调函数 camera.setPreviewTexture(textureView.getSurfaceTexture()); // 启动相机预览 camera.startPreview(); ``` 在上述代码中,首先获取TextureView控件,然后获取相机实例和相机参数。接着,获取相机支持的预览尺寸,并将TextureView的尺寸设置为预览尺寸。最后,将TextureView的SurfaceTexture对象作为预览回调函数的参数,并启动相机预览。 需要注意的是,使用TextureView预览相机画面需要考虑TextureView的生命周期,避免出现资源泄露等问题。在Activity的onPause()方法中,应该停止相机预览,并释放相机资源。在Activity的onResume()方法中,重新启动相机预览即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值