前言
这个问题其实在做项目的时候没有遇到过,因为项目代码配流已经很成熟一般不会出现什么问题,但是我之前在学习Camera2的时候,自己写的demo在修改预览比例的时候就会出现预览的内容发生形变的现象。
当时在网上咔咔乱找的语句把那个问题给解决了,但是问题的根因还是没有太理解,正好手握公司的项目代码,正好可以对比学习一下,可以看看问题的根因,以及需要注意的点。
由于涉及到公司代码,所以这篇博客不会以代码为主,而是以分析图解的形式进行编写。
正式开始
这次的文档分析一切以以下这篇帖子作为学习基础
原理探寻
根据该篇帖子的理论知识,进行分析项目代码与学习代码之间操作的区别。
当然啦,这篇帖子也会比较简单说一下我看完上面那篇文章的理解,喜欢速通的小伙伴也可以直接看我这篇文章,但是不包看得懂噢。
首先我们来看看安卓相机从预览到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!!!!