恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
VCAM虚拟摄像头:DirectShow实现视频采集设备伪装
首页
资讯中心
/
VCAM虚拟摄像头:DirectShow实现视频采集设备伪装
VCAM虚拟摄像头:DirectShow实现视频采集设备伪装
发布时间:2026/10/16 6:52:39
简介基于微软DirectShow多媒体框架实现的虚拟摄像头工程面向Windows多媒体、视频采集和上层应用开发者解决没有物理摄像头时模拟视频输入的问题可用于软件测试、功能演示、直播推流或与第三方程序集成。压缩包共包含154个文件以C/C头文件与源文件为主同时带有Visual Studio解决方案、工程配置、可注册的过滤器组件AX、调试符号与编译中间文件资源整体约71.33MB。目前已有2450人浏览学习是学习和复用DirectShow过滤器开发的一份实例。工程从虚拟源过滤器的创建入手完整演示了过滤图构建、视频样本抓取、多路视频混合渲染等关键流程同时覆盖设备注册、多线程数据生成、错误处理与性能调优等工程化细节并基于Windows 10和Visual Studio 2015环境整理便于直接打开编译、结合断点调试理解内部机制还可按需改造成屏幕采集、网络视频流接入等扩展方案。1. VCAM 虚拟摄像头一个用 DirectShow 把自己伪装成摄像头的 Source Filter一次线上直播前我需要把一台演示机的画面推进视频会议软件但这类软件只认摄像头、不认窗口共享源旁边人随口说了一句“装个虚拟摄像头不就行了”。当时心里想的是VCAM 虚拟摄像头听着玄学实际拆开看就是一个用 DirectShow 写的 Source Filter把自己注册成系统视频采集设备后任何能调用摄像头的应用都会把它当成普通摄像头来拉流。这份工程代码不大核心是一个 COM 组件加一个输出 Pin但链路完整覆盖设备注册、媒体协商、帧注入全流程非常适合对流媒体管线和 Windows 多媒体架构感兴趣的开发者也适合所有需要把自定义视频源喂给会议、直播软件的人。2. 架构设计虚拟摄像头从注册到出帧的完整链路2.1 整体数据流 Source Filter、Output Pin 与下游应用怎么对接DirectShow 是 Windows 上的一套流媒体框架核心是用 Filter Graph 把数据流串起来。一个视频采集链路里通常有 Source Filter采集源、Transform Filter转码处理、Renderer渲染或推送。VCAM 虚拟摄像头就是一个纯 Source Filter它不依赖物理设备帧数据来自程序内部——可以是屏幕截图、图片轮播、RTSP 流也可以只是一张彩色测试图。应用打开摄像头的完整时序是这样的先枚举“视频输入设备”类别得到设备列表用户选中 VCam 后应用按设备对应的 CLSID 创建 Filter 实例把实例加进 Filter Graph和下游 Pin 连接连接时双方协商媒体类型协商通过后应用拉流Graph 引擎周期性调用 Source Filter 的 FillBuffer 喂帧。整个过程中开发要管的只有两件事让系统在设备列表里能看到 VCam以及让 Pin 在 FillBuffer 里把帧数据交出去。数据流向用文字描述就是工作线程/回调 → FrameCache共享帧缓冲 → OutputPin::FillBuffer → 下游 Filter → 视频会议/直播软件这里要把“虚拟摄像头”拆成两个问题设备注册让系统相信它是一个摄像头和帧注入让摄像头有画面可发。前者靠注册表和 INF后者靠实现 DirectShow 的 Pin 接口。后面两章分别解决这两个问题。提示DirectShow 是经典用户态方案对旧版视频采集生态兼容性最好不需要驱动签名。但它伪装的是一个 DirectShow 设备Windows 商店类应用走 MediaFoundation 设备枚举不一定会认商业级虚拟摄像头通常还要追加一层内核态 KS 代理。这份工程聚焦纯 DirectShow 部分能覆盖绝大多数桌面直播与会议软件。2.2 工程结构一个 DLL、一个 Filter、一个 Pin整个工程的结构很直接一个进程内 COM DLLDLL 里只有一个 Filter 类Filter 里挂一个输出 Pin。文件职责见下表文件职责src/vcam.cppFilter 主类实现 IBaseFilter 接口管理 Pin 和帧源src/outputpin.cpp输出 Pin实现 FillBuffer、媒体协商、缓冲分配src/framesource.cpp帧源抽象示例实现是彩色测试图生成器src/register.cppDllRegisterServer / DllUnregisterServer 注册逻辑src/vcam.defDLL 导出表导出 COM 入口函数install/vcam.infINF 安装脚本复制 DLL 并写注册表win32/vcam.slnVS 工程引用 DirectShow 基类库依赖方面工程建立在 DirectShow 基类库BaseClasses之上。BaseClasses 是 Windows SDK 自带的一套 C 模板类把 IUnknown、IMemAllocator、IMediaSample 这些接口做了一层封装。写一个 Source Filter 不需要从零实现 COM 接口只要继承 CBaseFilter 和 CBaseOutputPin把注意力放在业务方法上。这也是整个虚拟摄像头工程最省心的地方基类库把引用计数、枚举 Pin、缓冲池这些琐碎但容易写错的样板代码全包了。2.3 设备注册把 Filter 挂进视频输入设备类别只写一个 COM DLL 是不够的。视频软件枚举摄像头时枚举器扫描的是“视频输入设备”类别CLSID_VideoInputDeviceCategory只有出现在这个类别下的 CLSID 才会被当成摄像头。所以注册要分两步标准 COM 注册加类别注册。标准 COM 注册由 DllRegisterServer 完成写 HKCR\CLSID{GUID} 下的 InprocServer32类别注册要额外在 VideoInputDeviceCategory 的 Instance 分支下建一个条目。用 INF 安装脚本最稳妥[DefaultInstall] CopyFiles vcam.dll AddReg VCam.AddReg [DestinationDirs] DefaultDestDir 11 [VCam.AddReg] ; 标准 COM 服务器注册 HKLM, SOFTWARE\Classes\CLSID\{B4C1B2A0-1234-4C67-9E58-2A5C6F3D1E88},,,VCam Filter HKLM, SOFTWARE\Classes\CLSID\{B4C1B2A0-1234-4C67-9E58-2A5C6F3D1E88}\InprocServer32,,,%11%\vcam.dll HKLM, SOFTWARE\Classes\CLSID\{B4C1B2A0-1234-4C67-9E58-2A5C6F3D1E88}\InprocServer32,ThreadingModel,,Both ; 挂入视频输入设备类别让应用能通过摄像头枚举发现它 HKLM, SOFTWARE\Classes\CLSID\{860BB310-5D01-11D0-BD3B-00A0C911CE86}\Instance\{B4C1B2A0-1234-4C67-9E58-2A5C6F3D1E88},CLSID,,{B4C1B2A0-1234-4C67-9E58-2A5C6F3D1E88} HKLM, SOFTWARE\Classes\CLSID\{860BB310-5D01-11D0-BD3B-00A0C911CE86}\Instance\{B4C1B2A0-1234-4C67-9E58-2A5C6F3D1E88},FriendlyName,,VCam Virtual Camera参数说明{B4C1...} 是工程自己的 Filter CLSID编译前替换成你生成的 GUID{860BB310-...} 是视频输入设备类别 GUID系统常量不能改%11% 是 INF 里的系统目录占位符32 位 DLL 装 64 位系统时 INF 会自动选 SysWOW64ThreadingModelBoth 表示对象创建不限定 COM 线程套间DirectShow 的线程模型要求这个值。FriendlyName 就是视频软件里显示的设备名建议别和已有设备重名否则用户分不清。这里有一个很容易忽略的坑64 位系统上 32 位 DLL 用 regsvr32 注册时注册表会被重定向到 WOW6432Node而 64 位枚举进程读的是 64 位视图两边可能对不上。所以开发期验证要分两步走先在注册表里确认键写到了哪个节点再拿枚举工具验证。完整 INF 在给用户分发时还要补 SourceDisksFiles 段DLL 和 INF 放同一目录这里不展开。3. 核心实现Filter 与 Pin 的接口骨架、帧注入与媒体协商3.1 Filter 主类继承 CBaseFilter把 Output Pin 装进去工程使用 BaseClasses 的 CBaseFilter 而不是纯手写 COM。Filter 类只做三件事构造函数里创建输出 Pin 和帧源重写 GetPinCount / GetPin 让 Graph 引擎能拿到输出 Pin实现 Pause/Stop 控制帧源线程启停。// vcam.h class CVCamFilter : public CBaseFilter { CVCamOutputPin* m_pPin; CVideoFrameSource m_frameSource; public: static CUnknown* WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT* phr); CVCamFilter(LPUNKNOWN pUnk, HRESULT* phr); ~CVCamFilter() override; int GetPinCount() override; CBasePin* GetPin(int n) override; STDMETHODIMP Pause() override; STDMETHODIMP Stop() override; }; // vcam.cpp CVCamFilter::CVCamFilter(LPUNKNOWN pUnk, HRESULT* phr) : CBaseFilter(TEXT(VCam Source), pUnk, m_pLock, CLSID_VCamFilter), m_frameSource(640, 480) { m_pPin new CVCamOutputPin(GetOwner(), this, m_pLock, m_frameSource); }逻辑说明构造函数的 pUnk 来自 COM 类工厂CBaseFilter 内部靠它维持引用计数m_pLock 是 CBaseFilter 内置的临界区对象所有 Pin 方法共享这把锁避免枚举和拉流时状态错乱。Pause 是 DirectShow 的“准备运行”状态帧源线程在这个时机启动Stop 时 Join 线程确保 Filter 释放时没有线程还在写 FrameCache。这两个方法的时序错了会出现“Filter 已停止但线程还在写内存”的经典崩溃。DLL 的 COM 入口由基类库的 CFactoryTemplate 数组提供配合 vcam.def 导出四个函数EXPORTS DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATEvcam.def 里这四行不能省。DllGetClassObject 是 COM 创建对象的入口DllRegisterServer / DllUnregisterServer 对应 regsvr32 的注册和卸载。如果把 DEF 文件里的导出设成 PUBLIC会被系统误认为普通 DLL 向外导出功能函数倒也不影响注册但看起来不干净。3.2 输出 Pin 的 FillBuffer每一帧是怎么填进去的下游每次拉流时Graph 引擎从缓冲池取一个 IMediaSample调用输出 Pin 的 FillBuffer。在这个方法里把帧数据拷贝进 sample再打时间戳返回。// outputpin.cpp HRESULT CVCamOutputPin::FillBuffer(IMediaSample* pSample) { BYTE* pData nullptr; HRESULT hr pSample-GetPointer(pData); if (FAILED(hr) || pData nullptr) return E_UNEXPECTED; // 帧源线程持续把最新画面写入 FrameCache这里只做一次性拷贝 m_pFilter-LockFrameCache(); memcpy(pData, m_pFilter-GetFrameCache(), m_imageSizeInBytes); m_pFilter-UnlockFrameCache(); // 时间戳单位是 100ns10000000 / fps 每帧时长 REFERENCE_TIME rtDuration 10000000 / m_pFilter-GetFps(); pSample-SetTime(m_rtStart, m_rtStart rtDuration); pSample-SetSyncPoint(TRUE); // 标记这一帧可作为随机访问点 m_rtStart rtDuration; return S_OK; }参数说明m_imageSizeInBytes 在 SetMediaType 阶段按“宽 × 高 × 像素字节数”算好避免 FillBuffer 里重复计算m_rtStart 是连续递增的播放时间如果画面需要和别的流对时间轴也可以改成从 m_pFilter 拿系统时间SetSyncPoint 对应 MJPEG/MPEG 压缩格式的关键帧概念RGB32 下不强制但建议保留Graph 的 seek 逻辑依赖这个标记。拷贝帧缓存必须加锁因为帧源线程和 FillBuffer 不在同一个线程。配套的帧源循环是一个独立线程按目标帧率持续往 FrameCache 写测试图// framesource.cpp DWORD WINAPI FrameSourceThread(LPVOID param) { CVCamFilter* pFilter (CVCamFilter*)param; while (pFilter-IsRunning()) { pFilter-LockFrameCache(); DrawTestPattern(pFilter-GetFrameCache(), pFilter-GetWidth(), pFilter-GetHeight()); pFilter-UnlockFrameCache(); Sleep(1000 / pFilter-GetFps()); } return 0; }这里有一个实际工程里的常见取舍真正上生产时 FrameCache 应该是双缓冲线程画新帧时 FillBuffer 读旧帧避免 memcpy 时读到半帧导致撕裂。测试工程图省事用一把锁包全拷贝改成屏幕捕获源时再换双缓冲后面二次开发章节会提到。3.3 媒体协商应用要 YUY2而内部画面是 RGB32DirectShow 连接 Pin 时下游会给出它支持的媒体类型Source Filter 要回答“这个格式我能不能给”。CheckMediaType 收窄到“视频 RGB32 或 YUY2”DecideBufferSize 告诉分配器每块缓冲多大、给几块。// outputpin.cpp HRESULT CVCamOutputPin::CheckMediaType(const CMediaType* pmt) { if (pmt-majortype ! MEDIATYPE_Video) return VFW_E_TYPE_NOT_ACCEPTED; if (pmt-formattype ! FORMAT_VideoInfo) return VFW_E_TYPE_NOT_ACCEPTED; if (pmt-subtype MEDIASUBTYPE_RGB32) return S_OK; if (pmt-subtype MEDIASUBTYPE_YUY2) return S_OK; return VFW_E_TYPE_NOT_ACCEPTED; } HRESULT CVCamOutputPin::DecideBufferSize( IMemAllocator* pAlloc, ALLOCATOR_PROPERTIES* pProps) { long imageSize m_pFilter-GetWidth() * m_pFilter-GetHeight() * 4; pProps-cbBuffer (imageSize 7) ~7; // 8 字节对齐 pProps-cBuffers 4; // 四块缓冲轮转 ALLOCATOR_PROPERTIES actual; return pAlloc-SetProperties(pProps, actual); }如果最终协商到 YUY2但内部帧缓存是 RGB32就需要在 FillBuffer 里做一次像素转换。常见做法是内置一个转换函数在 memcpy 之后调 ConvertRGB32ToYUY2。这里有笔性能账要算清格式1080p 单帧字节30fps 带宽RGB321920 × 1080 × 4 ≈ 8.29 MB≈ 249 MB/sYUY21920 × 1080 × 2 ≈ 4.15 MB≈ 124 MB/s结论很明确默认优先应答 YUY2带宽几乎减半。注意 CheckMediaType 答应的格式必须和 DecideBufferSize 的 cbBuffer 对得上——答应了 YUY2 却按 RGB32 算缓冲下游会看到半帧黑点或花屏。GetMediaType 按固定列表返回可用的媒体类型供下游枚举HRESULT CVCamOutputPin::GetMediaType(int iPosition, CMediaType* pmt) { if (iPosition 0) FillVideoInfoHeader(pmt, MEDIASUBTYPE_YUY2); else if (iPosition 1) FillVideoInfoHeader(pmt, MEDIASUBTYPE_RGB32); else return VFW_S_NO_MORE_ITEMS; return S_OK; }iPosition 从 0 开始递增返回 VFW_S_NO_MORE_ITEMS 表示枚举结束。这个列表就是视频软件设置面板里能选到的格式列表默认只给两档是故意的避免把几十种组合一下子丢给下游让应用的下拉框变得不可用。后面二次开发章节会把它扩展成动态组合。4. 构建、注册与验证把工程跑起来的最小闭环4.1 编译工程先有 strmbase.lib再编 vcam.sln基类库 BaseClasses 是编译 vcam 的前提。Windows SDK 自带源码通常位于 SDK 安装目录下的 DirectShow\BaseClasses和 vcam 工程是两个相互独立的项目。编译顺序不能反先编 BaseClasses 得到 strmbase.lib再打开 vcam.sln。步骤细分编译 BaseClasses生成 strmbase.libRelease或 strmbasd.libDebugVS 打开 vcam.sln把 include 目录指到 BaseClasses 源码目录预处理器加 _CRT_SECURE_NO_WARNINGS老代码里的 strcpy/sprintf 会触发 C4996 警告链接器附加依赖加上 strmbase.lib、winmm.lib、ole32.lib。命令行编译的话在 BaseClasses 目录下直接执行nmake -f makefile注意 nmake 必须在 Developer Command Prompt 环境里运行直接开普通 cmd 会报找不到编译器或者环境变量未定义。这里最容易翻车的是运行库不一致。BaseClasses 默认按多线程运行库编译vcam 工程如果选成“多线程调试 (/MTd)”链接阶段会报“无法解析的外部符号”或 LNK2038。我一般会把两个工程的运行库统一设成 Release/MT调试阶段统一 Debug/MTd不混用。出现这种链接错误时先别查代码检查两个工程的项目属性运行库设置。4.2 注册 DLL 的两种姿势编译产物 vcam.dll 需要进系统目录和注册表。开发期最省事的是 regsvr32regsvr32 /s vcam.dll/s 是静默参数。64 位系统上如果编译的是 32 位 DLL必须用 32 位版本的 regsvr32C:\Windows\SysWOW64\regsvr32.exe /s vcam.dll注册成功后先拿 ffmpeg 的 dshow 枚举验证系统是否把它当作视频设备ffmpeg -list_devices true -f dshow -i dummy输出里出现 “VCam Virtual Camera”说明设备和类别都挂对了。这一步能区分“COM 注册失败”和“类别注册遗漏”CLSID 正确但类别缺失时regsvr32 不报错但 ffmpeg 枚举不到设备。另外注意 ffmpeg 会把音频和视频设备列在一起找的是 video capture 那一栏别看到 audio 设备就当成功。分发到别的机器时regsvr32 要手敲命令、容易漏更适合 INF 安装。前面 2.3 的 INF 文件可以直接右键“安装”也可以用打包脚本调用 SetupInstallFromInfSection。两种方式对比方式regsvr32INF适用场景开发期快速验证分发给其他机器是否写类别取决于 DllRegisterServer 实现显式声明位数处理手动选 32/64 位 regsvr32自动按系统位数复制 DLL卸载regsvr32 /u控制面板卸载卸载的后悔药是 regsvr32 /u vcam.dll。如果 DLL 正在被引用卸载会失败先关闭所有使用摄像头的应用再试。这个“DLL 被占用”的坑在调试期会反复出现后面避坑章节会展开。4.3 用 GraphEdit 看板子和帧率注册完只是第一步还要验证 Filter 本身能不能被拉出画面。SDK 自带的 GraphEdit 工具可以把 “VCam Source” 从 DirectShow Filters 列表拖到白板上再接一个 Video Renderer按运行键启动。白板上能看到数据流在跑、帧率数字有跳动基本说明 Source Filter 的接口链路是通的。如果 GraphEdit 里一直连不上问题大概率在媒体协商而不是注册表。连接失败时会弹一个错误码VFW_E_CANNOT_CONNECT 是连接不上VFW_E_TYPE_NOT_ACCEPTED 是类型不接受。前者查 CheckMediaType 是否写得太严格后者查 GetMediaType 枚举项和 CheckMediaType 是否自相矛盾——枚举时说支持 RGB32CheckMediaType 却拒绝 RGB32下游永远连不上。GraphEdit 里还能调出属性页查看当前协商到的格式。对虚拟摄像头这种调试型项目我习惯先在 GraphEdit 验证、再用视频软件验证因为会议软件会把摄像头 API 封装得很深出问题不好定位是设备侧还是应用侧。5. 排查与避坑虚拟摄像头部署时的几个高频问题这一章写给“能编译但跑不通”的人。以下问题在虚拟摄像头工程里反复出现每条按“现象 → 原因 → 解决”记录。5.1 应用枚举不到 VCam 设备现象regsvr32 提示成功但 ffmpeg 的 dshow 枚举列表里没有 VCam视频软件里也找不到。注册表里能看到 CLSID 键。原因DllRegisterServer 只写了标准 COM 注册没写 VideoInputDeviceCategory\Instance 下的类别入口或者写的是 64 位视图的注册表但 32 位 DLL 的注册被重定向到了 WOW6432Node两边 KEY 对不上。解决先在 regedit 手工检查 {860BB310-...}\Instance 下有没有工程 CLSID没有就补 2.3 的 INF 安装有就用 SysWOW64\regsvr32.exe 重注册并确认 DLL 位数和设备枚举进程位数一致——64 位进程不能加载 32 位 DirectShow Filter这是位数限制不是注册表问题。5.2 能枚举到设备但画面全黑现象GraphEdit 里 Filter 状态是 Running帧率有跳动但 Video Renderer 输出纯黑视频软件里预览同样黑屏推流出去也是黑。原因最常见是 FillBuffer 没把数据填进 IMediaSample或者填进去的缓冲内容是全零另一个是高发原因协商答应了 YUY2但 FillBuffer 里仍按 RGB32 拷贝像素格式和实际字节数对不上。解决在帧源里先画一张彩色渐变测试图代替真实画面如果彩色图能出来说明管线正常问题在全零缓冲同时检查 FillBuffer 里 memcpy 的字节数是否和 DecideBufferSize 里计算的一致。如果 CheckMediaType 允许多个格式建议在 SetMediaType 里输出实际选中的 subtype确认下游选的是哪种。5.3 分辨率切到 1080p 后 CPU 飙升、画面撕裂现象默认 640×480 流畅改成 1920×1080 后 CPU 占用翻倍画面出现横向撕裂或明显卡顿。原因RGB32 在 1080p30 下带宽约 250 MB/sFillBuffer 里整帧 memcpy 加帧源线程再画一帧等于一帧被拷贝多次帧缓存不加双缓冲时FillBuffer 可能读到半帧。解决把协商主格式改成 YUY2字节数减半帧缓存升级为双缓冲帧源线程写前台帧FillBuffer 读后台帧DecideBufferSize 的 cBuffers 从 8 降到 4减少内存分配压力。1080p 还卡的话看是否在每次 FillBuffer 里做了像素转换把转换挪到帧源线程做一次而不是每次拉帧都转。5.4 32 位 DLL 在 64 位系统上注册失败提示“模块已加载但入口点未找到”现象开发机是 64 位 Windows编译出 32 位 vcam.dll双击 regsvr32 弹“模块已加载但找不到入口点 DllRegisterServer”。原因双击运行的 regsvr32.exe 是 64 位版本加载 32 位 DLL 时找不到 32 位函数入口地址。这是位数错配不是 DLL 写错。解决显式调用 SysWOW64 目录下的 32 位 regsvr32.exe同时确认 vcam.dll 的依赖运行库没有混入 64 位系统库。分发时直接用 INF由系统按目标架构复制对应位数 DLL能避免半数注册问题。5.5 能选格式但没有 1080p 选项现象ffmpeg 枚举到 VCam视频软件的设置有 640×480、800×600就是没有 1920×1080用户想要高清画面只能自己输入参数。原因GetMediaType 枚举项写死了固定列表里面没有 1080p有些应用只看枚举出来的列表不会主动请求未枚举的分辨率。解决GetMediaType 按 width/height/fps 的组合动态生成枚举项iPosition 从 0 到 N-1 返回一组视频信息头末尾 VFW_S_NO_MORE_ITEMS。枚举项不要一次给几十个组合常见做法是给 6~8 项覆盖 640×480、720p、1080p 各一档 30fps/60fps。提示如果应用仍然只显示 640×480检查 CheckMediaType 格式化视频信息头时是否漏了 biWidth/biHeight。下游拿到的 VIDEOINFOHEADER 尺寸为 0会默认退回最小项。把这两个字段填上多数“没有 1080p”的问题立刻消失。6. 二次开发把测试图帧源替换成屏幕捕获并暴露可配置分辨率6.1 帧源接口抽象现在工程的帧源画的是彩色测试图二次开发第一步就是把帧源抽象成接口让 FillBuffer 不关心画面从哪来class IFrameSource { public: virtual HRESULT GetFrame(BYTE* dest, LONG width, LONG height, REFERENCE_TIME* ts) 0; virtual LONG GetWidth() 0; virtual LONG GetHeight() 0; };FillBuffer 里从“memcpy 测试图”改成“pSource-GetFrame(pData, ...)”。换成屏幕捕获时GetFrame 内部用 BitBlt 抓主屏复制进 DIB 段或走 DXGI 桌面复制接口读纹理。BitBlt 方案在纯色静止画面下有 10%~20% CPU 开销DXGI Desktop Duplication 占用更低但要求较新的 Windows 版本工程里做两套实现按环境切换比较稳。6.2 暴露可配置的分辨率、帧率与格式调试期我会直接读一个 .ini 文件注册表也行但 .ini 在调试时改起来快。在 Filter 构造函数里加载配置把 width/height/fps/subtype 作为成员变量GetMediaType 按这组值生成枚举DecideBufferSize 按这组值算缓冲。这样切分辨率不需要重新编译 DLL放一台测试机改文件重启应用即可。更好的做法是给 Filter 实现 IAMStreamConfig 接口让应用在设置面板里动态改分辨率不用重启。实现 GetStreamCaps/SetFormat 两个方法内部同步 FrameCache 的尺寸和 FillBuffer 的 m_imageSizeInBytes。注意 SetFormat 之后要重新协商分配器否则下游缓冲还是旧尺寸会出现花屏。二选一的话我先做 .ini等界面需求来了再补 IAMStreamConfig。每次改完代码我的验证顺序固定成编译 → SysWOW64 的 regsvr32 重注册 → ffmpeg 枚举 → GraphEdit 拉板子跑一帧 → 改配置确认新尺寸生效。从那以后我每次改虚拟摄像头都强制走一遍这个最小流程宁可多花三分钟也不把黑屏问题留到下一步。希望帮到你。本文还有配套的精品资源点击获取