CameraControl 技术架构说明文档-基于 Canon EDSDK

0. 背景与目标

  • 已验证: 能识别 Canon EOS R6,可拍照、可把照片下载到电脑;并观察到"相机回放里没有新照片"的现象。

  • 目标:

    1. 薄封装 / CameraCore(相机控制内核)

    2. 照相亭控制层(Booth Controller:流程、UI、打印、AI、上传)

    3. 未来可对齐 Snappic 的"有趣 UI、多端扩展"的形态(你给的机器人形态图:大屏 + 上方相机头)。


1. 总体技术路线(工程分层)

核心思想要"工程化":

UI 不直接调用 SDK;UI 只发"命令事件"。
相机模型(CameraModel)统一缓存状态,并用 Observer 广播变化。
CommandProcessor 串行执行命令,处理 DEVICE_BUSY 重试。

可以把它分成 4 层:

1.1 EDSDK P/Invoke 层(最低层)

  • 文件:EDSDK.cs

  • 作用:声明 EDSDK 的常量、结构体、枚举、DllImport 的函数签名(EdsInitializeSDK / EdsOpenSession / EdsSetPropertyData / EdsDownload... 等)。

这就是未来要的"薄封装"的最底层:纯签名 + 常量定义,不带业务。


1.2 Model + Event 层(相机状态中心)

  • 文件:CameraModel.cs, CameraEvent.cs, Observer.cs, CameraEventListener.cs

  • 作用:

    • CameraModel 保存"当前相机状态"(属性值、属性描述、EVF 状态、下载状态...)

    • 通过 NotifyObservers(...) 向所有观察者广播事件(UI 控件、窗口、进度条等)

这层的价值:把"相机状态"从 UI/命令里抽离,形成单一事实来源(Single Source of Truth)。


