视频采集:Android平台基于Camera 2的实现

前言

这篇文章简单介绍下移动端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_3LEVEL_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()的第一个参数中将ImageReaderSurface传进去:

//通过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为辅助采集的架构实现比较靠谱。

本文同步发布于简书CSDN

End!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容