前言
这篇文章简单介绍下移动端Android系统下利用Camera2
相关API进行视频采集的方法。
Camera2
是谷歌在Android 5.0新增的用来替代Camera1
操作摄像头的一个全新的API。
按照惯例先上一份源码AndroidVideo。
Camera2
调用摄像头采集视频的核心实现在Camera2Capture.java。
权限配置
使用Android平台提供的摄像头,首先必须在配置文件中添加如下权限配置:
<uses-permission android:name="android.permission.CAMERA"/>
获取摄像头信息
打开摄像头管理器
CameraManager
是一个用于检测、连接和描述摄像头设备的一个系统服务,可以通过调用Context.getSystemService(java.lang.String)
方法来获取一个CameraManager
的实例:
CameraManager mManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
获取摄像头列表信息
通过调用CameraManager.getCameraIdList()
方法,可以得到一个摄像头id的列表:
String[] cameraIds = mCameraManager.getCameraIdList();
for (String id : cameraIds) {
//TODO
}
可以通过相对应的ID从CameraManager
获取到对应摄像头的属性集合CameraCharacteristics
。
在CameraCharacteristics
可以获取到诸如前后置情况、支持的输出size、支持的输出格式等等之类的。
for (String id : cameraIds) {
//传入摄像头id,获取对应摄像头的参数集
CameraCharacteristics characteristics = mManager.getCameraCharacteristics(id);
//获取摄像头的支持等级
Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
//如果是LEGACY等级,不建议使用该摄像头
if (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY)
{
continue;
}
//获取摄像头的朝向
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
//筛选出前置摄像头
if (facing != CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
//StreamConfigurationMap包含了该摄像头支持的size、format等信息
StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//获取输出格式为YUV_420_888时兼容的size
Size[] size = map.getOutputSizes(ImageFormat.YUV_420_888);
//获取输出View为SurfaceView时兼容的size
//Size[] size = map.getOutputSizes(SurfaceHolder.class);
//TODO 其他的参数,例如输出格式、输出帧率上下限等
}
PS:对于Camera2
采集系统来说,每个摄像头都有一个支持等级:
-
INFO_SUPPORTED_HARDWARE_LEVEL_3
支持YUV再处理和原始数据采集功能,并且具备先进的功能。 -
INFO_SUPPORTED_HARDWARE_LEVEL_FULL
支持先进的摄像头功能。 -
INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
向后兼容模式,底层等同于Camera1的实现。 -
INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
随机赠送的功能支持,支持性不足。
PS:总的来说如果摄像头等级是LEVEL_3
和LEVEL_FULL
才建议使用Camera2
进行采集,否则推荐采用兼容性更好的Camera1
进行视频采集。
打开摄像头
通过摄像头信息,我们可以找到所需要的CameraId,接下来就用这个ID去获取我们的摄像头设备CameraDevice
。
函数原型是public void openCamera(String cameraId, final CameraDevice.StateCallback callback, Handler handler)
,
cameraId是需要打开的摄像头的id,为了监听摄像头的情况,需要传入一个回调,也就是第二个参数CameraDevice.StateCallback
,当然如果我们不想让open操作占用UI线程的时间的话,
我们可以通过构造一个HandlerThread
的带Looper的子线程,然后将其Handler传入即可。
//打开摄像头,正常打开会回调到CameraDeviceStateCallback的onOpened方法
mManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
//摄像头成功连接
//camera也就是我们需要获取的摄像头设备
mCameraDevice = camera;
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
//摄像头断开连接
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
//打开错误
}
}, mHandler);
创建采集会话
在成功打开摄像头,获取到相应的CameraDevice
,我们需要创建一个采集会话来提供程序与摄像头的交流。
其函数原型是public abstract void createCaptureSession(List<Surface> outputs,CameraCaptureSession.StateCallback callback, Handler handler) throws CameraAccessException
。
第一个参数传入的是需要采集的Surface,为了监听会话创建情况,我们需要传入一个CameraCaptureSession.StateCallback
回调,当然第三个参数也就是让操作能在对应Handler所在的线程中进行。
//获取一个采集Session会话,正常流程回回调到CameraCaptureSessionStateCallback的onConfigured方法
mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceView.getHolder().getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
//会话创建成功
//mCameraCaptureSession也就是新创建的会话
mCameraCaptureSession = session;
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
//会话创建失败
}
}, mHandler);
PS:对于一些业务需求需要提高采集帧率(120fps及以上),createConstrainedHighSpeedCaptureSession()
这个会话能良好的支持该功能。
发送采集请求
当需要开始采集时,需要构造一个采集请求,然后将这个请求发送给采集会话。
//创建一个基于录制的请求
mRequest = mDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
//将需要的目标Surface加入Target列表
mRequest.addTarget(surface);
//重复发送这个请求,进行持续的采集
mCameraCaptureSession.setRepeatingRequest(mRequest.build(), NULL, mHandler);
原始数据回调
在Camera1的采集中,我们一般通过设置setPreviewCallbackWithBuffer()
和addCallbackBuffer()
来获取到采集的原始数据,那么在Camera2
中将如何实现该功能呢?
我们可以用到ImageReader
这个类:
//ImageReader是一个数据回调模块,类似于Camera1的setPreviewCallbackWithBuffer
mReader = ImageReader.newInstance(mConfig.mWidth, mConfig.mHeight, mConfig.mFormat, 2);
mReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
//数据处理
image.close();
}
}, mHandler);
我们需要在createCaptureSession()
的第一个参数中将ImageReader
的Surface
传进去:
//通过ImageReader.getSurface()获取一个Surface并将其传给Session中
mCameraDevice.createCaptureSession(Arrays.asList(mReader.getSurface())//....);
然后在CaptureRequest
添加这个Target:
//当然,构造请求时,需要将该Surface同时加入到Request的Target列表中
mRequest.addTarget(mReader.getSurface());
参考资料
对于Camera2相关,我们一般可以参考如下几个工程:
googlesamples/android-Camera2Basic
google/cameraview
结语
这篇文章简单介绍了Android平台基于Camera2
的api进行摄像头采集的功能。
Camera2
虽然是谷歌当前建议使用的采集框架,但是由于厂商的兼容性问题导致Camera2
的api功能相对不稳定;
所以笔者还是建议开发以Camera1
为主要采集、Camera2
为辅助采集的架构实现比较靠谱。
End!