1.3 Command 层(业务原子操作)

  • 文件夹:Command/* 以及 Command.cs

  • 作用:每个命令封装一次 EDSDK 操作:

    • 会话:OpenSessionCommand, CloseSessionCommand

    • 拍照:PressShutterCommand,(TakePictureCommand 文件里有省略...)

    • 下载:DownloadCommand, DownloadAllFilesCommand, DeleteAllFilesCommand

    • EVF:StartEvfCommand, DownloadEvfCommand, EndEvfCommand, DriveLensCommand, DoEvfAFCommand, ClickAFCommand ...

典型特征 :命令执行失败时,统一上报模型事件;遇到 DEVICE_BUSY 则发 DEVICE_BUSY 事件以便重试(见 PressShutterCommand.cs 的逻辑:EdsSendCommand 失败且为 Busy 时发事件)。PressShutterCommand.cs 里能看到这一套路。


1.4 Action / Controller / UI 适配层(把 UI 变成命令)

  • 文件:ActionSource.cs, ActionListener.cs, ActionEvent.cs, CameraController.cs, MainWindow.cs

  • 作用:

    • ActionSource.FireEvent(Command, Arg):UI 触发命令的统一入口

    • ActionListener:监听动作事件

    • CameraController:把动作翻译成"投递命令到 CommandProcessor"

    • UI 控件(Button/ComboBox/Label/ProgressBar/EvfPictureBox)普遍实现 IObserver:订阅模型事件、刷新自己

既有的"属性控件就是插件",就是这一层的体现。


2. 关键链路解析

2.1 启动与连接相机

典型流程(从 Program.csMainWindow / CameraController 推断):

  1. EdsInitializeSDK()

  2. 获取相机列表、选中相机

  3. 注册回调(ObjectEvent / PropertyEvent / StateEvent)→ CameraEventListener

  4. EdsOpenSession()

  5. 同步属性与可选范围(PropDesc),驱动 UI 初始化

这套结构对"照相亭"非常关键:我们要做的并不是 UI,而是 稳定的状态机 + 回调/事件闭环


2.2 为什么"电脑能看到新拍的,但 SD 卡回放看不到"?

OpenSessionCommand.cs 中,对 PropID_SaveTo 做了设置:

  • 当某个条件满足(代码里判断了一个 PropID_FixedMovie 的值)时,把 SaveTo 设为 EdsSaveTo.Host,并调用 EdsSetCapacity 伪装"电脑存储空间很大"。(项目里在 OpenSessionCommand.cs 60~68 行附近能看到:设置 SaveTo.Host + EdsSetCapacity

  • 否则把 SaveTo 设为 EdsSaveTo.Camera(约 72 行附近)。

结论

  • SaveTo=Host:照片"只到电脑",相机卡里不出现,所以相机回放看不到。

  • SaveTo=Camera:照片进卡,相机回放可见;如果你还想电脑也拿到,需要监听对象事件或再额外下载。

这与观察完全一致:你能"立刻在电脑看到刚拍的",同时相机回放看不到(典型 Host 模式)。


2.3 SD 卡"文件下载"下载到哪里?

DownloadAllFilesCommand.cs 里写死了默认路径:

  • Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)(约 115 行)

  • 然后拼接文件名 dirItemInfo.szFileName(约 117 行)

  • EdsCreateFileStream(...) + EdsDownload(...)(约 119~129 行)

所以下载到:当前 Windows 用户的"图片(My Pictures)"目录

后面做照相亭必改:这必须变成可配置路径(比如 D:\Booth\Shots\ 或按订单号/会话号分目录)。


3. LiveView(EVF)到底是什么?StartEvf / DownloadEvf / EndEvf 的意义

3.1 EVF(LiveView)定义

  • 相机进入"电子取景器实时流"状态,把每一帧(通常是 JPEG)通过 EDSDK 以 stream 的形式送到 PC。

  • PC 端不断拉帧并显示,同时可以在 LiveView 上做:

    • 对焦框信息(FocusInfo)

    • 触摸/点击对焦(ClickAF)

    • 执行 AF(DoEvfAF)

    • 变焦(Zoom)

    • 驱动镜头对焦(DriveLens:类似手动对焦步进)

3.2 你这个 Demo 的"拉帧方式"是事件驱动链,不是 Timer

EvfPictureBox.cs 中的核心逻辑非常关键:

  • 当收到 EVFDATA_CHANGED

    1. 先触发 GET_PROPERTY(FocusInfo)

    2. 立刻再次触发 DOWNLOAD_EVF 以获取下一帧

      (你代码里在 67~71 行附近能看到:GET_PROPERTY(FocusInfo) 然后 FireEvent(DOWNLOAD_EVF))

  • 当检测到 PropID_Evf_OutputDevice 包含 PC 时:

    • 认为 PC LiveView 开始,置 _active=true 并触发第一次 DOWNLOAD_EVF

      (约 82~88 行附近)

这意味着:

  • StartEvf 负责把相机切进"输出到 PC"的状态(通常是设置 EvfOutputDevice)

  • DownloadEvf 是"拉一帧"

  • EvfPictureBox 把"拉帧循环"串起来(拉到一帧 → 显示 → 继续拉下一帧)

  • EndEvf 负责退出 LiveView

这段机制你未来做照相亭特别有用:

你可以把"实时预览"完全抽成一个独立模块(EVF Pipeline),UI 只订阅"最新帧图像"。


4. 属性系统(Property Layer)是怎么设计的?为什么这么多 ComboBox/Label/TrackBar?

4.1 核心模板

很多 PropertyComboBox.csPropertyTrackBar.cs 等"基类"。整个属性系统大概遵循这个模式:

  1. map:把相机的枚举值 → 人类可读文本

    • 例如 TvComboBoxWhiteBalanceComboBoxIsoComboBoxPictureStyleComboBox 等都内置 map.Add(...)
  2. PropDesc 驱动:下拉框的可选项来自相机返回的"可选范围"

    • 这点非常关键:不同机型/模式支持值不同,不能写死。
  3. 控件实现 IObserver:

    • 收到 PROPERTY_CHANGED → 刷当前值

    • 收到 PROPERTY_DESC_CHANGED → 刷可选列表 + 再刷当前值

  4. 用户操作不改相机,只发命令事件

    • 例如 ActionButton/RadioButton:点击直接 FireEvent(Command, Arg)(你前面传的 ActionButton/ActionRadioButton 就是这个套路)

4.2 这对照相亭的直接价值

照相亭常见需求:

  • 强制固定:ISO、白平衡、画质、构图比例、曝光补偿上限等

  • UI 上允许用户可调(可选):美颜强度、模板、倒计时、连拍张数

  • 幕后动态变化:存储剩余张数、电量、温度、连接状态

这套属性层让你能做到:

  • UI 是 Web/Flutter/Qt 都无所谓:只要消费"属性状态 JSON"即可

  • 相机兼容性更强:R6/R100/R8 等都可以靠 PropDesc 自适应


5. 线程与稳定性:为什么它能"跑得稳"?

5.1 CommandProcessor 串行化

  • 命令是被排队执行的(串行),避免并发调用 EDSDK 造成状态混乱。

  • Busy 时通过事件提示重试(EDS_ERR_DEVICE_BUSY 是 EDSDK 常见状态)。

5.2 事件回调统一进 Model,再广播给 UI

这是 WinForms 稳定运行的重要原因:

  • 回调里不直接碰 UI

  • UI 只在 Observer 通知后刷新(通常还会做线程切换)

未来做照相亭:这套"回调→模型→广播"结构必须保留,否则你会被线程/UI 崩溃折磨。


6. 如何 演进成"照相亭控制层 + 薄封装"?

下面是 MVP 技术路线(结合你"对齐 Snappic 多端 + MVP 要快"的现实):

6.1 拆分

拆成 3 个工程:

  1. CameraCore(.NET Class Library)

    • 含:EDSDK.cs + Model + Command + EventListener + CommandProcessor

    • 对外暴露:CameraService 类(Open/Close/Capture/StartLiveView/GetFrame/SetProperty...)

  2. CameraHost(Windows 常驻进程 / Service)

    • 运行在机器人底座的工控机/miniPC 上(最现实)

    • 对外提供 HTTP/WebSocket:

      • /capture/status/liveview/frame/property/set
    • 负责:下载存储、文件命名、上传、打印、异常自恢复

  3. BoothUI

    • MVP :Web(React/Vue)全屏Flutter

    • UI 不需要知道 EDSDK,只调用 CameraHost API

    • 未来要 iPad/手机联动(对齐 Snappic)也更容易:同一套 Web/Flutter UI 直接复用

6.2 为什么我不建议 MVP 直接上 Qt "全家桶"

Qt 做大屏 kiosk UI 很强,但:

  • 你仍然要写 EDSDK 封装(C++/PInvoke)

  • 多端(iPad/手机)复用差(除非再做一套)

  • MVP 的最大风险不是 UI,而是相机链路稳定性、拍照下载、断线恢复

所以 MVP 更优先:先稳相机内核 + API 服务化,UI 可以快速迭代。


7. 机器人照相亭形态

  • 一体机大屏(竖屏)+ 头部摄像头模组(像机器人头)

  • "固定点位 + 触摸交互 + 自动拍照/引导"

这类硬件非常适合:

  • 底座内置 Windows mini PC(跑 CameraHost)

  • 屏幕跑 Web 全屏(Chromium kiosk)或 Flutter Windows

  • 远程手机/平板做"遥控/预览/扫码取片"------直接复用 API

这条路能最快对齐 Snappic 的"体验感",同时不被多端技术债拖死。


8. 下一步:

  1. 把 SaveTo 策略产品化

    • 你要明确:照相亭"照片最终落点"是什么?

    • 常见做法:SaveTo=Host(更快拿到)+ 需要时再同步/备份到卡(可选)

  2. 把下载路径、命名规则、会话目录抽出来

    • 现在写死 MyPictures(DownloadAllFilesCommand.cs 115 行附近)
  3. 做"拍照一次的完整闭环"状态机

    • StartEVF(可选)→ 倒计时 → PressShutter → 等 ObjectEvent → 下载 → 生成预览 → 上传/打印
  4. 把 CameraCore 做成一个 Class Library

    • WinForms 只是 Demo UI,你要把逻辑从 UI 剥离出来
  5. 做 CameraHost(HTTP/WebSocket)

    • 先让外部能调用:拍照 / 获取最新照片路径 / 获取 liveview 帧

    • UI 先用最简单页面验证(别先卷动画)


相关推荐
Elias不吃糖21 小时前
java开发的三层架构
java·开发语言·架构
AI周红伟21 小时前
周红伟:Sora2模型背后的技术原理与技术架构创新,和Sora2应用代码实操
架构
陌上丨1 天前
MySQL8.0高可用集群架构实战
数据库·mysql·架构
muddjsv1 天前
深入浅出 B/S 架构:从原理到实践,解锁 Web 应用开发核心
架构
国科安芯1 天前
永磁同步电机驱动控制系统中MCU的抗干扰设计
单片机·嵌入式硬件·性能优化·架构·安全性测试
川西胖墩墩1 天前
钻井平台设备布局图设计方法
人工智能·架构·流程图
独自归家的兔1 天前
系统存储机制深度剖析:从Win11临时文件夹设计看微软存储架构演进
架构
●VON1 天前
Flutter for OpenHarmony:基于原子清空与用户意图防护的 TodoList 批量删除子系统实现
学习·flutter·架构·跨平台·von
molaifeng1 天前
万字长文解析:Redis 8.4 网络 IO 架构深度拆解
网络·redis·架构
SJLoveIT1 天前
【深度复盘】Redis 分布式锁:从 SETNX 到 Redisson 看门狗的架构权衡
redis·分布式·架构