Neon: 把本地视频点亮成系统虚拟摄像头
视频处理WindowsC++Media Foundation会议软件里想放一段预先录好的视频,而不是自己的脸。市面上的办法基本是装 OBS 加虚拟摄像头插件,或者找那种要装内核驱动的商业软件。
Neon 是我写的一个更直接的东西:一个 Windows 11 上的虚拟摄像头服务,加一个桌面控制台。建几台虚拟相机,每台挂一个播放列表,Teams / OBS / 浏览器里直接选它出镜。
底层走 Media Foundation 的 MFCreateVirtualCamera,不用 DirectShow,不用签名驱动,用户态就能跑。

它解决什么
Windows 上做虚拟摄像头,历史上只有 DirectShow 一条路:注册一个 COM filter,指望消费方还认这套老 API。问题是现代应用早就迁到 Media Foundation 了,Teams、新版 Edge、UWP 相机应用都不吃 DirectShow 的虚拟设备。
Windows 11 22000 之后 Microsoft 补上了正经的入口 —— MFCreateVirtualCamera。注册进去的相机是系统级的、Frame Server 托管的真设备,任何走标准相机枚举的软件都能看见,不需要装驱动,也不需要给内核签名。
Neon 就是围绕这个 API 搭的完整东西:
最多 16 台相机同时跑,名字、分辨率、帧率各自独立(默认 1280x720@30,上限 4096、120fps)
每台一个播放列表,顺序播放、上下条环绕、精确 seek、三档循环
每台独立选输出声卡,音量静音即时生效
全部功能都有 HTTP API,界面只是 API 的一个客户端
帧的一生
这是理解整个项目最重要的一张图。关键在于解码和出帧发生在两个不同的进程里。
为什么要拆两个进程?因为 media source 那半边不是我说了算 —— 它是一个 COM 对象,由 Windows 的 Frame Server 服务加载到它自己的 svchost 里。我没法在那个进程里开解码器、开 WASAPI、跑 HTTP 服务,那是别人的地盘,出问题会连带搞挂共享宿主。
所以分工是这样:
dfvcam.exe跑在用户会话里,干重活 —— 解码、缩放、音频渲染、HTTP 控制、配置持久化dfvcam_source.dll是那个 COM media source,只干一件事:从 pipe 收 NV12 帧,交给 Frame Server
好处是解码崩了不会拖累系统服务,坏处是多了一次帧拷贝和一道 IPC。对 1080p30 来说这个代价可以接受,实测跑得满。
服务不在的时候相机不消失,media source 会输出一张深灰占位图,服务回来自动重连。这一点比"服务没开相机就从列表里消失"体验好得多 —— 会议软件的设备选择记忆不会被打断。
两个进程怎么对话
pipe 上是自定义的二进制协议,双向字节流加显式分帧。每条消息一个 16 字节头加载荷,magic 是 "DFVC"。握手和推流的时序:
几个设计点值得说:
slot 是预注册的。16 台相机对应 16 个写死在代码里的 CLSID,安装时一次性注册到 HKLM。相机实例启动时绑一个 slot,pipe 名字就是 \\.\pipe\dfvcam.slot00 到 slot15。这样做是因为 COM 注册需要管理员权限,而日常起停服务不该要提权 —— 提权的事只在 dfvcam install 那一次做完。
streaming 状态是自动开播的触发条件。Frame Server 只在真有消费者打开相机时才启动流,media source 把这个事件转成 StreamState 报上来。引擎看到 streaming_clients 从 0 变 1 且自己空闲,就自动开始播放。会议软件里一选中相机,画面就来了,不用先去界面点播放。
帧的时间戳由 source 重新打。载荷里带的 pts_100ns 只是参考值,media source 收到帧时按实时时钟重新戳。虚拟摄像头对消费者是 live 源,语义上不该有"回放进度",消费端看到的帧率由 Frame Server 的拉流节奏决定,画面没更新时重复末帧是正常的。
上手三步
构建要 VS2022 加 Windows SDK 22000+,依赖走 vcpkg manifest 自动装(drogon / spdlog / simdjson / WIL / gtest)。
cmake --preset vs2022
cmake --build --preset release预设是 x64-windows-static,产物是单文件全静态的 dfvcam.exe(约 8MB),不用装 VC++ Redist。版本号含 git hash,直接嵌进文件属性和 dfvcam version。
装好之后三条命令,提权只发生在第一条。
dfvcam install # 管理员,一次性
dfvcam doctor # 自检,免提权
dfvcam serve --port 8686install 干两件事:把 DLL 复制到 %ProgramData%\dfvcam\bin,在 HKLM 注册 16 个 slot CLSID。只有这一步要管理员。doctor 不需要提权,逐项检查并聚合退出码,任一项 FAIL 就退 1,可以直接串到脚本里。serve 是普通用户跑的。相机是 Session 生命周期 —— 服务在,相机在;服务退出,系统里就没有这些摄像头了。下次 serve 按 config.json 全部重建。
不想自己构建就直接下 Releases 里的安装包。
播放这块的行为
播放列表的语义花了不少心思,因为"改列表"和"正在播"这两件事撞在一起的时候,直觉答案往往是错的。
播放中换列表不断流:当前视频还在新表里就不打断,下标对齐到它的新位置,播完按新表顺序走;只有当前视频被移除了才重开
也可以明确指定start_index立即切到第几条
三档循环:不循环播到尾停、单条循环、整表循环
上一条下一条总是环绕,跟循环档位无关
暂停是音画同点续播,恢复没有跳变
坏文件自动跳过并记下原因,整表都打不开才进 error 态
seek 是精确落点,不是就近关键帧 —— 关键帧间距会用解码速度追上去。
音画同步走的是同一个 Source Reader 按时间戳交错解码,视频对 steady clock 步调,音频靠 WASAPI 背压。长视频没做设备时钟漂移校正,小时级的片子或者对漂移敏感的场景要注意。
音频这道坎
这是整个项目最没法绕过去的限制,值得单独说:Media Foundation 虚拟摄像头只有视频。
Windows 没有用户态的"虚拟麦克风" API。要让系统多出一个录音设备,必须写签名的内核驱动,这不是一个用户态服务能做的事。
Neon 的做法是把视频音轨渲染到你选的输出设备(注意是输出,不是录音设备)。想让会议软件把它当麦克风,得配 VB-Audio Virtual Cable:dfvcam 输出到 "CABLE Input",会议软件选 "CABLE Output" 当麦克风。
不优雅,但这是用户态方案的天花板。写清楚比假装没这回事好。
Neon 控制台
后端全功能都有 HTTP API,界面是 Tauri 2 加 React 19 写的一个客户端,默认打 http://127.0.0.1:8686。

