第9章:Camera App开发与Framework交互

这一章,我们终于要动手写App了。说实话,很多做驱动的兄弟觉得App层是"上层的事",跟自己没关系。但我在项目中吃过这个亏——不懂App怎么用Camera2 API,你连HAL层该暴露什么特性都搞不清楚。今天我们就从App开发者的视角,把Camera2 API的预览、拍照、录像,以及SurfaceView、TextureView、ImageReader这些核心组件彻底讲透。

9.1 Camera2 API应用开发基础

Camera2 API是Android 5.0引入的,它把相机设备抽象成一个状态机。你想想看,老旧的Camera1 API就像个黑盒子,你只能set参数、takePicture。而Camera2 API让你能直接控制每一个Capture Request,说白了,就是让你能精细控制每一帧的采集过程。

核心概念:CameraManager管理所有摄像头设备,CameraDevice代表一个物理摄像头,CameraCaptureSession负责发送Capture Request,而CaptureResult则返回每一帧的元数据。

我个人习惯把Camera2 API的调用流程总结为五步:

  1. 通过CameraManager获取摄像头列表
  2. 打开指定的CameraDevice
  3. 创建CameraCaptureSession
  4. 构建CaptureRequest并提交
  5. 处理CaptureResult回调

嗯,这里要注意:打开CameraDevice是异步操作,千万别在主线程里等。我在项目中遇到过新手直接在主线程调openCamera,结果ANR了,排查了半天才发现是线程问题。

9.2 预览:SurfaceView vs TextureView

预览是Camera App最基本的功能。Android提供了两种Surface来显示预览画面:SurfaceView和TextureView。很多人问我该用哪个,我的建议是——看场景。

特性SurfaceViewTextureView
渲染方式独立Surface,专用硬件合成层在View层级中作为普通View渲染
性能高,支持硬件加速稍低,需要GPU合成
动画/变换不支持支持平移、缩放、旋转
生命周期需监听surfaceCreated/Destroyed随Activity生命周期

如果你只是做普通预览,SurfaceView是首选。但如果你要做美颜、滤镜这些需要处理每一帧画面的操作,TextureView更方便——因为它可以直接拿到Bitmap。

我曾经在一个美颜App项目里,一开始用了SurfaceView,结果发现要截取预览帧做美颜处理,还得通过ImageReader再读一遍,多了一次内存拷贝。后来换成TextureView,直接getBitmap()就搞定了,性能提升很明显。

9.3 ImageReader:获取图像数据的利器

ImageReader是用来从Camera2获取图像数据的。它内部维护了一个Surface,你把这个Surface设置到CaptureRequest的输出目标里,每一帧数据就会回调到ImageReader的onImageAvailableListener中。

ImageReader reader = ImageReader.newInstance(width, height, 
    ImageFormat.YUV_420_888, 2); // 2是最大缓冲帧数
reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        // 处理YUV数据
        image.close(); // 必须关闭,否则会卡死
    }
}, backgroundHandler);

重要:Image使用完后必须调用close(),否则ImageReader内部缓冲池会被耗尽,导致预览卡死。我曾经调试过一个Bug,预览几秒后画面就卡住了,查了两天才发现是Image没close。

ImageReader的格式通常用YUV_420_888,这是Android推荐的格式,兼容性最好。如果你要做美颜,拿到YUV数据后转成RGB,用OpenCV或者RenderScript处理,再渲染回屏幕。

9.4 Camera特性查询与配置

Camera2 API最强大的地方,就是你可以通过CameraCharacteristics查询设备支持的所有能力。比如:

  • 是否支持闪光灯:FLASH_INFO_AVAILABLE
  • 支持的分辨率列表:SCALER_STREAM_CONFIGURATION_MAP
  • 是否支持HDR:CONTROL_AE_AVAILABLE_MODES
  • 物理摄像头数量:LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE

我个人习惯在App启动时,先遍历所有摄像头,把特性打印出来。这样调试时一眼就能看出设备支持什么、不支持什么。

CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
String[] cameraIds = manager.getCameraIdList();
for (String id : cameraIds) {
    CameraCharacteristics chars = manager.getCameraCharacteristics(id);
    Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
    // 判断是前置还是后置
    StreamConfigurationMap map = chars.get(
        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    // 获取支持的分辨率
    Size[] outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
}

配置Request时,通过CaptureRequest.Builder设置参数。比如设置自动对焦模式:

builder.set(CaptureRequest.CONTROL_AF_MODE, 
    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

小技巧:不是所有设备都支持所有模式。设置前最好先查一下Characteristics,否则设置不支持的参数,Request会被忽略,而且不会报错。我刚开始做的时候就被这个坑过——明明设了HDR模式,但拍出来的照片就是没效果,后来才发现设备不支持。

9.5 多摄像头同时使用:Logical Camera

Android 9引入了Logical Camera的概念。说白了,就是把多个物理摄像头(比如广角+长焦)虚拟成一个逻辑摄像头。App打开这个逻辑摄像头,系统会根据场景自动切换物理摄像头,或者同时使用多个物理摄像头合成更好的效果。

查询逻辑摄像头的方法:

Integer logicalType = chars.get(
    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
// 检查是否包含LOGICAL_MULTI_CAMERA
int[] physicalIds = chars.get(
    CameraCharacteristics.PHYSICAL_CAMERA_IDS);

使用逻辑摄像头时,你可以指定使用哪个物理摄像头:

// 创建Session时指定物理摄像头
session.setRepeatingRequest(request, callback, handler);
// 或者通过CaptureRequest的物理摄像头ID
builder.setPhysicalCameraId(physicalId);

我曾经在一个双摄虚化项目里,需要同时从主摄和副摄获取图像数据做深度计算。逻辑摄像头帮了大忙,但要注意——不同物理摄像头的帧同步是个大问题。如果两个摄像头的帧率不一致,合成出来的效果会很奇怪。

9.6 实战:开发一个支持美颜的Camera App

好了,理论说完了,我们来动手做一个带美颜功能的Camera App。核心思路是这样的:

  1. 使用TextureView做预览,方便获取每一帧画面
  2. 通过ImageReader获取YUV数据
  3. 用OpenCV做美颜处理(磨皮、美白、瘦脸)
  4. 处理后的画面渲染到TextureView上

美颜的核心算法其实不复杂,我简单说一下磨皮的原理:

  • 用双边滤波或高斯滤波做平滑
  • 通过边缘检测保留边缘细节
  • 用透明度混合,在平滑和原图之间做平衡
// 伪代码:磨皮处理
Mat src = new Mat(height, width, CvType.CV_8UC3);
// 将YUV转成RGB
Imgproc.cvtColor(yuvMat, src, Imgproc.COLOR_YUV2RGB_NV21);
// 双边滤波磨皮
Mat dst = new Mat();
Imgproc.bilateralFilter(src, dst, 15, 80, 80);
// 与原图混合
Core.addWeighted(src, 0.3, dst, 0.7, 0, result);

性能警告:美颜处理很耗CPU。如果每帧都做全尺寸处理,低端手机会卡成PPT。我的经验是:先降采样到640x480处理,处理完再放大回原尺寸。另外,用RenderScript做GPU加速效果更好。

拍照功能就简单了,在美颜处理后的帧上,调用ImageReader的acquireLatestImage()拿到当前帧,保存为JPEG。录像的话,需要用MediaCodec编码,把美颜后的帧喂给编码器。

嗯,这里要提醒一下:Camera2 API的录像比拍照复杂得多。你需要同时维护预览Surface和编码器Surface,而且帧率要稳定。我建议用MediaRecorder配合Camera2的TEMPLATE_RECORD模板,这样系统会自动优化参数。

9.7 本章小结

Camera2 API虽然复杂,但掌握了它的设计思想——把相机抽象成状态机,用Request驱动每一帧——你就能灵活控制相机了。SurfaceView和TextureView各有适用场景,ImageReader是获取图像数据的关键。多摄像头开发要特别注意帧同步问题。美颜App的核心是图像处理管线,性能优化是关键。

说实话,Camera App开发最考验人的不是API调用,而是对各种异常情况的处理。摄像头被占用、权限被拒绝、设备不支持某个特性……这些在实际项目中都会遇到。多写try-catch,多查Characteristics,能省很多调试时间。

核心要点回顾:

  • Camera2 API的核心是CameraDevice + CaptureSession + CaptureRequest
  • 预览用SurfaceView性能好,需要处理帧用TextureView
  • ImageReader用完必须close()
  • 逻辑摄像头可以同时使用多个物理摄像头
  • 美颜处理要注意性能,降采样是常用手段
Camera2 API 应用开发核心流程 Android App (Camera2 API) CameraManager (获取摄像头列表) CameraDevice (打开摄像头) CameraCaptureSession (创建会话) CaptureRequest (配置参数并提交) 输出目标 SurfaceView TextureView ImageReader MediaCodec MediaRecorder Allocation
公众号:蓝海资料掘金营,微信deep3321