拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AMCap源码拆解:DirectShow视频捕获与Filter Graph构建指南

简介AMCap是一套基于DirectShow的视频采集示例程序核心价值在于演示如何通过Source Filter、Decoder Filter与Renderer Filter搭建完整过滤图完成实时视频预览与录制。对于刚接触DirectShow框架或需要快速实现摄像头采集功能的开发者这份资源提供了可直接运行的参考实现也可帮助理解过滤器之间的连接方式、参数配置以及DirectShow中常见的Source/Decoder/Renderer职责划分。压缩包共32个文件大小约6.06MB既包含2个exe可执行文件也包含5个cpp与5个h源码文件、Makefile编译脚本、rc/res/ico等工程资源以及obj/pch等编译中间产物便于对照代码和实际运行效果进行学习。包内还保留了程序说明文档解压后即可运行和调试AMCap程序通过查看源码还能学习过滤器图构建与视频参数设置的常见写法适合在此基础上扩展自定义filter或修改采集参数。资源已有258人学习适合作为DirectShow视频采集入门与二次开发的实用参考。1. 为什么 AMCap 这种老工具还是值得拆一遍如果你在 2025 年还在 Windows 上做摄像头采集大概率会直接调 Media Foundation或者干脆扔给 OpenCV 的 VideoCapture。但真正把 DirectShow 的 filter 图跑通、看过源码的人几乎都绕不开 AMCap 这个“最小可用的视频捕获示例”。它最早是 DirectShow SDK 自带的 demo体积小到只有一个 exe却能完成设备枚举、预览、格式切换、录像到 AVI 这一整套流程。拿到这个压缩包里的源码你会发现它比想象中适合做三件事第一快速验证一个摄像头驱动是否按 UVC 规范暴露了 DirectShow 接口第二作为自定义 filter 的宿主测试自己写的推流、截图或格式转换模块第三学习老旧但仍被大量工业相机、采集卡依赖的 DirectShow 编程模型。对刚入行的开发来说它是读懂 filter 图连接方式的第一手材料对老手来说里面 crossbar 和媒体类型协商的代码依然是排查采集兼容性问题时可以直接翻的“字典”。2. DirectShow 过滤图与 AMCap 的采集链路2.1 从 Capture Filter 到 Renderer 的拓扑DirectShow 的核心思路是“过滤器图”Filter Graph。AMCap 所做的工作本质上就是把视频采集设备封装成一个 Source Filter再把这个 filter 的输出引脚连接到渲染或存储组件。常见的拓扑是Video Capture Filter (设备源) - Output Pin - Smart Tee Filter (可选用于同时预览和录制) - Preview Pin - 视频渲染窗口 - Capture Pin - AVI Mux - File WriterAMCap 在“Preview”模式下直接用IVideoWindow把视频画到窗口在“Capture”模式下则把流送入 AVI Mux 写文件。整个图由IGraphBuilder的ConnectDirect或RenderPin方法完成连接。源码中与拓扑最相关的是InitCaptureGraphBuilder和FindCaptureDevice这两个函数。FindCaptureDevice会用System.DeviceEnum枚举CLSID_VideoInputDeviceCategory下的所有设备然后一个一个试ICaptureGraphBuilder2::FindInterface直到找到支持采集的 filter。这种做法看起来笨但恰恰能避开某些驱动在枚举阶段就崩溃的问题。2.2 AMCap 中 Filter 连接的关键代码路径打开amcap.cpp核心函数是StartPreview()。它有两条路径如果已有 graph builder就RenderPin到窗口否则重新创建图。关键代码如下// 创建视频采集 filter 并加入过滤图 HRESULT hr CoCreateInstance(CLSID_VideoInputDeviceCategory, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)pSrc); hr pGraph-AddFilter(pSrc, LVideo Source); // 使用 capture graph builder 自动连接预览 hr m_pBuilder-RenderPin(pCapturePin, NULL, pmt);RenderPin是ICaptureGraphBuilder2最“黑魔法”的接口你给它一个输出引脚它会自动寻找合适的 renderer 或 mux并插入必要的中间 filter比如颜色空间转换器。AMCap 在用户选择“Use Preview Pin”时会先获取ICaptureGraphBuilder2::FindInterface找 preview 引脚找不到再 fallback 到 capture pin。这里的参数pmt很重要。如果传入NULL系统用设备默认媒体类型。如果指定一个AM_MEDIA_TYPE则会强制协商常用于锁定分辨率或格式。AMCap 中SetVideoFormat就是先构建一个VIDEOINFOHEADER然后把pmt设置成这个格式再调用Renegotiate。2.3 参数表常见视频格式与媒体类型媒体类型 GUID含义AMCap 中的显示名MEDIASUBTYPE_YUY2YUY2 裸流YUY2MEDIASUBTYPE_UYVYUYVY 裸流UYVYMEDIASUBTYPE_RGB2424 位 RGBRGB24MEDIASUBTYPE_MJPGMotion JPEGMJPGMEDIASUBTYPE_H264 (FourCC)H.264 压缩流H264视驱动而定MEDIASUBTYPE_MPEG2_VIDEOMPEG-2 视频MPEG2部分采集卡在 AMCap 的“Options - Video Capture Filter”设置页里列出的每一项其实对应一组VIDEOINFOHEADER。通过IAMStreamConfig::GetStreamCaps可以获得设备支持的所有格式。源码里的GetMediaType函数会调用GetStreamCaps并填充 AMcap 自己的VIDEOFORMAT数组。这里有个坑同一设备可能用不同帧间隔表达不同帧率比如AvgTimePerFrame333333表示 30fps但驱动可能返回333000这种非标值AMCap 的显示是直接转成整数所以你可能看到 30.03fps。排查格式协商问题时先看这个字段再看biWidth和biHeight。3. 源码结构与 Amcap 类模块拆解3.1 工程文件与编译入口解压 AMCap.rar 后工作区文件一览文件职责amcap.cpp主程序框架CAmcapApp 与消息处理amcap.h主头文件类声明crossbar.cpp管理视频信号路由模拟信号输入切换SampleCGB.cpp具体的 capture graph builder 封装status.cpp状态栏与 OSD 显示Makefile命令行构建配置amcap.rc资源脚本菜单、对话框、图标XP32_RETAIL、XP32_DEBUG对应不同构建配置的中间产物目录stdafx.h是预编译头里面包含了dshow.h、strmif.h等 DirectShow 头文件。amcap.cpp的第一行#include stdafx.h之后会看到用BEGIN_MESSAGE_MAP声明的整个窗口消息路由。注意这是基于 MFC 的虽然是轻量 MFC 程序但编译时必须链接 MFC 库这也是为什么老的 SDK 版本下直接编译常常报错的原因。3.2 核心类 CAmcapApp 与消息循环AMCap 的主窗口不是对话框而是一个自定义的CAmcapWindow源码里直接做成全局CMainWindow。它处理的消息包括WM_CREATE里启动 DirectShow 初始化、WM_CAP_DRIVER_CONNECT之类的自注册消息、以及WM_TIMER做帧统计。CMainWindow::OnCreate中调用了InitCaptureGraphBuilder()这个函数完成以下步骤CoCreateInstance(CLSID_FilterGraph)创建过滤图管理器。CoCreateInstance(CLSID_CaptureGraphBuilder2)创建采集图构建器。m_pBuilder-SetFiltergraph(pGraph)把两者绑定。注册IGraphBuilder的事件通知用于监听图状态变化。这里的m_pGraph和m_pBuilder是整个程序的“大脑”。所有后续操作包括Capture()、StopCapture()、ChangePreviewState()都是围绕这两个 COM 对象做状态机切换。如果熟悉现代推理框架的“图构建”概念其实 DirectShow 的 filter graph 也有点类似先拼接算子filter再做形状对齐pin 媒体类型协商最后启动执行。3.3 Crossbar 模块处理摄像头信号路由crossbar.cpp是很容易被忽略但非常有用的模块。很多 USB 摄像头没有 crossbar但采集卡比如 TV 卡、老式视频采集卡通常有多个输入通道Composite、S-Video、Tuner。AMCap 通过IAMCrossbar接口枚举输入引脚。SetCrossbarRoute的逻辑如下// 从设备获取 IAMCrossbar 接口 IAMCrossbar *pXbar NULL; hr pSrc-QueryInterface(IID_IAMCrossbar, (void**)pXbar); // 枚举所有输入引脚找 Composite 类型 long nInputs 0, nOutputs 0; pXbar-get_NumInputRoutes(nInputs); pXbar-get_NumOutputRoutes(nOutputs); // 设置路由将输入索引 0 连接到输出索引 0 hr pXbar-Route(0, 0);这里Route(0,0)的第一个参数是输入引脚索引第二个是输出引脚索引。每个 pin 有物理类型通过get_InputRoutePhysicalType获取常见值有PhysConn_Video_Composite、PhysConn_Video_SVideo、PhysConn_Video_Tuner。AMCap 默认会找一个物理类型编号最小的输入来接通。如果你用 AMCap 切换采集卡通道没反应多半是Route参数反了或者没有调用SetRoute后的同步接口。3.4 SampleCGB 与状态管理的分工SampleCGB.cpp从名字可以看出是“Sample Capture Graph Builder”它封装了ICaptureGraphBuilder2的常用调用减少主程序里的重复代码。它提供的ConnectCapturePins方法内部会尝试FindInterface并按照 AMCap 预设的优先级连接先找 Preview Pin避免占用 capture pin 导致无法录像。如果找不到 preview pin则用 capture pin 同时做预览和录像此时需要插入Smart Tee或让 renderer 支持多路。status.cpp则负责把帧率和捕获状态显示到窗口底部状态栏。它会订阅IMediaEventEx的通知当 filter 图抛EC_VIDEO_SIZE_CHANGED时自动更新显示分辨率。这部分代码逻辑简单但如果你想在自己的播放器里嵌入“动态分辨率感知”功能可以参考它的消息处理方式。4. 编译与调试从源码到可用 exe4.1 用 Visual Studio 编译 AMCap 的前置条件AMCap 的原始工程基于 VS2002/2003但现代 VS 也能编译只是要做三件事安装 Windows SDK其中必须包含 DirectShow 头文件dshow.h、qedit.h。Win10/11 SDK 仍会带一部分 DirectShow 扩展但qedit.h已不是默认路径需要从旧 SDK 拷贝或者用 mingw 的 dshow 头文件替代。项目属性中启用 MFCUse MFC in a Static Library因为amcap.cpp用到了CWinApp和消息映射。处理strsafe.h的_CRT_SECURE_NO_WARNINGS警告。我的做法是新建一个 Win32 空项目把amcap.cpp、crossbar.cpp、SampleCGB.cpp、status.cpp全部加进去再手动添加 Windows SDK 的包含目录。不直接打开原工程文件因为旧的.dsp/.vcproj升级后会有大量无意义的转换噪声。4.2 编译步骤与常见错误命令行编译命令cl.exe 方式可以这样cl /D WIN32 /D _WINDOWS /D _UNICODE \ /I C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include \ amcap.cpp crossbar.cpp SampleCGB.cpp status.cpp \ /link strmiids.lib ole32.lib oleaut32.lib winmm.lib \ /SUBSYSTEM:WINDOWS /OUT:amcap.exe注意这里使用strmiids.lib它包含了CLSID_VideoInputDeviceCategory、MEDIASUBTYPE_YUY2等 GUID 的定义。如果链接报错unresolved external symbol _CLSID_FilterGraph就是漏了strmiids.lib。最容易遇到的编译错误是amcap.cpp(120) : error C3861: GetTickCount: identifier not found需要包含windows.h且定义_WIN32_WINNT0x0501。error C2872: IUnknown : ambiguous symbol是因为atlbase.h和dshow.h冲突解决方式是先包含dshow.h不提前引用 ATL。qedit.h未找到如果只是编译 AMCap不需要qedit.h但如果你自己扩展时要找ISampleGrabber则需要从旧 Windows SDK 中拷贝该头文件。4.3 运行时验证 Filter 是否挂载编译成功后运行amcap.exe它会弹出一个简单的窗口。如果摄像头支持 DirectShowDevices菜单里会出现设备名。选择设备后窗口立即开始预览。此时可以用 GraphEdit旧 SDK 工具或者 GraphStudioNext 来验证过滤图结构。打开工具后菜单栏“Graph - Connect to Running Graph”然后选择 amcap 进程。你能看到实际的 filter 链比如USB Video Device - Smart Tee - Video Renderer。如果设备输出 MJPG但 AMCap 预览显示为 RGB24说明 filter 图中间自动插入了Color Space Converter Filter。这可以帮助你确认驱动到底输出什么以及是谁在做格式转换。提示如果 AMCap 预览正常但录像文件打不开优先检查 AVI Mux 是否被插入以及你选择的压缩器是否支持当前色彩空间。GraphStudioNext 里能看到 AVI Mux 的输入 pin 是否与 capture pin 协商成功。5. 捕获参数调优与真实场景中的取舍5.1 帧率、分辨率与像素格式的权衡AMCap 的“Video Capture Filter”属性页里可以调整帧率、分辨率、曝光等。但 DirectShow 的格式协商并不是设置完就立刻生效而是通过IAMStreamConfig::SetFormat并且某些设备需要先停止流再重新启动。源码中的SetVideoFormat做了StopPreview-SetFormat-StartPreview的完整动作这就是为什么你在 AMCap 里切换分辨率时画面会黑一下。选择像素格式时我的经验是USB 摄像头优先选MJPG因为同样是 1080p30fpsMJPG的带宽占用只有 YUY2 的 1/3 到 1/2。但MJPG是压缩流如果你要做逐帧图像处理必须先解码。YUY2则直接是 YUV 数据OpenCV 抓取时还要转 BGR。AMCap 源码里没有自带解码器它直接把MEDIASUBTYPE_MJPG交给 Video Renderer由系统内置的MJPEG Decompressor解码。如果你在自己的程序里通过 sample grabber 拿数据需要自己处理 JPEG 解码。帧率设置上注意AvgTimePerFrame的单位是 100ns。想设置 25fps则是400000。在属性页里通常有滑杆但有些驱动只接受固定几个档位比如 5/10/15/30不会给你任意帧率。此时你需要用IAMStreamConfig::GetNumberOfCapabilities遍历所有能力找到最接近你目标的组合。5.2 DirectShow 滤镜调试技巧如果 AMCap 无法预览但硬件在其他软件里正常可以顺着以下路径排查检查设备是否被占用。AMCap 和浏览器/Camera App 同时打开同一个摄像头时后打开的一方会失败。这是 DirectShow 设备的排他访问特性。检查过滤图是否停止在连接阶段。在 GraphStudioNext 中手动构建图把设备的输出直接连到视频渲染器如果连不上说明设备的媒体类型与 renderer 不兼容需要一个中间 filter。查看IAMStreamConfig::GetStreamCaps返回的VIDEO_STREAM_CONFIG_CAPS中VideoStandard字段有些摄像头支持 720p 但不支持 1080i如果GetFormat请求了不支持的格式SetFormat会返回E_INVALIDARG。调试时建议在ConnectDirect调用前后打印hr值。常见错误码HRESULT含义常见原因VFW_E_CANNOT_CONNECT两个 pin 找不到共同媒体类型两端格式不匹配需中间 filterVFW_E_NOT_STOPPEDfilter 正在运行无法更改格式需要先调Stop()VFW_E_TYPE_NOT_ACCEPTED输出 pin 拒绝了输入 pin 提供的媒体类型缓冲格式或时间戳间隔不对E_POINTER参数空指针或 query 接口未实现设备不支持该扩展接口5.3 捕获延迟与缓冲区的控制AMCap 默认预览延迟在 100ms 左右这个延迟主要来自 Video Renderer 的重采样缓冲。如果你想做低延迟预览可以用IVideoWindow::put_Owner直接把视频画到自绘窗口并关闭AutoShow。更激进的做法是绕开 AIO Renderer用ISampleGrabber拿到样本后自己渲染但那样会引入额外的帧同步开销AMCap 源码里没有做这一步。在录像场景中AMCap 通过ICaptureGraphBuilder2::SetOutputFileName创建 AVI Mux然后设置WriteBufSize参数。这个缓冲区大小决定了写入文件的粒度。默认值不适合长时间高分辨率录像写入太频繁会导致磁盘碎片建议在SetOutputFileName后通过IFileSinkFilter::SetFileName配合调大缓冲。不过需要注意缓冲区过大在系统崩溃时丢失的数据也更多。如果你拿 AMCap 源码做二次开发还有一个值得借鉴的细节它在每帧捕获后会用IMediaSample::GetTime取时间戳并通过status.cpp统计帧间隔。这比用GetTickCount在应用层掐表更准确因为后者会丢失 renderer 内部排队导致的抖动。把这些时间戳监控代码搬到自己的采集框架里能快速发现驱动或 USB 控制器层面的传输异常。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门