摄像头配置必须把以下三类信息分开管理:
VID_PID,例如 1374_41357。deviceId、/dev/videoN 和 Camera2 ID。这些值可能在设备重启后变化,不能作为持久化唯一标识。在当前 FY-3576SE 上,三台摄像头的 VID/PID 均不相同,因此可以稳定区分。程序重启或设备重启后,只要摄像头仍然连接且应用数据未被清除,就会按照保存的 VID/PID 恢复原选择。
| 字段 | 示例 | 含义 | 是否适合持久化 |
|---|---|---|---|
| 当前用途 | 当前菜品识别 | 业务角色,不是设备属性 | 保存业务与设备的绑定关系 |
| 建议客户显示名称 | MC-500U(XC-TECH) | 产品名与厂商名,供客户选择 | 只用于显示,不作为唯一标识 |
| 稳定标识 VID/PID | 1374_41357 | USB 厂商编号与产品编号的组合 | 当前设备可作为持久化主键 |
| 序列号 | SN00151 | 厂商写入设备的单机序列号 | 可靠且唯一时,适合增强唯一性 |
| USB 物理端口 | 1-1.2 | 摄像头连接的总线和端口路径 | 可作为无序列号设备的绑定兜底 |
| V4L2 | /dev/video0 | Linux 本次启动分配的视频节点 | 不适合,重启或枚举顺序变化后可能改变 |
| Camera2 ID | 100 | Android 外置相机 HAL 本次启动暴露的相机编号 | 不适合,需在每次启动时重新计算 |
| Android USB deviceId | 例如旧配置中的 1004 | Android 在当前 USB 连接期间分配的运行时编号 | 不适合,重连或重启后可能改变 |
以下为 2026-09-03 在 FY-3576SE 上的设备快照:
| 建议显示名称 | 当前稳定标识 | 序列号 | USB 端口 | 当前 V4L2 | 当前 Camera2 ID |
|---|---|---|---|---|---|
| MC-500U(XC-TECH) | 1374_41357 | SN00151 | 1-1.2 | /dev/video0 | 100 |
| USB 2.0 Camera(Sonix Technology Co., Ltd.) | 3141_25440 | SN0001 | 1-1.6.1 | /dev/video2 | 102 |
| USB2.0_CAM1(Sonix Technology Co., Ltd.) | 11391_19040 | USB2.0_CAM1 | 1-1.6.2 | /dev/video28 | 128 |
历史设备记录中,MC-500U 与 USB2.0_CAM1 曾分别对应 /dev/video28、/dev/video0,与本次启动正好互换。这证明 V4L2 和 Camera2 编号只是运行时地址,写死映射会在重启后选错摄像头。
| 配置项 | 保存内容 | 示例 |
|---|---|---|
usb_camera | 菜品摄像头稳定 VID/PID | 1374_41357 |
dish_camera_resolution_{VID_PID} | 该摄像头独立的分辨率 | dish_camera_resolution_1374_41357 = 1280x960 |
菜品摄像头的选择和分辨率都跟随稳定标识保存。因此,同一台摄像头重启后即使获得不同的 Android deviceId,仍能读取原来的选择和分辨率。
| 配置项 | 保存内容 | 生命周期 |
|---|---|---|
face_uvc_camera_id | 人脸摄像头稳定 VID/PID | 长期持久化,是恢复选择的依据 |
face_camera_id | 当前启动对应的 Camera2 ID | 运行时派生值,每次启动或使用前重新同步 |
face_camera_resolution | 人脸摄像头分辨率 | 长期持久化 |
face_camera_rotation | 旋转角度 | 长期持久化 |
face_camera_mirror | 镜像开关 | 长期持久化 |
face_camera_id 虽然也写入配置,但它不是摄像头身份主键。真正的持久化依据是 face_uvc_camera_id;程序每次根据当前硬件拓扑重新得到新的 Camera2 ID。
客户选择“产品名称(厂商名称)”
↓
程序读取该 UsbDevice 的 vendorId、productId
↓
生成稳定标识 VID_PID
↓
菜品相机写入 usb_camera
人脸相机写入 face_uvc_camera_id
↓
分辨率等参数按稳定标识或业务配置单独保存
SharedPreferences 读取已保存的稳定标识。设备重启后,USB deviceId、V4L2 节点和 Camera2 ID 都允许变化。恢复过程如下:
读取 face_uvc_camera_id
↓
扫描 /sys/class/video4linux/video*/device
↓
从视频节点追溯 USB 父设备,读取 VID/PID
↓
建立 VID_PID → 主 V4L2 节点映射
↓
读取 Camera2 中 hardware level = EXTERNAL 的相机 ID
↓
推导且校验唯一、一致的 Camera2/V4L2 编号偏移
↓
生成本次启动的 face_camera_id
当前设备表现为 Camera2 ID 与主 V4L2 节点存在统一偏移,例如 /dev/video0 → 100、/dev/video2 → 102、/dev/video28 → 128。程序通过当次枚举结果推导这个偏移,不再把 100、102、128 与具体型号写死绑定。
旧版菜品配置曾使用 deviceId_VID_PID,例如 1004_1374_41357。新版读取时取最后两段并规范化为 1374_41357。
如果发现旧分辨率键 fbl_旧ID,程序会把值复制到新的 dish_camera_resolution_{VID_PID}。旧键暂不删除,便于升级过渡和问题回溯。
| 场景 | 是否保持选择 | 程序行为 |
|---|---|---|
| 退出后重新打开程序 | 是 | 从 SharedPreferences 读取稳定标识并重新匹配 |
| Android 整机重启 | 是 | 忽略变化后的运行时编号,重新解析 V4L2/Camera2 对应关系 |
| USB 枚举顺序变化 | 是 | 按 VID/PID 匹配,不按 deviceId 或 Camera2 ID 匹配 |
| 摄像头短暂拔出后插回 | 正常情况下是 | 重新枚举后按稳定标识找回设备 |
| 保存的摄像头未连接 | 否,等待设备恢复 | 提示“已保存的摄像头不可用,请重新选择”,不静默改成第一台 |
| 清除应用数据 | 否 | SharedPreferences 被清除,需要重新设置 |
| 卸载后重新安装 | 通常否 | Android 通常会删除应用私有配置,需要重新设置 |
| 覆盖升级 APK | 正常情况下是 | 应用私有配置保留,同时执行旧格式迁移 |
| 同时连接两台相同 VID/PID 摄像头 | 当前不能可靠区分 | 必须使用序列号或物理端口增强标识 |
设置页不直接显示 1374_41357 之类的内部标识,当前采用“产品名称(厂商名称)”。
产品名称或厂商名称缺失时,界面使用“USB 摄像头”等兜底文案。名称只服务于客户识别,不参与底层唯一性判断。
VID + PID + serialNumber。只在序列号非空、非通用占位值且在当前设备列表中唯一时使用。VID + PID + physicalPort。摄像头必须固定插在指定接口;换口后提示重新绑定。usb:1374:41357:sn:SN00151 usb:3141:25440:port:1-1.6.1
USB 2.0 Camera(Sonix · 尾号0001) USB 2.0 Camera(Sonix · USB口1) USB 2.0 Camera(Sonix · USB口2)
还可以增加客户自定义别名,例如“菜品台上方”“人脸支付屏”。别名只作为显示属性,底层仍保存稳定绑定标识。
/dev/videoN 都不能单独作为长期身份依据。| 组件 | 职责 |
|---|---|
UvcCameraIdentity | 生成、规范化和比较稳定 VID/PID;兼容旧 deviceId_VID_PID |
DishCameraSettings | 管理菜品摄像头和分辨率持久化;迁移旧配置 |
FaceCameraSettings | 管理人脸 UVC 稳定标识,并在使用前同步 Camera2 ID |
ExternalCameraIdResolver | 从 sysfs 和 Camera2 获取当次启动的硬件拓扑 |
FaceCameraIdMapping | 校验并计算稳定标识对应的 Camera2 ID |
CameraUsbUtils | 将保存的稳定标识解析成当前连接的 UsbDevice 并打开菜品摄像头 |
SettingsCameraFragment | 展示友好名称、保存选择、处理失联和角色冲突 |
/dev/videoN 和 Camera2 ID。