23、车载摄像头与视频:Camera2 API集成、行车记录仪、倒车影像、视频流处理

车载摄像头,说白了就是汽车的「眼睛」。在Android Automotive OS里,这块坑特别多。我最早接触车载摄像头项目时,以为跟手机上的Camera2开发差不多,结果一上手就发现完全不是那么回事——分辨率、帧率、延迟、安全优先级,每个维度都跟手机不一样。

今天咱们就把这块彻底讲透。从Camera2 API的集成,到行车记录仪和倒车影像的具体实现,再到视频流处理的核心逻辑,我会把我在项目里踩过的坑、总结的经验,全都摊开来讲。

核心知识点一览:

  • Camera2 API在车载环境下的适配要点
  • 行车记录仪的前后双录实现
  • 倒车影像的低延迟显示方案
  • 视频流编码、存储与回放
车载摄像头与视频 知识体系 Camera2 API 集成 行车记录仪(前后双录) 循环录制 · 事件触发 · 存储管理 倒车影像(低延迟显示) SurfaceView · 优先级抢占 · 引导线 视频流编码与存储 MediaCodec · MP4封装 · 回放 关键:延迟 < 100ms · 优先级 · 安全合规

23.1 Camera2 API 在车载环境下的适配

Android的Camera2 API,在手机上已经非常成熟了。但在车上,有几个关键差异你必须知道。

第一个差异:摄像头数量与方向。 车载系统通常有4-8个摄像头,前后左右甚至环视。每个摄像头的物理方向是固定的,不像手机可以旋转。我做过一个项目,客户要求前置摄像头必须输出竖屏画面,但传感器本身是横屏的。嗯,这里要注意——你不能简单用旋转矩阵,因为车载摄像头通常有固定的安装角度,旋转后视野会丢失。

正确的做法是在配置CameraCharacteristics时,读取SENSOR_ORIENTATION,然后根据实际安装角度做裁剪而非旋转。

// 获取摄像头方向
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

// 车载摄像头通常固定,直接映射到显示方向
// 前视:0度,后视:180度,左视:270度,右视:90度
int displayRotation = getDisplayRotation();
int correction = (sensorOrientation - displayRotation + 360) % 360;
// 注意:这里不要旋转,而是裁剪对应区域

我的经验: 车载摄像头的预览尺寸,别用手机上的1920x1080。我建议用1280x720,因为车载屏幕分辨率通常不高,而且720p的带宽压力小很多,延迟也能降下来。你想想看,倒车影像如果延迟超过100ms,驾驶员会明显感觉「卡」。

第二个差异:CameraManager的权限模型。 在Android Automotive上,摄像头权限不是运行时权限,而是通过android.car.CAR_CAMERA权限来控制。这个权限是系统级的,普通应用拿不到。所以如果你的应用要访问摄像头,必须声明这个权限,并且确保应用是系统应用或具有系统签名。

我曾经遇到一个坑:第三方导航应用想调用前视摄像头做AR导航,结果一直报权限错误。后来发现,Android Automotive对第三方应用的摄像头访问做了严格限制——除非用户明确授权,否则只能访问后视摄像头(倒车场景)。

23.2 行车记录仪:前后双录的实现

行车记录仪的核心需求很简单:同时录制前后两路视频,循环存储,事件触发时锁定片段不被覆盖。

实现上,我习惯用两个独立的CameraCaptureSession,分别绑定前后摄像头。注意,这里不能共用同一个Session,因为前后摄像头的帧率、分辨率可能不同。

// 前摄像头配置
CameraDevice frontCamera = ...;
frontCamera.createCaptureSession(
    Arrays.asList(frontSurface, frontImageReader.getSurface()),
    new CameraCaptureSession.StateCallback() { ... },
    handler
);

// 后摄像头配置(独立Session)
CameraDevice rearCamera = ...;
rearCamera.createCaptureSession(
    Arrays.asList(rearSurface, rearImageReader.getSurface()),
    new CameraCaptureSession.StateCallback() { ... },
    handler
);

循环录制怎么搞? 说白了就是分段存储。我一般把视频切成1分钟一段,存到外置存储的/sdcard/DCIM/Dashcam/目录下。当总大小超过设定阈值(比如32GB),就删除最旧的片段。但要注意——事件触发的片段要加锁,不能删。

事件触发逻辑:

  • G-Sensor检测到碰撞 → 锁定当前片段前后各30秒
  • 用户手动点击「保存」 → 锁定当前片段
  • 锁定的片段移到/sdcard/DCIM/Dashcam/Protected/目录

