D7aa51891d85234354c7a52884d93c6f
Android 端相机相关开发经验分享

本文主要分享下Android端相机相关开发的经验。

众所周知,Android平台不仅系统碎片化严重,而且不同手机的硬件配置差异导致开发某些模块的时候坑比较多,相机模块就是其中之一。为什么呢?首先,Android系统目前已经提供了两套Camera API,其中Camera 2 API是从Android 5.0(API Level 21)开始提供的。你可能会想了,那岂不是现在市面上很多机型都可以使用Camera 2 API啦?然而并不是,原因就是下面要说的第二点,很多Android手机对Camera 2 API的支持都不到位,即使是很多现在刚发的新机,它们有些依然只支持老的Camera API!这就导致做相机开发的时候不得不根据手机的实际情况切换不同的Camera API。

很显然,自己从零开始构建这么一个Camera模块是比较困难的,这里推荐Google提供的一个非官方库cameraview,CameraView这个项目可以帮助Android开发者快速创建一个可以适配不同Android系统和不同Android设备,并且包含各种基本功能的相机界面,它的使用正如它的说明文档中那样,引入一个自定义的CameraView,其他一切和Camera有关的事情都由它来处理。如果你的需求是相机预览、切换前后摄像头、切换闪光灯、切换预览图片的比例以及拍照等功能的话,那么这款小巧的库是一个不错的选择。

既然已经有cameraview这个轮子了,那这篇文章是不是就完结了?图森破!前面提到过,这个库是非官方库,所以它已经有很长时间没有更新了,issues中堆了很多已知bug竟然没人去解!但是,又能怎样呢?还不是只能原谅它,难不成要自己撸一个?(看完cameraview的代码你就知道撸一个这个库多么不容易,需要很熟悉Camera API和Camera 2 API,而且要适配那么多机型也确实是困难啊,一个版本迭代的时间根本做不完呐)

言归正传,这次自己做相机模块的需求开发之前调研了几个轮子,最终还是决定使用cameraview这个库,因为它比较小巧简洁,没有多余的废代码或者废功能,也方便我自己定制相机界面。Github上还有几个star特别高的Camera模块封装,比如CameraKit-Android,但是个人感觉有点复杂了,连视频录制的功能都有了,可能不适用于小场景下界面和功能上的定制。

本文主要说的是自己在做相机模块需求或者说使用cameraview的过程中遇到了哪些问题以及相应的解决方案,最终我对cameraview进行了一番enhancement,感兴趣可以看下这个库CameraView,主要改进的点已经在README文档中说明了,可能最有用的是补齐重要路径的log以及修复几个上线后的crash bug吧。

1. 简述cameraview组件的设计

通过阅读cameraview组件的源码可知,内部设计如下图表所示:

img

img

其中的核心类是自定义的CameraView组件,它支持通过xml来设置摄像头、宽高比、闪光灯等属性,相机相关的各项工作实际上是通过PreviewImplCameraViewImpl这两个抽象类来完成的。

PreviewImpl是用来实现相机预览的,内部可能是用SurfaceView或者TextureView来实现,所以它的实现子类有两个,TextureViewPreviewSurfaceViewPreview。因为TextureView是从Android 4.0(API level 14)开始才有的(TexturView算是SurfaceView的一个增强版),所以在Android 4.0之后使用的是TextureViewPreview,在Android 4.0之前只能使用SurfaceViewPreview

CameraViewImpl是用来实现相机开启、设置相机参数以及实现各种相机功能的核心类,根据API level的不同分为三个实现子类,Camera1Camera2Camera2Api23,其中Camera2是为Android 5.0(API level 21)及以上系统提供的,Camera2Api23继承自Camera2,是为Android 6.0(API level 23)及以上系统提供的。

PreviewImplCameraViewImpl的创建代码如下:

img

img

搞清楚了前面的图表再去阅读cameraview的源码就清晰很多了,其他的类都是围绕着CameraView而展开的。

Size就是描述宽和高,例如800x600、400x300或者640x360等;
AspectRatio就是描述Size的宽高比,例如800x600和400x300这两个Size都是4:3,但是640x360是16:9;
SizeMap就是维护AspectRatioSize的映射列表,例如{"4:3": {800x600, 400x300}, "16:9": {640x360}} 这种形式;
DisplayOrientationDetector就是用来监测相机界面屏幕旋转,然后通知相关组件应对屏幕旋转的变化,例如对预览画面进行调整。

2. 关于Camera1和Camera2的选择

下面详细说下Camera1和Camera2的选择问题,它实际上并不是那么简单地根据API level然后选择创建对应的CameraViewImpl的实现子类就可以了。这里还有一个小细节,那就是如果是选择了Camera2,但是在启动相机的时候发现这个手机对Camera2的支持很弱怎么办?从源码来看,这个时候cameraview会自动将它降级为Camera1,然后使用之前设置的相机参数尝试重新启动相机。这种情况在很多手机上都存在,从我手头上测试的机型来看,小米 5/4c、Vivo X7、Meizu MX6/Pro6、Galaxy S4、Huawei H60-L11等机型都是这样子的(后面有表格记录了该数据)。

img

img

看到这段代码的时候我先是一愣,哟嚯,还有这种操作,666,转瞬一想,微微一笑,因为我发现这段代码很明显是可以优化的。首先,PreviewImpl之前是创建好了的,这里切换CameraViewImpl是不需要改变PreviewImpl的,所以这里没有必要重新调用createPreviewImpl方法;其次,对于某个手机来说,如果它是Android 5.0以上的系统,但是对Camera 2 API的支持就是很差怎么办?如果按照这段代码的逻辑,将导致这个手机每次启动相机的时候都会先用Camera2试一次,发现不行再用Camera1试一次,很明显这样会减慢相机的启动速度。其实,我们只要记录下这个手机上是否之前使用Camera2启动失败转而使用Camera1启动成功的事件,如果有这个记录的话,那么选择CameraViewImpl的时候就直接使用Camera1,不要再用Camera2了!哈哈,真是机智如我 😎

相应的修改已经体现在我改进之后的CameraView库中,大致代码如下:

img

img

3. AspectRatio的选择

下面看下AspectRatio的选择问题,前面提到AspectRatio实际上就是图像的宽高比,可能是4:3,也可能是16:9,也可能是其他的比例。另外,我们还需要知道相机模块这里有好几个地方需要设置宽高比,这里建议阅读Android相机开发那些坑这篇文章,其中详细解析了下面的三个尺寸之间的关系:

SurfaceView/TextureView尺寸:即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里SurfaceView/TextureView显示的预览图像暂且称作手机预览图像。在CameraView组件的源码中有个属性adjustViewBounds,如果设置为false的话,那么它就会铺满CameraView组件所占的空间,如果设置为true的话,那么会根据AspectRatio的设置按照这个宽高比显示预览图像。

Previewsize:相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。

Picturesize:相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。

为了保证相机模块的显示和工作正常,通常建议上三个尺寸的宽高比是一样的,如果比例不一致的话就可能导致图像变形,而且这个比例最好是4:3或者16:9这样比较普遍支持的比例,否则输出结果千奇百怪,例如华为H60-L11这款手机,它就不支持输出16:9这个比例的图片,但是好在4:3这个比例还是支持的。

在细读了cameraview原始的AspectRatio、Previewsize和Picturesize的尺寸选择的代码之后,我觉得这块的代码不够严谨,例如输出图像的大小默认就是这个比例下能够输出的最大大小。

不过老实说,这块代码的确是不好写,因为不同应用的需求不同,例如我这边产品要求输出图片最好是1920x1080这个大小(16:9),那么我就会优先选择16:9这个比例,而不是cameraview中默认的4:3这个比例。所以这里我修改了cameraview原始的AspectRatio的选择以及Previewsize和Picturesize的选择的代码,让CameraView优先使用16:9这个比例,不支持的话那就使用4:3这个比例,在支持16:9这个比例的时候优先使用1920x1080这个输出图像大小,如果不支持的话那就尝试其他的大小,在4:3这个比例下的逻辑类似,大致代码如下:(不同应用要根据自己的需求修改哦)

img

img

img

img

下表是我利用一些测试手机收集得到的数据,从表格数据中不难看出,除了Google的最新亲儿子Pixel之外,其他手机对Camera 2 API的支持都比较弱,导致要切换到Camera1。另外,大部分手机都支持16:9的图像比例,而且大部分手机也都支持输出1920x1080这个大小的图像,但是有些手机不支持从而选择了1280x720这个输出大小,甚至选择了4:3这个比例下的2048x1536这个输出大小。

img

img

[注1:当时收集数据的时候没有去注意Preview Picture Size,所以这一栏基本为空。其中Meizu MX 6为什么是从一个大小变到另一个大小呢?因为当时自己的比例和尺寸选择策略导致预览图像大小是960x540,这个大小导致预览画面非常模糊,后来debug发现了这个问题,于是想办法调整策略使其变成1920x1080,调整后显示就不再模糊啦]

[注2:不过即使是保证了三个尺寸的比例是一致的,在某些手机上还是会出现一些奇怪的现象,比如cameraview的issues列表中的这个这个,也就是保存的图片和预览时看到的图片不一样!这个现象我在一台华为荣耀手机上必现,暂时还没有很好的解决方案,好在问题机型并不多,可以延期解决]

4. 相机拍照

top Created with Sketch.