拖拽文件入窗、点条目弹小窗预览(静音起播,不干扰推流音频)、进度条本地插值所以不会逐秒跳、800ms 轮询、服务掉线自动重连并在离线屏显示底层原因。安装版离线时点"立即重试"会顺手拉起捆绑的后端,不用开终端。

关窗行为可以配:每次询问、最小化到通知栏托盘、只退界面服务继续、退出并停服务。深浅双主题默认跟随系统。
HTTP API 的完整参考在仓库 docs/http-api.md。命令类接口是异步的,返回 200 只表示命令入队,实际效果轮询 GET /cameras/{id} 看 playback.state 和 last_error。
默认免认证只听回环。要暴露到局域网,在 config.json 填 auth 节就启用 JWT,HS256 手写实现,签名密钥每次启动随机生成,所以服务重启后所有旧 token 失效、界面会自动回到登录屏。
实测数字
不想只说"能跑",所以贴几个验收时量到的:
双路 1080p30 并发拉流:29.9 / 30.2 fps,满帧
低帧率相机 2fps 探针:2.2 fps(修复分片睡眠之前是 4.9,超发了一倍多)
16 槽占满时创建第 17 台:400 拒绝,doctor不误报
手改坏配置 4097x719@999 加载:回退到 1280x720@30,音量夹到 100,并告警
单元测试 40 项,行为探针最多一轮 105 项
可复现性:单测剥掉墙钟两跑逐字节一致,配置重启双写 SHA256 一致
最后一条是我现在写完东西必做的一步。第一次认真做这个校验时,前两轮全绿,第三轮才发现外部评测的输出指纹三次都不同 —— 原因是把墙钟时间放进了对照表,让"同样输入同样输出"的核对整个失效。跑一次绝对发现不了。
已知限制
Windows 11 22000+ 专用,因为
MFCreateVirtualCamera就是那时候才有的
改名等于重建虚拟摄像头(friendlyName 是设备身份的一部分),已经打开该相机的应用要重新选一次设备
系统会在名字后面强制追加 "Windows Virtual Camera" 后缀,这是 OS 行为,防止伪装成物理摄像头
不支持图片或纯音频文件作播放项,得有视频流
分辨率帧率建相机时固定,所有视频拉伸到该格式,不加黑边
仓库
代码在 newdee/neon,MIT。当前 0.1.4。
media source 的接口面和事件契约参考了 smourier/VCamSample,那是我读 MFCreateVirtualCamera 时唯一找到的完整可跑样例,省了我大量时间。
有 Windows 11 的话装个包试试,一条视频当摄像头这件事比想象的有用 —— 至少下次开会不想露脸的时候有个正当理由了。