这里有个细节:视频文件的命名最好带上时间戳和摄像头ID,方便后续检索。我用的格式是FRONT_20250101_143022.mp4,这样按文件名排序就是时间顺序。

23.3 倒车影像:低延迟显示方案

倒车影像是车载摄像头里最「硬」的需求。为什么?因为延迟必须控制在100ms以内,否则驾驶员会感觉画面滞后,影响倒车安全。

我最早用TextureView来做预览,结果延迟一直在150ms左右,怎么优化都下不去。后来换成SurfaceView,延迟直接降到80ms。原因很简单——TextureView有额外的合成层,而SurfaceView是直接输出到显示硬件。

注意: 倒车影像的优先级必须高于其他所有UI。当车辆挂入R挡时,系统应该立即抢占显示资源。在Android Automotive中,可以通过CarOccupantZoneManager来设置显示优先级,确保倒车画面不被其他应用遮挡。

引导线的绘制也是个技术活。我建议用Overlay的方式,在SurfaceView上层叠加一个自定义View,根据方向盘角度动态绘制引导线。不要直接在视频帧上画线,因为那样需要修改每一帧的像素数据,性能开销太大。

// 引导线叠加层
public class GuidelineOverlay extends View {
    @Override
    protected void onDraw(Canvas canvas) {
        // 根据方向盘角度计算轨迹线
        float steeringAngle = getSteeringAngle();
        // 绘制两条轨迹线(左轮、右轮)
        Path leftPath = calculatePath(steeringAngle, LEFT);
        Path rightPath = calculatePath(steeringAngle, RIGHT);
        canvas.drawPath(leftPath, paint);
        canvas.drawPath(rightPath, paint);
    }
}

23.4 视频流处理:编码、存储与回放

视频流处理这块,核心是MediaCodec。我习惯用MediaCodec的异步模式,配合Surface输入,效率最高。

编码参数上,行车记录仪我推荐H.264编码,码率8-12Mbps,帧率30fps。H.265虽然压缩率更高,但解码兼容性在车机上还不够好。我遇到过一些老款车机,H.265的视频播放卡顿甚至花屏。

MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1280, 720);
format.setInteger(MediaFormat.KEY_BIT_RATE, 10_000_000); // 10Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 关键帧间隔1秒
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

MediaCodec codec = MediaCodec.createEncoderByType("video/avc");
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface inputSurface = codec.createInputSurface();

存储格式: 我建议用MP4容器,因为兼容性最好。但要注意,MP4的moov box在文件末尾,如果录制过程中断电,文件会损坏。解决办法是使用MediaMuxeraddTrack方法,并在录制完成后正确写入moov box。或者,你也可以用TS格式,它更抗中断。

回放方面,我推荐用ExoPlayer,它对车载场景做了很多优化,比如无缝切换、自适应码率。而且ExoPlayer支持MediaSource的拼接,可以方便地实现「事件前后片段」的连续播放。

避坑指南: 我曾经在视频回放时遇到音画不同步的问题。后来发现是音频采样率设置不对——车载麦克风的采样率通常是16000Hz,而视频编码器默认用44100Hz。解决办法是统一用48000Hz,这是车机硬件最稳定的采样率。

23.5 实战:倒车影像的完整流程

最后,我把倒车影像的完整流程串起来,给你一个清晰的实现路径。

步骤 操作 关键点
1 监听车辆R挡信号 通过CarPropertyManager监听GEAR_SELECTION
2 打开后视摄像头 使用CameraManager.openCamera,指定后视摄像头ID
3 创建SurfaceView预览 设置SurfaceView为全屏,优先级最高
4 启动预览Session 配置低延迟参数,关闭自动对焦(固定焦距)
5 叠加引导线 根据方向盘角度动态绘制
6 退出R挡时关闭 释放摄像头资源,恢复之前界面

嗯,这里要注意第4步的「关闭自动对焦」。车载后视摄像头通常是广角定焦镜头,自动对焦反而会导致画面频繁抖动。我建议直接设为CONTROL_AF_MODE_OFF,然后手动设置焦距到无穷远。

好了,车载摄像头这块的内容就讲到这里。从Camera2 API的适配,到行车记录仪和倒车影像的具体实现,再到视频流处理的编码存储,每个环节都有不少细节。你在实际项目中遇到问题,可以回头看看这些经验,应该能帮你少走不少弯路。