完善 OTG 相机有线传图:支持 Sony/Canon 自动识别,页面 detach 保持会话以便再次进入复连。

引入进程级 CameraTetheringSession 与共享 USB Browser;离开传图页仅取消 UI 回调,App 终止时再完整 shutdown 释放 PTP 与 Browser 资源。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-01 15:45:35 +08:00
parent 5f7ef24683
commit 90d135b5ac
34 changed files with 2870 additions and 310 deletions

View File

@ -2,26 +2,73 @@
## 职责
提供 USB OTG 相机有线连接与 Sony PTP 边拍边传能力,不包含业务 API 或 OSS 上传。
提供 USB OTG 相机有线连接与 Sony/Canon PTP 边拍边传能力,不包含业务 API 或 OSS 上传。
## 核心组件
| 组件 | 说明 |
| --- | --- |
| `CameraServiceProtocol` | 相机服务抽象,上层只依赖此协议 |
| `SonyCameraService` | Sony 相机实现,封装 PTP 下载与连接状态 |
| `ImageCaptureDeviceManager` | ImageCaptureCore 设备浏览、会话与 PTP 事件 |
| `SonyPTPCommands` | Sony SDIO/PTP 命令封装 |
| `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. `SonyCameraService.connect()` 启动 USB 设备搜索。
2. 授权通过后打开 ICC 会话并执行 Sony SDIO 握手
3. 拍摄事件触发 PTP 下载,底层先写入临时目录,再由传输管道按账号与相册移入业务目录
4. 通过 `onNewAsset` 回调通知上层有新照片
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()`