首页

行业资讯 NEWS

【技术干货】Android直播相机开发详解

172017-02
2017-02-17 15:54拓海浏览:

由于国内手机厂商林立,各家定制系统繁杂以及Android自身的版本多样性和兼容性,给Android上的直播开发带来了特别大的难度和复杂度。其中以自定义相机的开发所要考虑的兼容性和遇到的坑最多。本文主要总结了Android相机开发的相关知识、流程,以及容易遇到的坑,方便对Android上相机开发有兴趣的朋友以及从事直播行业的同事少走弯路,技术共享。

 

 

Android 相机API中关键类解析

 

通过相机API实现拍摄功能涉及以下几个关键类和接口:

Camera:最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:

·      open():获取camera实例。

·      setPreviewDisplay(SurfaceHolder):绑定绘制预览图像的surface。surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。通过surfaceHolder可以将Camera和surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。

·      setPrameters设置相机参数:包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。

·      startPreview():开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。

·      stopPreview():停止预览,关闭camra底层的帧数据传递以及surface上的绘制。

·      release():释放Camera实例

 

SurfaceView用于绘制相机预览图像的类,提供给用户实时的预览图像。普通的view以及派生类都是共享同一个surface的,所有的绘制都必须在UI线程中进行。而surfaceview是一种比较特殊的view,它并不与其他普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同时处理其他交互逻辑,因此对view的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。     

 

surfaceholder:surfaceholder是控制surface的一个抽象接口,它能够控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview通过getHolder()方法获得surfaceholder 实例,通过后者管理监听surface 的状态。   

 

SurfaceHolder.Callback接口:负责监听surface状态变化的接口,有三个方法:

 

·      surfaceCreated(SurfaceHolder holder):在surface创建后立即被调用。在开发自定义相机时,可以通过重载这个函数调用camera.open()、camera.setPreviewDisplay(),来实现获取相机资源、连接camera和surface等操作。

·      surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface发生format或size变化时调用。在开发自定义相机时,可以通过重载这个函数调用camera.startPreview来开启相机预览,使得camera预览帧数据可以传递给surface,从而实时显示相机预览图像。

·      surfaceDestroyed(SurfaceHolder holder):在surface销毁之前被调用。在开发自定义相机时,可以通过重载这个函数调用camera.stopPreview(),camera.release()来实现停止相机预览及释放相机资源等操作。

 

 

自定义相机的开发流程

 

自定义相机开发,通常需要完成以下步骤:

·      检测并访问相机资源 和需要采集的分辨率,如果支持,请求访问相机资源。

·      创建预览类 创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。此类能够显示相机的实时预览图像。(通过setPreviewDisplay接口)

·      建立预览布局 有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起。

·      设置相机采集数据回调接口setPreviewCallback,用于获取相机采集到的具体视频数据(默认为NV21格式),在对数据进行图像旋转或是美颜等前处理将数据推流到服务器。

·      释放相机资源 相机是一个共享资源,必须对其生命周期进行细心的管理。当相机使用完毕后,应用程序必须正确地将其释放,以免其它程序访问使用时,发生冲突。

 

同时需要在应用程序中增加Camera的权限

 

 

相机的开发中遇到的常见问题以及兼容性处理

 

1、相机预览方向问题处理 

 
 

这里首先要理解两个概念: 

图像的Sensor方向:手机Camera的图像数据都是来自于摄像头硬件的图像传感器(Image Sensor),这个Sensor被固定到手机之后是有一个默认的取景方向的。

Camera的预览方向:由于手机屏幕可以360度旋转,为了保证用户无论怎么旋转手机都能看到“正确”的预览画面(这个“正确”是指显示在UI预览界面的画面与你人眼看到的眼前的画面是一致的),Android系统底层根据当前手机屏幕的方向对图像Sensor采集到的数据进行了旋转处理,然后后才送给显示系统,因此,打开Camera应用后,无论怎么旋转手机,你都能看到“正确”的画面,Android系统提供一个API来手动设置Camera的预览方向,叫做setDisplayOrientation,默认情况下,这个值是0,与图像Sensor方向一致,所以对于横屏应用来说,就不需要更改这个Camera预览方向。但是,如果你的应用是竖屏应用,就必须通过这个API将Camera的预览方向旋转90,与手机屏幕方向一致,这样才会得到正确的预览画面。

 

