Skip to content

feat(plugin-sdk): 新增插件摄像头提供者(camera provider),支持非 DirectShow 来源进入视频展台 - #739

Open
lrs2187 wants to merge 4 commits into
InkCanvasForClass:net10from
lrs2187:feat/plugin-camera-provider
Open

lrs2187 wants to merge 4 commits into
InkCanvasForClass:net10from
lrs2187:feat/plugin-camera-provider

Conversation

@lrs2187

@lrs2187 lrs2187 commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

本 PR 完全使用 Vibe Coding 编写。功能已测试过,可以正常使用(如下图)。PR 目的是便于插件注册视频展台摄像头源。

同时本 pr 也尝试解决了漫游无法缩放视频展台画面的问题,等待在校实际测试。

image

以下为 aigc,已人工删减。

动机(真实用例)

宿主「视频展台」的摄像头下拉框只有一条数据来源:DirectShow
FilterCategory.VideoInputDevice 枚举(MW_VideoPresenter.cs + DirectShowCameraService.cs)。
插件无法向这个列表注入任何非 DirectShow 来源。

我们的真实设备是驿欧无线视频展台:它只输出 WiFi RTSP 流(rtsp://<设备IP>:554/stream0),
本身不是 USB/DirectShow 摄像头。目前唯一能让它出现在宿主下拉框里的办法是在系统里注册一个
虚拟 DirectShow 摄像头
(原生 C++ COM 滤波器 + HKCU 注册表 + VMR9 renderless 分配器协商)。
这条路径的代价:

  • 要写原生 COM 滤波器并处理 VMR9/D3D 渲染协商(我们目前正卡在这里);
  • 对全系统可见,相机/会议软件都会看到,用户困惑;
  • 与 OBS Virtual Camera 等软件设备存在 DevicePath 冲突/串台风险;
  • 安装/卸载涉及系统级 COM 注册,插件包不再“自包含”。

更正规的做法是宿主直接提供 plugin camera provider 能力:插件把自己采集的帧交给宿主,
由宿主负责预览、拍照与分辨率。本 PR 实现该能力,且不改变现有 DirectShow 路径的行为。

改动摘要

展开

1. SDK(InkCanvas.PluginSdk/)

  • 新增 ICameraProviderService + 相关类型(ICameraProviderService.cs,233 行):
    • ICameraProviderService:RegisterProvider / UnregisterProvider / NotifyCamerasChanged;
    • IPluginCameraProvider(插件实现):ProviderId、GetCameras()、
      StartCamera(cameraId, sink)、StopCamera(cameraId)、SetCameraFormat(w,h,fps);
    • IPluginCameraFrameSink(宿主实现):IsActive、Submit(byte[] bgr24, w, h, stride, ts);
    • PluginCameraDescriptor(Id / DisplayName / Capabilities / SupportsPropertyControl)、
      PluginCameraCapability(W/H/FPS)。
  • HostApiRequirement.CurrentApiVersion 1.13.0 → 1.14.0(按文件注释的维护规则:新增对外接口必须抬次版本号)。
  • README.md 增加 camera provider 章节;rules/plugin_sdk.md 更新服务表 + 新增使用章节。

2. 宿主(Ink Canvas/)

文件 改动
Helpers/CameraProviderRegistry.cs(新增 556 行) 提供者注册表 + IPluginCameraFrameSink 实现。双缓冲“最近一帧”(避免逐帧分配大数组)、活动推流生命周期、CamerasChanged/FrameSubmitted 事件、RenderLatestFrame/GetLatestFrameBitmapSource
Plugins/Services/CameraProviderService.cs(新增 59 行) SDK 服务的宿主实现(DI 单例)
App.xaml.cs 向插件 DI 注册 ICameraProviderService
Helpers/ICameraService.cs CameraInfo 增加 IsProviderCamera / ProviderId / ProviderCameraId
Helpers/DirectShowCameraService.cs(+329) ① RefreshCameraListAsync 把 provider 摄像头合并进 AvailableCameras;② EnumerateResolutionsAsync 用插件声明的能力填充 NativeResolutions/AllResolutionFpsCombos(在调用线程内联填充,避免与 UI 遍历竞争);③ StartPreviewAsync 为 provider 摄像头走推流分支(转发 FrameReceived、GetCurrentFrameAsBitmap*);④ ProbeCameraPropertiesAsync 对 provider 摄像头短路(属性滑块置灰)
MainWindow.xaml VideoPresenterSpecialModeContainer 内新增 VideoPresenterProviderImage(WPF Image)
MainWindow_cs/MW_VideoPresenter.cs(+493) 非 DirectShow 渲染路径:插件帧 → WriteableBitmap → Image;与 VideoCaptureElement 共享 LayoutTransform/RenderTransform 实例(缩放/旋转/镜像/批注一致);拍照取最近帧后走现有旋转/镜像/矫正/插画布管线;生命周期(切换摄像头/分辨率、照片页、退出展台、卸载插件);CamerasChanged 时刷新下拉框
Plugins/PluginManager.cs 插件卸载/加载失败时按 ProviderId 兜底清理 provider
docs/camera-provider.md(新增 257 行) 上游文档:最小接入、API 参考、宿主实现、与虚拟 DirectShow 路线对比、限制、本机自用最小改动集

3. 关键实现点

  1. 单一下拉框数据源不变:provider 摄像头被合并进 ICameraService.AvailableCameras,
    所有现有列表/索引/持久化/预选逻辑(videoPresenterLastCameraName 等)零改动即可工作。
  2. 渲染复用现有变换:VideoPresenterProviderImage 的 LayoutTransform/RenderTransform
    在代码里指向 VideoCaptureElement 的同一批实例,因此镜像(VideoPresenterFullCanvasMirror)、
    旋转(VideoPresenterFullCanvasRotation)、缩放/平移(ApplyBoothPreviewTransform)、
    墨迹批注、照片页变换全部自动一致,无需复制逻辑。
  3. 拍照不依赖 D3D 回读:provider 摄像头激活时取 CameraProviderRegistry.GetLatestFrameBitmapSource(),
    之后完全复用现有 path 2(旋转/镜像/纸框矫正/插照片栏)。
  4. 线程安全:插件可在任意线程 Submit;宿主在锁内拷贝到双缓冲,UI 线程合并渲染通知并用
    WriteableBitmap.WritePixels 更新,插件缓冲区可立即复用。
  5. 兼容性优先:DirectShowCameraService 的所有 DirectShow 分支保留原逻辑,只在入口处
    根据 CameraInfo.IsProviderCamera 分流;PluginManager 的清理对不注册 provider 的插件是空操作。

Plugins can now expose their own camera sources to the built-in video
presenter (booth) without registering a system-level DirectShow device.

- SDK: IPluginCameraProvider / IPluginCameraFrameSink / PluginCameraDescriptor,
  service IProviderCameraService; CurrentApiVersion 1.13.0 -> 1.14.0
- Host: CameraProviderRegistry, provider cameras merged into the booth camera
  list, non-DirectShow render path (WPF Image sharing the VideoCaptureElement
  LayoutTransform/RenderTransform instances), capture via latest frame reusing
  the existing insert-to-canvas pipeline, lifecycle cleanup on plugin unload
- Docs: docs/camera-provider.md, plugin_sdk rules, SDK README

Verified: host builds (x64 Release, 0 errors); 27 host-integration assertions
pass; GUI smoke test renders provider frames in the video presenter and captures
a photo. Existing DirectShow cameras and plugins are unaffected.
@CJKmkp

CJKmkp commented Oct 24, 2026

Copy link
Copy Markdown
Member

请确认无误后可合并

@AstrZero

Copy link
Copy Markdown
Collaborator

谁确认喵喵,要我跟进嘛 @CJKmkp

@CJKmkp

CJKmkp commented Oct 24, 2026

Copy link
Copy Markdown
Member

你来吧

@CJKmkp
CJKmkp requested a review from AstrZero October 24, 2026 09:33
@CJKmkp CJKmkp added the 新特性 New feature or request label Oct 24, 2026
@PrefacedCorg

Copy link
Copy Markdown
Collaborator

玩下周跟进 你们都别动

- 展台特殊模式 + 漫游时跳过“多指书写”手势拦截,让 WPF/WinRT 双指捏合
  进入 VideoPresenterSpecialMode_ManipulationDelta / ApplyWinRTVideoPresenterGesture,
  而不是被漫游/墨迹手势吃掉,表现为“墨迹能缩放、摄像头画面只平移”
- WinRT 路径把展台特殊模式分支提到漫游分支之前
- 非多指书写时信任 WPF 报的 manipulator 数;有缩放增量时也允许缩放,
  避免触点记账偏少把捏合误判成单指
漫游双指手势走的是 TransformBoardRoamingContent:它对板书墨迹/图片应用完整
矩阵,但对视频展台预览只同步 translationDelta(旧假设“展台不支持缩放”),
所以漫游里墨迹能缩放、实时画面和相册照片不动。

- 双指手势把 previousMidpoint 作为锚点传给 TransformBoardRoamingContent
- 展台预览按同样的 scale 等比缩放,并保持“缩放锚点 + 平移”的变换一致
- ApplyBoothPreviewTransform 同时更新实时画面与冻结照片的 Scale,相册页同样生效
- 不再依赖 Board 的双指缩放开关:漫游使用自己的 IsEnableTwoFingerZoomRoaming
- 回退上一版试探性的“漫游时跳过多指书写拦截”改动,避免抢漫游手势
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

新特性 New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants