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 的热插拔处理流程大致是这样的:

  1. USB 事件通知:当 USB 摄像头插入时,内核通过 uevent 机制通知用户空间。
  2. ExternalCameraProvider 监听:这个 Provider 会监听 USB 设备的插拔事件。
  3. 设备枚举:Provider 调用 V4L2 API 枚举摄像头节点(通常是 /dev/video*)。
  4. 创建 Camera 实例:如果发现新的摄像头,就创建一个 ExternalCamera 实例,并注册到 CameraService。
  5. 通知上层:通过 CameraManager.AvailabilityCallback 通知应用层。

拔掉时的流程正好相反。Provider 检测到设备消失,就会移除对应的 Camera 实例,并通知上层。

我曾经踩过一个坑:某些 USB 摄像头在拔掉时,内核不会立即清理 /dev/video 节点,导致上层以为摄像头还在。后来发现是 USB 控制器驱动的问题,需要加一个延迟检测机制。

警告:热插拔时如果应用正在使用摄像头,必须做好异常处理。否则轻则应用闪退,重则导致整个 CameraService 挂掉。建议在应用层监听 CameraDevice.StateCallback 的 onDisconnected 回调。

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 架构与交互流程 Camera App CameraService ExternalCameraProvider ExternalCamera HAL V4L2 驱动 USB 控制器 USB Camera UEvent 监听 数据流方向 热插拔事件方向

从这张图可以看出来,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 的内容就讲到这里。这部分虽然看起来简单,但实际做起来细节很多。尤其是热插拔和配置限制,稍不注意就会出问题。希望今天的分享能帮你少踩几个坑。


公众号:蓝海资料掘金营,微信deep3321