Android系统提供2种使用手机相机资源实现拍摄视频功能的方法,一种是直接通过Intent调用系统相机组件,代码如下所示:
Intent intent = new Intent(); intent.setAction("android.media.action.VIDEO_CAPTURE"); intent.addCategory("android.intent.category.DEFAULT"); intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);另一种是使用相机API来自定义,这种方式使用于需要定制拍摄视频界面或者开发特殊拍摄视频功能的场景,像美拍,微视都是如此。
Camera:最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法如下:
open():获取camera实例
setPreviewDisplay(SufaceHolder):绑定绘制预览图像的surface。Suface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。通过sufaceHolder可以将Camera和Surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。
setPrameters设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。
startPreview():开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。
stopPreview():停止预览,关闭camera底层的帧数据传递以及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()来实现停止相机预览及释放相机资源等操作。
总结一下我在开发自定义拍摄视频时遇到的一些问题:
1. Activity设为竖屏时,SurfaceView预览图像颠倒90度。
分析这个问题之前,先介绍Android手机几个方向概念:
屏幕方向:在Android系统中,无论屏幕是否横屏还是竖屏,屏幕左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。
相机传感器方向:手机相机的图像数据都是来自硬件的图像传感器,这个传感器在被固定到手机后有一个默认的取景方向,如下图所示,坐标原点位于位于手机横放时的左上角,即与横屏应用的屏幕X方向一致。换句话说,与竖屏应用的屏幕X方向呈90度。
图1 相机传感器方向示意图
相机预览方向:由于手机屏幕可以360度旋转,为了保证用户无论怎么旋转手机都看到“正确”的预览画面(这个“正确是指现在UI预览界面的画面与人眼看到的眼前画面是一致的),Android系统底层根据当前手机屏幕方向对图像传感器采集到数据进行了旋转处理,然后才传至显示系统,因此可以保证预览画面始终“正确”。在相机API中可以通过setDisplayOrientation函数设置相机预览方向。在默认情况下,这个值为0,与图像传感器一致,因此对于横屏应用来说,由于屏幕方向和预览方向一致,预览图像不会颠倒90度,但是对于竖屏应用,屏幕方向和预览方向垂直,所以会出现颠倒90度现象,为了得到正确的预览画面,必须通过API将相机预览方向旋转90,保持与屏幕方向一致,如图2所示:
图2 相机预览方向示意图(红色箭头为预览方向,蓝色方向为屏幕方向)。
2. SurfaceView预览图像,拍摄视频拉伸变形
解释这个问题之前,先说明几个跟相机有关的尺寸。
SurfaceView尺寸:即自定义相机应用中用户显示预览图像View的大小,当它铺满全屏时就是屏幕大小,这里surfaceView显示的预览图像暂且称作显示预览图像。
PreviewSize:相机硬件提供的预览帧数据尺寸,预览数据传递给SurfaceView,实现预览帧数据的显示。这里帧数据尺寸称作帧数据图像。
VideoSize:相机硬件提供拍摄视频的分辨率尺寸,拍摄帧数据生成视频文件,最终保存成.mp4格式的视频。
下面说下开发过程遇到的拉伸变形:
1 手机显示预览画面中物体拉伸变形。
2 点击拍摄视频时,手机预览画面会停顿下,此时图像是拉伸变形的,拍摄完视频预览画面恢复后图像又正常。
现象1是SurfaceView和PreivewSize的长宽比例不一致,因为手机预览图像是由帧数据图像根据SurfaveView大小缩放得来的。当长宽比例不一致时必然导致变相,现象2是由于PreviewSize和VideoSize大小不一致,网上查了些资料。在开发时最好SurfaveView, PreviewSize, VideoSize三个尺寸比例保持一致。
3. 各种crash
在相机硬件在聚焦和拍摄前,必须要保证已经连接到surface,并且开启相机前必须保证已经连接到surface,并且开启相机预览,surface有收到预览数据,如果还没调用startPreview函数前就执行autofocus,就会报异常。
Camera很多参数设置需要同getSupportedXXX(),来获取相机硬件支持的参数,否则在不同手机上,易发生兼容性错误。
拍摄完视频后,后续会用ffmpeg对视频进行裁剪,压缩等处理,敬请关注后续技术积累