21、Camera 扩展镜头:ExternalCamera特性、UVC Camera支持、热插拔处理、ExternalCamera配置限制
好,咱们今天来聊聊 Android 相机框架里一个挺有意思的模块——ExternalCamera。说白了,就是外接摄像头。
你想想看,手机自带的摄像头虽然越来越强,但总有些场景不够用。比如做直播需要多机位,或者搞工业检测要接个显微镜镜头。这时候,USB 摄像头就派上用场了。Android 从很早的版本就开始支持这个,但真正把它做成一个标准化的框架,还是从 Android 10 左右开始完善的。
我个人习惯把 ExternalCamera 看作是「二等公民」。为什么这么说?因为它虽然能工作,但跟内置摄像头比,限制还是挺多的。咱们今天就把它的特性、UVC 支持、热插拔处理,还有那些让人头疼的配置限制,一次性讲清楚。
ExternalCamera 是什么?
ExternalCamera,顾名思义,就是通过 USB 接口连接到 Android 设备上的摄像头。它走的协议通常是 UVC(USB Video Class),这是一个标准协议,Windows、Linux、macOS 都支持,Android 也不例外。
在 Android 相机框架里,ExternalCamera 被抽象成一个独立的 Camera HAL 模块。它跟内置摄像头走的是同一套 Camera API,但底层实现完全不同。内置摄像头走的是 MIPI CSI 接口,而 ExternalCamera 走的是 USB 总线。
我记得在 Android 10 之前,外接摄像头基本靠厂商自己适配,兼容性一塌糊涂。后来 Google 在 AOSP 里加入了 ExternalCameraProvider,这才有了统一的实现标准。
UVC Camera 支持
UVC 是 USB Video Class 的缩写。说白了,只要你的 USB 摄像头符合 UVC 标准,插到 Android 设备上就能被识别,不需要额外装驱动。这跟 Windows 上「即插即用」是一个道理。
Android 的 UVC 支持依赖 Linux 内核的 UVC 驱动。具体来说,内核需要开启以下配置:
CONFIG_USB_VIDEO_CLASS=y
CONFIG_MEDIA_USB_SUPPORT=y
CONFIG_V4L_PLATFORM_DRIVERS=y
如果内核没开这些选项,那外接摄像头根本不会被识别。我在项目中遇到过好几次,客户说「插上没反应」,结果一查,内核编译时把 UVC 驱动给砍了。
UVC 摄像头支持的分辨率、帧率、格式,都通过标准的描述符上报给系统。Android 的 ExternalCamera HAL 会解析这些描述符,然后生成对应的 Camera 配置。常见的格式包括:
- YUYV(原始格式,带宽占用大)
- MJPEG(压缩格式,带宽占用小)
- H.264(部分高端摄像头支持)
嗯,这里要注意:MJPEG 格式虽然省带宽,但解码需要额外的 CPU 开销。如果你做的是低功耗设备,最好优先选 YUYV 格式。
热插拔处理
热插拔是 ExternalCamera 最核心的特性之一。内置摄像头焊在主板上,不存在「插拔」的问题。但外接摄像头不一样,用户随时可能插上或拔掉。
Android 的热插拔处理流程大致是这样的:
- USB 事件通知:当 USB 摄像头插入时,内核通过 uevent 机制通知用户空间。
- ExternalCameraProvider 监听:这个 Provider 会监听 USB 设备的插拔事件。
- 设备枚举:Provider 调用 V4L2 API 枚举摄像头节点(通常是 /dev/video*)。
- 创建 Camera 实例:如果发现新的摄像头,就创建一个 ExternalCamera 实例,并注册到 CameraService。
- 通知上层:通过 CameraManager.AvailabilityCallback 通知应用层。
拔掉时的流程正好相反。Provider 检测到设备消失,就会移除对应的 Camera 实例,并通知上层。
我曾经踩过一个坑:某些 USB 摄像头在拔掉时,内核不会立即清理 /dev/video 节点,导致上层以为摄像头还在。后来发现是 USB 控制器驱动的问题,需要加一个延迟检测机制。
ExternalCamera 配置限制
这部分是重点,也是大家最容易踩坑的地方。ExternalCamera 跟内置摄像头比,限制太多了。我列几个最常见的:
| 限制项 | 说明 | 原因 |
|---|---|---|
| 分辨率上限 | 通常不超过 1080p | USB 带宽有限,高分辨率需要高带宽 |
| 帧率限制 | 30fps 是常态,60fps 很少见 | USB 2.0 带宽只有 480Mbps |
| 不支持多路流 | 同一时刻只能输出一路流 | USB 控制器不支持多通道 |
| 不支持 ZSL | 零快门延迟不可用 | USB 传输延迟大 |
| 不支持 HDR+ | 多帧合成不可用 | 帧率不稳定,无法对齐 |
| 不支持物理摄像头 | 多摄协同不可用 | USB 摄像头是独立设备 |
除了这些,还有一个隐藏限制:USB 控制器性能。很多低端设备的 USB 控制器是挂在内部总线上的,带宽共享。如果你同时插了 U 盘和摄像头,可能会出现摄像头掉帧的情况。
我个人建议,在做 ExternalCamera 方案时,先确认以下几点:
- 设备 USB 控制器是 USB 2.0 还是 3.0?
- 摄像头支持哪些分辨率和帧率组合?
- 应用场景对延迟是否敏感?
如果对延迟敏感,比如做视频通话,建议用 MJPEG 格式,虽然解码耗 CPU,但传输延迟低。如果对画质敏感,比如做拍照,建议用 YUYV 格式,但要注意带宽够不够。
v4l2-ctl --list-formats-ext 命令查看摄像头支持的所有分辨率和帧率组合。这个命令在 Android 上也能用,前提是设备有 root 权限。
ExternalCamera 的架构图
下面这张图展示了 ExternalCamera 在 Android 相机框架中的位置和交互流程。我画得比较简洁,重点突出热插拔和数据流两条主线。
从这张图可以看出来,ExternalCamera 的架构其实挺清晰的。应用层通过 CameraService 跟 Provider 交互,Provider 再调用 HAL 层,HAL 层通过 V4L2 驱动跟 USB 摄像头通信。热插拔事件则通过 UEvent 监听,直接通知 Provider。
这里有个细节:ExternalCameraProvider 是运行在独立进程里的,跟 CameraService 不在同一个进程。这样做的好处是,即使 Provider 挂了,也不会影响 CameraService 的正常运行。
实际项目中的避坑指南
最后,我分享几个实际项目中遇到的坑,希望能帮你少走弯路。
坑一:USB 带宽不足导致掉帧
我曾经在一个平板上接 1080p 30fps 的摄像头,结果画面一直卡顿。查了半天,发现平板的 USB 控制器是 USB 2.0,而且跟 Wi-Fi 共享带宽。解决办法是降低分辨率到 720p,或者改用 MJPEG 格式。
坑二:热插拔导致 CameraService 崩溃
有一次测试,拔掉摄像头时应用直接闪退。后来发现是应用没有监听 onDisconnected 回调,还在继续使用已经释放的 Camera 实例。解决方案是在 onDisconnected 回调里立即释放资源。
坑三:某些摄像头不兼容
市面上有些廉价摄像头虽然标称 UVC 兼容,但实际上实现有 bug。比如描述符里写支持 1080p,但实际只能输出 480p。遇到这种情况,建议在应用层做一次能力验证,不要完全相信描述符。
- ExternalCamera 走的是 UVC 协议,依赖内核 UVC 驱动
- 热插拔通过 UEvent 机制实现,应用层需要监听 onDisconnected
- 配置限制主要来自 USB 带宽和控制器性能
- 实际项目中,带宽和兼容性是最常见的坑
好了,关于 ExternalCamera 的内容就讲到这里。这部分虽然看起来简单,但实际做起来细节很多。尤其是热插拔和配置限制,稍不注意就会出问题。希望今天的分享能帮你少踩几个坑。