在Android相机开发实际开发过程中遇到了不少问题,在网上找了这些资料,但是感觉如果没有经历过Android相机开发开发,直接看这些还是有点太抽象,建议参考一些代码来学习下面的内容 由于之前没有接触过Android相机开发,所以在整个开发过程中踩了不少坑,费了不少时间和精力。这篇文章总结了Android相机开发的相关知识、流程,以及容易遇到的坑,希望能帮助今后可能会接触Android相机开发的朋友快速上手,节省时间,少走弯路。 一.Android中开发相机应用的两种方式Android系统提供了两种使用手机相机资源实现拍摄功能的方法,
这篇文章主要是从如何使用相机API来定制自定义相机这个方向展开的。 二.相机API中关键类解析通过相机API实现拍摄功能涉及以下几个关键类和接口: Camera:最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:
SurfaceView:用于绘制相机预览图像的类,提供给用户实时的预览图像。普通的view以及派生类都是共享同一个surface的,所有的绘制都必须在UI线程中进行。而surfaceview是一种比较特殊的view,它并不与其他普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同时处理其他交互逻辑,因此对view的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。 SurfaceHolder:控制surface的一个抽象接口它能够控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview通过getHolder()方法获得surfaceholder 实例,通过后者管理监听surface 的状态。 SurfaceHolder.Callback接口:负责监听surface状态变化的接口,有三个方法:
三.自定义相机的开发过程定制一个自定义相机应用,通常需要完成以下步骤,其流程图如图1所示:
四. 开发过程遇到的一些坑下面再讲讲我在开发自定义相机时踩过的一些坑: 1.相机预览方向适配问题的产生1.1、相机的安装方向如何获取?Android官方提供orientation这个属性:表示相机预览图像顺时针旋转orientation度后才能与到设备自然方向一致。 假设设备是竖屏显示。后置相机传感器是横屏安装的。 你面向屏幕时,如果后置相机传感器所采集的图像的上边和设备自然方向的右边是平行的,则后置相机的orientation是90。 如果前置相机传感器所采集的图像的上边和设备自然方向的右边是平行的,则前置相机的orientation是270。 这个值,不同的设备有所差异,但大多数都是这样的值。 1.2、Activity设为竖屏时,SurfaceView预览图像为什么是逆时针旋转90度?说明这个问题之前,先介绍下Android手机上几个方向的概念: 屏幕方向:在Android系统中,屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。 图像传感器(Image Sensor)方向:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,如下图2所示,坐标原点位于手机横放时的左上角,即与横屏应用的屏幕X方向一致。换句话说,与竖屏应用的屏幕X方向呈90度角。 相机的预览方向:将图像传感器捕获的图像,显示在屏幕上的方向。在默认情况下,与图像传感器方向一致。在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器方向一致。
注意:设置预览方向并不会改变拍出照片的方向 如图3所示(红色箭头表示相机预览的x轴方向,蓝色箭头表示屏幕的x轴方向)。 下面是Camera.setDisplayOrientation的注释文档:
注释中的第二段,描述了这个API修改的仅仅是Camera的预览方向而已,并不会影响到PreviewCallback回调、生成的JPEG图片和录像文件的方向,这些数据的方向依然会跟图像Sensor的方向一致。 看到这里,相信你肯定有疑问,按照这样旋转的话,UI预览图应该是顺时针旋转90度才对啊,但为什么实际上是逆时针旋转 在开始分析前,先熟悉一个概念 自然方向(natrual orientation) 每个设备都有一个自然方向,手机和平板的自然方向不同。Android:screenOrientation的默认值unspecified即为自然方向。 下图是MI3手机的屏幕“自然”方向和后置相机的图像传感器方向。 后置相机的orientation是90 1、图像传感器获得到图像后,就知道了这幅图像每个坐标的像素值,但是要显示到屏幕上,就要按照屏幕的坐标系来显示,于是就需要将图像传感器的坐标系逆时针旋转90度,才能显示到屏幕的坐标系上, 1.3、 前置摄像头的镜像效果Android相机硬件有个特殊设定,就是对于前置摄像头,
以MI3为例 通过程序调用取到的后置相机的orientation是90,前置相机的orientation是270。 orientation可以理解为图像传感器方向顺时针旋转到屏幕自然方向的角度。 下图所示是MI3手机的后置相机和前置相机对准同一个小人,后置前置相机采集到的图像、前置相机预览的图像。 后置 对于后置相机,相机预览的图像和相机采集到的图像是一样的。只需要旋转后置相机orientation度,即90度即可和屏幕方向保持一致; 前置 对于前置相机来说,相机预览的图像和相机采集到的图像是镜像关系, 注意下图小人头发在前置采集的图像和预览图像中头发是相反的。 因此在MI3手机上做竖屏应用时, 采集的图像:顺时针旋转270度后,与屏幕方向一致 预览的图像:顺时针旋转90度后,与屏幕方向一致.(因为底层相机对采集的图像做了镜像处理,所以只需转90度) 1 、图像传感器获得到图像后,就知道了这幅图像每个坐标的像素值,但是要显示到屏幕上,就要按照屏幕的坐标系来显示,于是就需要将图像传感器的坐标系顺时针旋转90度,才能显示到屏幕的坐标系上,于是看到的图像顺时针旋转了90度 2、也就是说前置摄像头获取到的图像(未经镜像处理)需要顺时针旋转270,才能和手机的自然方向一致(上图第二行) 3、但是在预览的时候,做了镜像处理后,只需要顺时针旋转90度,就能和手机的自然方向一致。(如上图第三行): 下图显示了,屏幕的坐标系和前置摄像头的坐标系 下图简单显示了,横屏和竖屏的情况 此外,由于拍摄图像并没有做水平翻转,所以对于前置摄像头拍出来的照片,用户会发现跟预览时所见的是左右翻转的。可以根据自己的需求进行处理。 1.4、官方推荐的相机预览方向适配做法通过orientation属性的含义可以知道,我们可以用它和应用的方向来做相机预览方向的适配,下面代码是官方网站推荐的。 但并不是所有手机的orientation值都靠谱,比如VIVO V1手机第一次获取后置相机的CameraInfo的orientation值是90,而当执行了mCamera = Camera.open();之后再获取CameraInfo的orientation值就是0,而且以后获取的都是 0 ,除非重启手机。无论是这款手机上的哪个应用,只要执行了一次Camera.open()之后,其他所有程序中获取CameraInfo的orientation都是是0。 因此按照此方法做适配不能解决所有手机上的问题。
2、Camera的拍照方向当你点击拍照按钮,得到的图片方向不一定与画面中预览的方向一致,这是因为拍摄的照片是将图像Sensor采集到的数据直接存储到SDCard上的,因此,Camera的拍照方向与上述的Camera的图像Sensor方向一致。 为了演示这个问题,我用手机的Camera对同一个场景拍了两张照片,第一张是横着拿手机拍的,第二张是竖着拿手机拍的。然后用在电脑上打开得到的图片(实际场景中的杯子是竖着的),效果如下所示: 由此可见,如果横向拿手机拍照,由于正好与Camera的拍照方向一致,因此得到的照片是“正确”的;而竖着拿手机拍照的话,Camera的图像Sensor依然以上面描述的角度在采集图像并存储到SDCard上,所以得到的图片就是右图这样的,因为竖着拿手机正好与图像Sensor的方向相差了90度。由此,大家应该明白了为什么我们用手机拍出的照片经常需要旋转90度才能看到“正确”的画面了吧? 3. SurfaceView预览图像、拍摄照片拉伸变形说明这个问题之前,同样先说一下几个跟相机有关的尺寸。
下面说下我在开发过程中遇到的三种拉伸变形现象:
现象1的原因是SurfaceView和Previewsize的长宽比率不一致。因为手机预览视图的图像是由相机预览图像根据SurfaceView大小缩放得来的,当长宽比不一致时必然会导致图像变形。 后两个现象的原因则是Previewsize和Picturesize的长宽比率不一致所致,查了相关的资料,发现其具体原因跟某些手机相机硬件的底层实现有关。 总之为了避免以上几种变形现象的发生,在开发时最好将SurfaceView、PreviewSize、PictureSize三个尺寸保证长宽比例一致。具体实现可以先通过camera.getSupportedPreviewSizes()和camera.getSupportedPictureSizes()获得相机硬件支持的所有预览和拍摄尺寸,然后在里面筛选出和SurfaceView的长宽比一致并且大小合适的尺寸,通过camera.setPrameters来更新设置。注意:市场上手机相机硬件支持的尺寸一般都是主流的4:3或者16:9,所以SurfaceView尺寸不能太奇葩,最好也设置成这样的长宽比。 4. 各种crash前两个Crash的原因是:相机硬件在聚焦和拍照前必须要保证已经连接到surface,并且开启相机预览,surface有收到预览数据。如果在还没有执行camera. setPreviewDisplay或者未调用camera. startPreview之前,就调用camera.autofocus或camera.takepicture,就会出现这个运行时异常。 对应到自定义相机的代码中,要注意在拍照按钮事件响应中执行camera.autofocus或camera.takepicture前,一定要检验camera有没有设置预览Surfaceview并开启了相机预览。这里有个方法可以判断预览状态:Camera.setPreviewCallback是预览帧数据的回调函数,它会在SurfaceView收到相机的预览帧数据时被调用,因此在里面可以设置是否允许对焦和拍照的标志位。 还有一点要注意,camera.takePicture()在执行过程中会执行camera.stopPreview来获取拍摄帧数据,表现为预览画面卡住,而如果此时用户点击了按钮的话,也就是调用camera.takepicture,也会出现上面的crash,因此在开发时,可能还需要屏蔽拍照按钮的连续点击。 5. 锁屏下相机资源的释放问题为了节省手机电量,不浪费相机资源,在开发的自定义相机里,如果预览图像已不需要显示,如按Home键盘切换后台或者锁屏后,此时就应该关闭预览并把相机资源释放掉。 参考官方API文档,
我们可以在对应的回调函数里,处理相机的相关操作,如连接surface、开启/关闭预览。 至于相机资源释放,则可以放在Acticity的onpause里执行。相应的,要重新恢复预览图像时,可以把相机资源申请和初始化放在Acticity的onResume里执行,然后通过创建surfaceview,将camera和surface相连并开启预览。 但是在开发过程中发现,对于按HOME键切后台场景,程序可以正常运行。对于锁屏场景,则在重新申请相机资源时会发生crash,说相机资源访问失败。那么原因是什么呢?我在代码里增加了调试log, 检查了代码的执行顺序,结果如下: 在自定义相机页面按HOME键时的执行流程:
|
|
来自: lifei_szdz > 《Android应用开发》