
1. 项目定位与整体思路
CameraControl 核心目标是:
- 用 EDSDK.dll(native)实现 相机连接/会话、拍照、下载、属性读写、LiveView、对焦/测光等
- 用一个 WinForms UI 把这些能力串起来(你现在看到的 Camera Control 界面)
- 用"命令队列 + 事件回调 + 观察者通知"把 UI 与相机操作解耦
后面要做照相亭软件,直接以它为基底是很合理的:先复用"相机控制域模型 + 命令执行框架 + 事件回调框架",再把 UI 替换成照相亭控制层/远端控制层。
2. 代码模块分层(从上到下)
2.1 UI 层(WinForms)
MainWindow / RemoteCapture / CameraSetting / DateTimeZoneSetting / RecordFuncCardSetting / Progress
这些是窗口与对话框。Property/*.cs:大量自定义控件(ComboBox/TrackBar/Label/Button),用于把某个"相机属性"映射为 UI 控件。
关键点:UI 不直接调用 EDSDK,而是发 ActionEvent,让 Controller 去排队执行命令。
2.2 控制层(Controller)
-
CameraController : ActionListener是核心入口它持有:
_model(相机状态/数据)_processor(命令队列执行器)
启动流程里,Controller 先启动命令线程,再投递 OpenSession、GetProperty 等初始化命令:
UI 触发动作后,Controller 会把动作翻译成命令并 Post 到队列,例如 TakePicture、StartEvf、DownloadEvf 等:
另外它也提供 "下载整卡/删整卡/格式化" 这种高层入口:
2.3 命令层(Command Pattern)
Command/*.cs 基本都是 一个命令类 = 一个相机操作,例如:
-
TakePictureCommand:发送快门命令并处理 busy/error,成功后把canDownloadImage = true -
DownloadCommand:对单个 directoryItem 执行 EdsCreateFileStream → EdsDownload → EdsDownloadComplete -
DownloadAllFilesCommand:遍历卷(Volume)下的 DCIM / XFVC(轮流切换),统计图片列表,再批量下载
这套命令模式非常适合你做"薄封装":完全可以保留命令层,把 UI 事件换成"照相亭业务指令"(拍照、预览、倒计时、打印、上传AI...)。
2.4 模型层(CameraModel)
CameraModel 存相机句柄、属性缓存、状态位(如 isEvfEnable、canDownloadImage 等),并提供 NotifyObservers 做事件广播。
(项目里很多命令完成后会 _model.NotifyObservers(e),UI 再根据事件刷新界面。)
TakePictureCommand 设置 canDownloadImage,是 "拍照后允许下载"的一个 gating:
2.5 事件与观察者(Observer / Listener / Delegates)
Observer.cs:观察者接口/基类(用于 UI 或其他组件订阅模型事件)CameraEvent / CameraEventListener:相机事件封装(错误、下载开始/完成、busy 等)ActionEvent / ActionListener / ActionSource:UI 动作事件体系(典型 Java 风格,C# 用类封装"命令+参数")
典型路径是:
UI控件 → 发 ActionEvent(Command.xxx, arg) → CameraController.ActionPerformed → CommandProcessor.PostCommand → Command.Execute → CameraModel.NotifyObservers(CameraEvent) → UI刷新
2.6 Native 互操作层(薄封装 / PInvoke)
EDSDK.cs 是你要的"薄封装"雏形:
它用 [DllImport("EDSDK.dll")] 暴露 EDSDK API,例如 EdsSetPropertyData / EdsCreateFileStream / EdsCreateMemoryStream 等。
并且做了一些 Marshal 辅助(例如 ManualWBData 的序列化/反序列化):
你后面做照相亭时,最佳实践就是把 EDSDK.cs 这种 P/Invoke 层严格独立为一个程序集 (比如
CameraSdk.Native),上层只用"强类型 C# API",不要到处散落 DllImport。
3. 关键运行时流程
3.1 启动与会话
Controller.Run() 启动命令线程并投递 OpenSession:
OpenSessionCommand 里有一段很重要:它在会话开始前通过一串 EdsSetPropertyData(... 0x01000000, magicKey, ..., PropID_XXX) "启用私有属性"(TempStatus、RollingPitching、MovieParam、Aspect...等)。
这与官方文档里"在 open session 前做 enable property 才能用某些扩展属性"的说明是一致的:
3.2 拍照与"为什么照片不在 SD 卡里"
观察到的现象:电脑能立刻拿到新照片,但相机回放看不到。这在 EDSDK 控制里最常见原因就是:
- SaveTo(保存目标)被设置为 Host(电脑)或 Host+Camera
- 如果是 Host,照片不会写入卡(或写入被延迟/不生成),相机回放自然看不到
也搜到 PropID_SaveTo 的常量存在(你之前的查找结果),说明这个 Sample 具备控制 SaveTo 的入口(是否在你这版代码的 StillImage 流程里设置,要看 OpenSession / RemoteShooting 的完整逻辑;你这次上传的片段里 OpenSession 主要在做"enable private properties",还没直接看到 SaveTo 的设置代码)。
但可以确定的是:下载逻辑确实是把文件写到"运行目录(dirItemInfo.szFileName)对应的路径"(注意这里是直接用文件名创建文件流,并不是选一个固定下载目录):
如果你从 UI 里点击 "SD 卡下载",弹窗 2/2 一闪而过但找不到文件,常见原因就是:
文件被下载到了程序当前工作目录(例如 bin\x64\Debug\ 下),而不是你以为的 Pictures/Downloads。
✅ 可以立刻验证:在
DownloadCommand的EdsCreateFileStream(dirItemInfo.szFileName, ...)这一行打断点,看dirItemInfo.szFileName和进程当前目录(Environment.CurrentDirectory)到底是什么。这比猜下载到哪更快。
4. LiveView(EVF)是什么?StartEvf / DownloadEvf / EndEvf / DriveLens / DoEvfAF / ClickAF 的含义
Canon EDSDK 里把 LiveView 叫 EVF(Electronic View Finder) 数据流控制。
4.1 StartEvf(开启 LiveView 输出到电脑)
核心是把相机的 EVF 输出设备切到 "PC"(有的相机也支持同时输出到 LCD+PC)。官方文档示例也强调"start LiveView 后,会通过 property change 触发下载 EVF 图像"的流程:
4.2 DownloadEvf(拉取一帧 LiveView)
本质是循环调用:
- 创建 MemoryStream(或复用缓冲)
EdsCreateEvfImageRef(stream)EdsDownloadEvfImage(camera, evfImage)- 解析 evfImage 中的可视区域、对焦框信息、JPEG 帧数据,然后画到 UI(你的工程里是
EvfPictureBox)
(你上传的 pdf 示例里明确出现了 EdsCreateMemoryStream / EdsCreateEvfImageRef / EdsGetPropertyData(...Evf_VisibleRect...) 的套路)
4.3 EndEvf(退出 LiveView)
Controller 里在 EndEvf 前还会强制把 EVF AF 关掉:
这属于"退出 LiveView 的安全收尾",避免相机处在 AF 状态导致后续命令失败。
4.4 DriveLens(手动驱动镜头对焦)
DriveLensCommand 一般会调用 EVF 模式下的 "驱动镜头近/远一步"的命令(Near1/2/3、Far1/2/3)。
Controller 明确把 Focus Near/Far 按钮映射到 DriveLensCommand:
4.5 DoEvfAF(让相机在 LiveView 下执行 AF)
Controller 把 EVF_AF_ON / EVF_AF_OFF 映射到 DoEvfAFCommand:
4.6 ClickAF(点选对焦点)/ ClickWB(点白平衡)
这是高级交互:你在 LiveView 画面上点一下,程序把坐标转换成相机需要的坐标系/Rect,然后发命令让相机在那个点 AF 或做 WB 采样。
Controller 里 ClickAF / ClickWB 的分支非常清晰:
5. 这个工程的"技术路线总结"(一句话版)
WinForms UI + Controller(ActionEvent)+ Command Queue(串行化相机操作)+ CameraModel(状态缓存)+ Observer(事件回调驱动 UI 刷新)+ EDSDK P/Invoke(薄封装)
这正是想要的"照相亭控制层"理想底座:
- EDSDK 操作必须串行、要处理 busy/retry ------ 命令队列很关键
- 拍照/下载/LiveView 都是异步事件驱动 ------ Observer 很关键
- UI/业务一定会变 ------ Controller/Command/Model 的解耦让你以后替换 UI 成本极低
6. 要做 AI Photo Booth:MVP 技术栈设想
给的约束是:
- MVP:iPad/手机作为控制端
- 相机控制(Canon R 系列)目前最稳妥还是 Windows 主控 + EDSDK(你已经验证官方 demo 可跑)
因此 MVP 我建议你把系统拆成两块:
6.1 Booth Host(Windows 迷你主机 / 工控机)
- 运行基于 CameraControl 抽出来的 Camera Service
- 对外提供 HTTP/WebSocket API(拍照、LiveView帧、状态、设置参数、下载路径、AI上传、打印...)
- 内部继续复用:CommandProcessor + Commands + Model + EventListener
6.2 Control Client(iPad/手机)
- 用 Web(H5/PWA) 做控制端最快(浏览器即用,iPad/Android 都 OK)
- 控制端通过 WebSocket 看 LiveView(低延迟)+ 通过 HTTP 发指令(可靠)
这样你不用纠结 Flutter/Qt 先上哪个:MVP 先把 "跨端控制" 用 Web 解决,Host 端继续 .NET/C# 做相机控制最省风险。