概述
Android提供了俩种使用相机的方式
- 一种是直接通过Intent调用系统相机组件,这种方法快速方便,适用于直接获取照片的场景
- 另一种是使用相机Api来自定义相机,这种方法用于需要定制相机界面或开发相机特殊功能的场景
相机中的关键API
Camera开发主要用到了来个类
- Camera类:最主要的类用于管理和操作camera资源,它提供了完整的相机底层接口
- SurfaceView 或TextureView:负责把Camera的数据显示在屏幕上
Camera类的API | 解释 |
---|---|
getNumberOfCameras | 获取设备的相机数量,一般有俩个 前置相机和后置相机 |
open(CameraId) | 获取Camera实例,传入参数1 是获取前置摄像头, 0 为后置摄像头 |
getParameters | 获取相机的参数,包括闪光灯模式,对焦模式,预览和拍照尺寸 |
setParameters | 把自己设置好的相机参数,设置给相机 |
setPreviewDisplay(SurfaceHolder) | 绑定预览图像的SurfaceView,当相机和surfaceView绑定后,相机预览的图像会通过surfaceView显示在屏幕上 |
setPreviewTexture(SurfaceTexture) | 绑定预览图像的TextureView,当相机和TextureView绑定后,相机预览的图像会通过TextureView显示在屏幕上 |
setPreviewCallback | 获取相机的预览数据 |
setDisplayOrientation | 设置预览画面,顺时针旋转的角度 |
startPreview | 开始预览 |
stopPreview | 停止预览 |
release | 释放相机 |
takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) | 这个是实现相机拍照的主要方法,包含了三个回调参数。shutter是快门按下时的回调,raw是获取拍照原始数据的回调,jpeg是获取经过压缩成jpg格式的图像数据的回调。 |
getCameraInfo(int cameraId, CameraInfo cameraInfo) | 获取指定id的相机信息 |
startFaceDetection | 开始人脸检测 |
stopFaceDetection | 停止人脸检测 |
setFaceDetectionListener | 设置人脸监听回调 |
我们继续看一下Camera类的内部类Parameters
上面我们说到一个getParameters方法,这个方法的返回值就是Parameters类
Camera.Parameters类的Api | 解释 |
---|---|
getSupportedPreviewSizes | 获取相机支持预览图片大小 |
setPreviewSize(640, 480); | 设置预览图片带下,这里就是设置预览图片为640*480 |
getSupportedPreviewFormats | 获得相机支持的图片预览格式,所有的相机都支持ImageFormat.NV21更多的图片格式可以自行百度或是查看ImageFormat类 |
setPreviewFormat(int pixel_format) | 设置预览图片的格式 |
getSupportedPictureSizes | 获取相机支持采集的图片大小(就是拍摄照片的带下) |
setPictureSize(int width, int height) | 设置拍摄照片的大小 |
getSupportedPictureFormats | 获取相机支持的图片格式 |
setPictureFormat | 设置拍摄图片的格式 |
getSupportedFocusModes | 获取相机支持的对焦模式 |
setFocusMode | 设置相机对焦模式 |
getMaxNumDetectedFaces | 返回当前相机所支持的最大的人脸检测个数 |
SurfaceView
用于绘制相机预览图像,提供给用户实时的预览图像,普通的View以及派生类都是共享一个Surface的,所有的绘制都需要在UI线程中,而SurfaceView是一种比较特殊的View,他不与其他普通View共享Surface,而是在他内部有一个独立的Surface,SurfaceView只负责管理这个Surface的格式尺寸以及显示位置,由于Ui线程还需要处理其他Ui交互,并不能保证View的更新速度和帧率,而SurfaceView有一个独立的Surface,因此可以在独立线程中绘制,因此可以提高更高的帧率
SurfaceView的优缺点
优点:
- 可以在一个独立的线程绘制,不会影响主线程
- 使用双缓冲机制,播放视频更流畅
双缓冲机制:SurfaceView更新视图时用到了俩个Canvas,一个frontCanvas一个backCavans,每次实际显示的就是frontCanvas,backCavans储存的是上一次跟改前的视图,当lockCanvas()获取画布时,实际得到的是backCavans,而不是正在显示的frontCanvas,之后你在backCavans绘制视图,再unlockCanvasAndPost(canvas)这个视图,那么上传的canvas会替代frontCanvas,而原来的frontCanvas将切换到后台作为backCavans
缺点:
- SurfaceView它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,但是在7.0以后SurfaceView可以进行平移,缩放等
SurfaceHolder:SurfaceHolder是控制Surface的一个抽象接口,它能够控制Surface的尺寸格式,修改surface的像素,监听surface的变化等,Surface的典型用例就是用于SurfaceView中,通过getHolder获取SurfaceHolder,来监听Surface的状态
SurfaceHolder.Callback接口:负责监听Surface状态变化的接口,有三个方法
- surfaceCreated(SurfaceHolder holder):当Surface被创建后立即被调用,在这里可以进行打开相机资源的操作
- surfaceChanged():当Surface的size发生变化时调用
- surfaceDestroyed(SurfaceHolder holder):当Surface被销毁时调用,可以在这里清相机资源
TextureView
TextureView继承自View,他将内容流直接投影到View中,和SurfaceView不同的是,他是一个普通的View,他可以平移,缩放等,他必须运行在硬件加速的窗口中
优点
- 他可以向普通的View一样进行平移缩放,截图
缺点
- 他必须运行在硬件加速的窗口,内存占用比SurfaceView高,5.0之前是在主线程渲染,5.0之后有单独的渲染线程
那么怎么选择这俩个控件
-
在7.0系统上SurfaceView比TextureView更有优势,可以使用动画,并且不会有黑边,如果在7.0以下有动画效果那么就需要用到TextureView
-
从安全和性能上考虑应该使用SurfaceView
自定义相机预览开发步骤
- 添加权限
在 Android Manifest.xml 中添加相机权限
<uses-permission android:name="android.permission.CAMERA" />
- 把SurfaceView写入XML
<SurfaceView
android:id="@+id/surface"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
- 监听Surface状态
surfaceView = findViewById(R.id.surface);
surfaceView.getHolder().addCallback(surfaceCallback);
private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//在这里打开相机
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//在这里回收相机资源
}
};
- 查看摄像头数量
int cameras = Camera.getNumberOfCameras();
这个方法返回相机的数量,在有前后摄像头的手机,会返回数量2
- 打开摄像头
mCamera = Camera.open(0);
传入参数CameraId,通常0为后置摄像头,1为前置摄像头
- 设置旋转角度和参数
mCamera.setDisplayOrientation(90);
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
//预览大小设置
parameters.setPreviewSize(640, 480);
mCamera.setParameters(parameters);
设置预览画面,顺时针旋转角度,和设置预览画面的大小,可以设置的参数有很多参考上方的API
- 开始预览
mCamera.setPreviewDisplay(surfaceView.getHolder());
mCamera.startPreview();
- 拿到预览数据
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) { //这里的data就是每一帧的数据
}
});
完整的代码
private void initView() {
surfaceView = findViewById(R.id.surface);
surfaceView.getHolder().addCallback(surfaceCallback);
}
private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
};
public void start() {
if (mCamera == null) {
mCamera = Camera.open(0);
}
//因为横屏
mCamera.setDisplayOrientation(90);
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
//预览大小设置
parameters.setPreviewSize(640, 480);
mCamera.setParameters(parameters);
mCamera.setPreviewDisplay(surfaceView.getHolder());
mCamera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
});
mCamera.startPreview();
} catch (Exception e) {
e.printStackTrace();
}
}
//摄像头数据的回调
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
开发相机所遇到的坑
相机的预览方向问题
我们先了解一下基本概念
- 相机的数据都来自于相机硬件的相机传感器,这个传感器被固定到手机后,会有一个默认的取景方向,且不会改变
- 相机预览的时候有一个预览方向,可以通过setDisplayOrientation方法设置
- 相机采集照片也是有一个方向的,与预览方向无关
- 屏幕坐标:在Android 中屏幕左上角是坐标的原点(0,0),原点向右是X轴正方向,原点向下是Y轴正方向
- 自然方向:每个设备都有一个自然方向,手机和平板的自然方向不同,手机的自然方向是portrait(竖屏),平板的自然方向是landscape(横屏)
- 图像传感器的方向:手机相机的图像数据来自于手机硬件的图像传感器,这个传感器固定到手机上之后会有一个默认的取景方向
- 相机的预览方向:将图像传感器捕捉到的图像,显示在屏幕上的方向,默认情况下和屏幕的方向一致,在相机API
setDisplayOrientation
方法可以设置预览方向,默认为0,和传感器方向一致
- 相机采集图像方向:相机采集图像后需要顺时针旋转
- 通过setDisplayOrientation设置预览方向,使预览方向为自然方向,前置摄像头在进行旋转之前,图像会进行一个水平方向的翻转,所以用户在看预览图形时会像照镜子
- 绝大部分手机的传感器方向是横向的,且不能改变,所以要旋转90或270,也就是说保存图片时需要对图片进行旋转
- 摄像头获取的帧数据,并不会随着预览方向改变
SurfaceView预览图像变形,拍摄照片图像变形问题
首先我们先明白三个概念
- SurfaceView尺寸:即自定义相机中显示图像的SurfaceView的大小,当他铺满全屏时,就是屏幕的大小,这里称为手机预览图像
- PreViewSize:相机硬件提供预览帧图像的大小,预览帧传给SurfaceView,实现图像的显示,这里称为相机预览图像
- Picturesize:相机硬件提供拍摄帧数据尺寸的大小,拍摄数据可以形成位图文件,最终保存为jpg等形式,这里称为相机拍摄图像
- 手机预览图像由相机预览图像生成,拍摄的照片由相机拍摄图像生成
问题
-
手机中预览的问题被拉伸被拉伸
这个是由于SurfaceView尺寸和PreViewSize长宽比例不同导致,因为手机预览图像是由相机预览图像生成,所有长宽比例不一致,必定会被拉伸 -
拍摄照片物体被拉伸
这种是由PreViewSize和PictureSize,长宽比例不同导致,总之为了避免这些问题的发生,SurfaceView、PreviewSize、PictureSize这三者的长宽尺寸需要比例相同,可以通过camera.getSupportedPreviewSizes()和camera.getSupportedPictureSizes()获取支持的尺寸,然后筛选和SurfaceView尺寸比例相同的数据,设置给相机,注意,市场上主流的长宽比例尺寸为4:3和16:9,所以SurfaceView尺寸不能太奇葩
锁屏下资源的释放
如果Home键切换后台或者锁屏后,就应该关闭资源把相机释放掉,可以在onResume和onPause中释放相机
参考:https://juejin.im/entry/58b4ccd944d904006a1ce446
https://www.jianshu.com/p/f8d0d1467584
https://juejin.im/entry/5912ba2d128fe10058694030
https://zhuanlan.zhihu.com/p/20559606