对于摄像头预览变形的一些思考

题记

相信对于大多数同学来说,摄像头预览并不陌生,一个典型的功能就是扫码,只要开发过应用的同学基本都有写过这个功能。大多数情况下,预览界面就是整个页面,这个时候预览基本不存在变形的情况(或者变形的情况很小,肉眼很难分辨出来),但是,我们的产品总是会提出一些不一样的需求,比如说一个固定的顶部扫码预览区域加其他内容,这种时候我们该怎么处理呢?
无标题.png

处理方案

假设这部手机的分辨率为1080×1920,扫码框大小为1080×600,那么我们可能会以以下方式去处理这个问题。

1.寻找最佳的分辨率

这个也是大部分同学想到的处理方案,那我们就按这个思路去解决这个问题,以下是核心代码。

// 相机图像默认分辨率,都是横屏(即宽>高)
List sizes = cameraWrapper.camera.getParameters().getSupportedPreviewSizes();
if (sizes == null) return;
int w, h;
// 扫描框宽高,我们要将宽高设为横屏模式
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        w = getWidth();
        h = getHeight();
} else {
        w = getHeight();
        h = getWidth();
}

double targetRatio = w * 1.0 / h;
// 最佳摄像头尺寸
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
double aspectTolerance = Double.MAX_VALUE;
 int targetHeight = h;

 // 获取最佳摄像头尺寸
for (Camera.Size size : sizes) {
        double ratio = size.width * 1.0 / size.height;
        if (Math.abs(ratio - targetRatio) > aspectTolerance) continue;
        if (Math.abs(size.height - targetHeight) <= minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
                aspectTolerance = Math.abs(ratio - targetRatio);
        }
}

以上运行结果我们会得到1080×1080的尺寸,和我们1080×600的预期结果差距还是很大的,然后我们预览摄像头可以用SurfaceView,TextureView等,这里我就不贴代码了。我们会发现预览的界面变形很严重,和我们所想的结果是不一样的,这个方案可以排除。

2.投机取巧式

这个方案是其他内容的布局覆盖一部分扫码预览区域,比如我们用1方案得到的摄像头尺寸设置预览区域为1080×1080,然后其他区域将部分扫码预览区域覆盖,只展示1080×600,所以说是一种投机取巧的方案。这种方案能够解决预览区域变形的情况,实现起来也很简单,有个缺点,就是覆盖的区域会出现一闪一闪的情况,大多数情况下也能满足产品的需求,但是如果遇到个别有强迫症的产品,那这种方案也解决不了问题。这里就不贴具体的代码和图片了,有兴趣的同学可以自己去试试。

3.将预览的画面进行裁剪

这个方案是在1方案的基础上进行处理的,当然你可以不进行1方案处理,但是裁剪以及预览画面展示的时候不能达到最好的效果。裁剪的方案还是很多的,例如直接裁剪,大体过程就是获取画面的每一帧进行裁剪然后展示,先不说我们如何裁剪,裁剪的性能怎样,但是由于摄像头预览的图像格式为YUV420P,而我们展示则是Bitmap,因此总是有YUV420P转Bitmap的过程,这种转化过程性能消耗是非常高的,基本上可以排除;另一种是使用OpenGL进行裁剪,这种方法对性能要求不高,但是需要我们对OpenGL有所了解(有的同学说我可以直接百度复制别人的代码),这是一种可行的方案,也解决了方案2的问题,但是处理起来比较麻烦。这里就不贴具体的代码和图片了,有兴趣的同学可以自己去试试。

4.从onMeasure方法入手

这个方案是在1方案的基础上进行处理的,当然你可以不进行1方案处理,但是预览画面展示的时候不能达到最好的效果。对于大部分同学来说这个方法是知道的,没错,就是自定义View时要重写的方法之一,对这个方法不熟悉的同学可以自行百度了解下。这里的核心处理就是根据控件的尺寸获取最佳的摄像头尺寸,然后根据摄像头的尺寸重新测量控件的尺寸,使其宽高按摄像头比例进行缩放,也就是让1080×600变为1080×1080,也就是绘制的时候是按1080×1080去绘制,而展示的只有1080×600这部分。这也是个可行的方案,解决了方案2的问题,而且处理起来也比较简单。下面是核心代码,效果过这里就不贴了,有兴趣的同学可以自己试试

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    // 获取最佳摄像头尺寸
    setOptimalPreviewSize(width, height);
    int o = getResources().getConfiguration().orientation;
    float ratio = o == Configuration.ORIENTATION_PORTRAIT ? mPreviewHeight * 1f /mPreviewWidth : mPreviewWidth * 1f / mPreviewHeight;
    float r = width * 1f / height;
    if (ratio < r) height = (int) (width / ratio + 0.5f);
    else width = (int) (height * ratio + 0.5f);
    super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}

总结

摄像头预览变形这个问题由于预览尺寸只有固定的几个,可以说历史遗留问题,百度上的大都是方案1的解决办法,但是实际开发中就如我这个例子是没法用这个方案去解决的,我这里只是提供了几种解决这个问题的思路供大家参考,希望对你能起到一定的帮助,当然,同学们可能有自己更好的解决方案。

你可能感兴趣的:(对于摄像头预览变形的一些思考)