# 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` → Sony - `0x04A9` → Canon - 其他 → 提示不支持的相机品牌 ## 业务流程 1. `AutoDetectCameraService.connect()` 启动 USB 设备搜索(或复用已有连接)。 2. 发现相机并完成系统授权后,按 Vendor ID 创建品牌 Service 并 attach。 3. 品牌 Service 打开 ICC 会话并执行品牌专有 PTP 握手(Sony SDIO / Canon SetRemoteMode)。 4. 拍摄事件触发 PTP 下载或 catalog 回退,底层先写入临时目录。 5. 通过 `onNewAsset` 回调通知上层有新照片。 ## 相机端前置条件 - **Sony**:设为电脑遥控(PC Remote)模式 - **Canon**:设为电脑遥控 / PC Remote / 照片传输(PTP)模式 - 均需关闭「仅充电」模式 ## 依赖 - ImageCaptureCore(系统框架) - 无第三方库依赖 ## 页面生命周期与资源释放 OTG 相机连接由 `CameraTetheringSession.sharedService` 进程级持有;`CameraServiceFactory.makeAutoDetect()` 始终返回该单例。 **进入 OTG 页:** 1. `.task` 调用 `WiredCameraTransferViewModel.start()`。 2. `pipeline.attachToUI(...)` 绑定 UI 回调。 3. `connect()` 启动或复用 USB 搜索;若已连接则跳过 attach,同步状态并补发 detach 期间缓冲的新片。 4. 发现相机后按 Vendor ID attach 品牌服务,打开 ICC 会话并执行 PTP 握手。 **离开 OTG 页(detach):** 1. `.task` 取消或 `onDisappear` 触发 `detachFromUI()`(幂等)。 2. 仅清空 `onNewAsset` / `onConnectionStateChange` 等 UI 回调,取消 Pipeline 上传/通知任务。 3. **不** PTP CloseSession、**不** stop Browser、**不** nil `activeService`。 4. 底层 D215 轮询与 PTP 下载可继续,新片写入 `pendingAssetsWhileDetached`,再次进入时 flush。 **App 即将终止(shutdown):** 1. `AppDelegate.applicationWillTerminate` 调用 `CameraTetheringSession.shutdown()`。 2. 品牌 Service 完整断开(Sony 先 `Position_Key_Setting=Local` 再 PTP CloseSession)。 3. `usbBrowser.stop()` 停止 USB Browser 并释放授权状态。 进入后台**不**主动 shutdown;系统挂起 USB 时由 `deviceBrowserDidResumeOperations` 处理恢复。 若返回后再进入无法连接,优先检查日志中是否出现「会话关闭超时」或在非 terminate 场景误调用了 `shutdown()`。