这个问题对于竖屏应用以及一些特定的手机,如Nexus 5X,Nexus 6上尤为明显。因此google官方给出的建议是需要应用层根据Sensor来配置Camera的显示方向。

 

 

2、相机自动对焦问题以及机型兼容

 
 

对于自定义的相机在手机晃动时,相机会失去焦点导致画面模糊,这时就需要我们程序上进行自动对焦,保证相机采集的画面是清晰的(手机系统的相机由厂商实现了这部分功能)。

 

正常情况下,Android的SDK也考虑到了这部分问题,因此只需要对Camera设置正确的自动对焦的模式,Camera就会自己完成自动对焦。

 

这种方式在大部分手机上实现了自动对焦,而且效果还不错,而且不需要去额外调用方法。但是实际测试中发现国内的某些机型如:小米4、华为荣耀 3C等在这种方式下不会自动对焦。由于国内Android碎片化太过严重,且上面的机型市场占有率都较高,因此需要我们通过其他的方式来兼容这类手机(感叹国内Android开发者的不易,希望google在Android 7.0以后可以很好的解决碎片化的问题)。

 

既然系统的API无法解决,那我们来想一想能否通过检测手机晃动的行为来实现自动对焦呢?答案是 可以。 上节在相机预览方向问题时,我们提到了系统的Camera会自动检测Sensor方向来保证我们看到的画面是正的,因此我们可以通过Sensor来检测手机的晃动,从而在手机晃动结束后进行一次对焦,从而完成自动对焦的效果。

 

具体的做法:先获取系统的SensorManager并实现SensorEventListener的接口,在获取到的SensorManager中注册SensorEventListener接口。然后就可以在onSensorChanged()中判断手机的运动状态,自动去调用mCameraFocusListener.onFocus();进行一次对焦。

 

注意:SensorManager在注册了监听器后会持有Context的引用,因此在Activity销毁或是取消自动对焦需要需要调用mSensorManager.unregisterListener(this,mSensor); 方法在取消注册,否则会产生内存泄漏。

 

3、相机资源释放问题

 
 

为了节省手机电量,不浪费相机资源,在开发的自定义相机里,如果预览图像已不需要显示,如按Home键盘切换后台或者锁屏后,此时就应该关闭预览并把相机资源释放掉。参考官方API文档,当surfaceView变成可见时,会创建surface并触发surfaceHolder.callback接口中surfaceCreated回调函数。而surfaceview变成不可见时,则会销毁surface,并触发surfacedestroyed回调函数。我们可以在对应的回调函数里,处理相机的相关操作,如连接surface、开启/关闭预览。 至于相机资源释放,则可以放在Acticity的onpause里执行。相应的,要重新恢复预览图像时,可以把相机资源申请和初始化放在Acticity的onResume里执行,然后通过创建surfaceview,将camera和surface相连并开启预览。

 

但是在测试中我们发现对应按Home键的情况,可以正常的释放相机,在程序恢复时可以再次获取相机。但在按锁屏键时,再次获取相机时会失败Crash。通过增加Activity生命周期,以及surfaceCreated和surfacedestroyed函数的log日志,最后发现:

 

程序运行->按HOME键 

Activity调用的顺序是onPause->onStop 

SurfaceView调用了surfaceDestroyed方法 

 

然后再切回程序 

Activity调用的顺序是onRestart->onStart->onResume

 SurfaceView调用了surfaceCreated->surfaceChanged方法 

 

而对于锁屏,其执行流程则是:

Activity只调用onPause方法 

解锁后Activity调用onResume方法 

SurfaceView中surfaceholder.callback的所有方法都没有执行 

 

原因找到了,锁屏时Activity只进入了onPause状态并没有进入onStop,因此SurfaceView也没有调用surfaceDestroyed方法。最后解决的办法是在onPause和onResume中分别调用SurfaceView的setVisiable(View.INVISIBLE) 和 setVisiable(View.VISIBLE)方法,主动让SurfaceView回调Create和Destory方法。

  

自此,Android上直播相机中所需要的开发技巧以及各种机型所带来的坑基本上讲解完毕了。希望读者在阅读这篇文章后可以较为容易的开发出一个自定义的相机程序,同时对于相机开发中遇到的大部分问题也可以在这里找到答案。笔者后续也会对于相机优化性能,数据采集缓冲buffer以及各种美颜等预处理方面的知识进行展开讲解,也希望有Android直播方面开发经验的同学可以互相交流,分享经验,让直播更好的改变我们的生活。

BACK
分享: