引入进程级 CameraTetheringSession 与共享 USB Browser;离开传图页仅取消 UI 回调,App 终止时再完整 shutdown 释放 PTP 与 Browser 资源。 Co-authored-by: Cursor <cursoragent@cursor.com>
3.2 KiB
3.2 KiB
CameraTethering
职责
提供 USB OTG 相机有线连接与 Sony/Canon PTP 边拍边传能力,不包含业务 API 或 OSS 上传。
核心组件
| 组件 | 说明 |
|---|---|
CameraServiceProtocol |
相机服务抽象,上层只依赖此协议 |
CameraTetheringSession |
进程级单例,持有共享 AutoDetectCameraService |
AutoDetectCameraService |
按 USB Vendor ID 自动识别品牌并委托 |
SonyCameraService |
Sony 相机实现,封装 SDIO/PTP 下载 |
CanonCameraService |
Canon EOS 相机实现,封装 GetEvent/PTP 下载 |
ImageCaptureDeviceManager |
Sony ICC 设备管理 |
CanonImageCaptureDeviceManager |
Canon ICC 设备管理 |
PTPHelper |
共享 PTP 编解码与命令发送 |
CameraDownloadStorage |
本地下载作用域、目录与文件名消毒 |
品牌识别
插入 USB 相机后,AutoDetectCameraService 读取 ICCameraDevice.usbVendorID:
0x054C→ Sony0x04A9→ Canon- 其他 → 提示不支持的相机品牌
业务流程
AutoDetectCameraService.connect()启动 USB 设备搜索(或复用已有连接)。- 发现相机并完成系统授权后,按 Vendor ID 创建品牌 Service 并 attach。
- 品牌 Service 打开 ICC 会话并执行品牌专有 PTP 握手(Sony SDIO / Canon SetRemoteMode)。
- 拍摄事件触发 PTP 下载或 catalog 回退,底层先写入临时目录。
- 通过
onNewAsset回调通知上层有新照片。
相机端前置条件
- Sony:设为电脑遥控(PC Remote)模式
- Canon:设为电脑遥控 / PC Remote / 照片传输(PTP)模式
- 均需关闭「仅充电」模式
依赖
- ImageCaptureCore(系统框架)
- 无第三方库依赖
页面生命周期与资源释放
OTG 相机连接由 CameraTetheringSession.sharedService 进程级持有;CameraServiceFactory.makeAutoDetect() 始终返回该单例。
进入 OTG 页:
.task调用WiredCameraTransferViewModel.start()。pipeline.attachToUI(...)绑定 UI 回调。connect()启动或复用 USB 搜索;若已连接则跳过 attach,同步状态并补发 detach 期间缓冲的新片。- 发现相机后按 Vendor ID attach 品牌服务,打开 ICC 会话并执行 PTP 握手。
离开 OTG 页(detach):
.task取消或onDisappear触发detachFromUI()(幂等)。- 仅清空
onNewAsset/onConnectionStateChange等 UI 回调,取消 Pipeline 上传/通知任务。 - 不 PTP CloseSession、不 stop Browser、不 nil
activeService。 - 底层 D215 轮询与 PTP 下载可继续,新片写入
pendingAssetsWhileDetached,再次进入时 flush。
App 即将终止(shutdown):
AppDelegate.applicationWillTerminate调用CameraTetheringSession.shutdown()。- 品牌 Service 完整断开(Sony 先
Position_Key_Setting=Local再 PTP CloseSession)。 usbBrowser.stop()停止 USB Browser 并释放授权状态。
进入后台不主动 shutdown;系统挂起 USB 时由 deviceBrowserDidResumeOperations 处理恢复。
若返回后再进入无法连接,优先检查日志中是否出现「会话关闭超时」或在非 terminate 场景误调用了 shutdown()。