Win10+VS2015环境下Qt与DirectShow多路摄像头采集录制实战 📅 发布时间:2026/9/8 8:22:29 👁 浏览次数: 简介基于VS2015、Qt与DirectShow的Windows 10多摄像头采集录像示例演示了如何同时打开多个USB摄像头将多路采集画面实时显示在Qt窗口上并支持视频录制录制时每隔30秒自动生成一个新视频文件可有效降低长时间录制过程中因断电、程序异常或拔插摄像头造成整段录像损坏的风险。整个压缩包共53个文件主要包含C源码和头文件、Qt界面设计文件ui与qrc、VS工程配置文件sln、vcxproj、props、编译日志、中间obj文件以及可执行exe、调试pdb等包体约20.41MB目录结构清楚可以快速找到摄像头管理、界面显示和录像保存等核心模块。已有1588人学习下载适合正在学习DirectShow采集、或者希望在Windows上搭建视频监控原型的开发者。通过学习这份示例工程读者能掌握DirectShow设备枚举与多路打开方式、Qt显示实时视频流的常见写法、按固定时长分段保存文件的实现思路并了解VS2015中Qt工程的编译链接方式稍加修改即可扩展到更多摄像头或融入现有监控系统。 老同学前几天找我说他手上有一个在Win10系统、VS2015环境下用Qt框架配合DirectShow开发的多摄像头采集项目需求是在同一界面中同时打开多个USB摄像头并把画面录制下来遇到了不少问题来找我聊天。我听完他的描述不由得笑了——这不是我在工业视觉项目里反复折腾过的老套路吗。虽然现在市面上流行的已经是Qt6搭配V4L2或者Media Foundation的新玩法但在老设备、老代码、老团队的项目里Win10 VS2015 Qt DirectShow这套组合仍然是很多实际项目的绝对主力配置。这篇博文就把这套方案的完整落地流程写清楚包括环境搭建的坑、采集框架选型逻辑、多路摄像头的线程模型、录制方案对比以及打包发布时的注意点。不绕弯子直接按实操顺序写给正准备接手类似项目的同行一个能直接抄作业的参考。1. 项目需求拆解与技术选型思路1.1 为什么还在用VS2015和DirectShow很多人看到VS2015和DirectShow第一反应是“这玩意儿还能用吗”。我的答案是不仅能用而且用得好好的。在工业自动化、实验室设备、简易监控类的场景中大量上位机程序是2016年到2019年之间写的当时团队的开发环境就是VS2015 Qt 5.x采集方案选了DirectShow。原因很简单DirectShow是Windows系统自带的组件不需要额外安装驱动SDK摄像头支持范围广老旧的USB工业相机、普通UVC摄像头都能兼容。相比Media FoundationDirectShow的资料多、代码范本多、问题排查思路成熟遇到坑抱着老代码还能救一救。如果你接手的是这种老项目最忌讳的是“反正要改不如换新框架”。硬件驱动、采集卡固件、Camera SDK都可能依赖特定的DirectShow Filter强行迁移到其他采集框架往往要把整套底层协议都重写一遍成本完全失控。这套方案的核心思路是“缝缝补补”地让它跑起来而不是推翻重来。1.2 Qt版本与VS编译器的匹配逻辑Qt在Windows下有两种常规编译路线MinGW和MSVC。VS2015项目必须使用MSVC编译的Qt库因为VS环境的运行时代码和ABI接口与MinGW不兼容混用会导致链接阶段一堆莫名其妙的LNK错误。很多新手在这块栽跟头是从Qt官网下载了一个带MinGW的安装包装完后VS2015里却怎么都配置不了甚至提示“Unknown Qt version”。原因是Qt库的编译器类型没对上。正确做法是安装Qt时勾选msvc2015_64或者msvc2015_32取决于工程是x64还是x86然后在VS里进行配置。这里有一个历史经验Qt 5.15.2是支持VS2015的最后一个较好选择再新的Qt版本虽然也能用但很多功能开始默认依赖更高版本的编译器特性。老项目要的是稳定我用的是Qt 5.12.10 msvc2015_64实测整个开发周期没有出现过编译器不兼容的问题。如果你手里有老项目的代码千万要确认版本对应关系。Qt库位数、VS工程平台位数、摄像头SDK位数这三个必须全部一致否则在程序运行到COM组件交互时就会突然崩溃而且崩溃位置很难从调用栈里看出来。1.3 采集框架选型DirectShow、V4L2还是Media Foundation从“多路USB摄像头并发采集”这个需求出发可选框架有多个这里给大家做一个直接对比对比项DirectShowMedia FoundationV4L2系统适用Windows全系列Windows 7及以上Linux设备兼容度高老设备支持好高新硬件支持好高Linux下是标准多路采集并发手动管理Graph灵活自带拓扑模型文件节点方式编码录制库需配套Encoder Filter自带编码能力需配套GStreamer等老项目复用极高需重写采集逻辑完全重写跨平台这个项目需要打开多个USB摄像头并录制视频录像是重头需求DirectShow的优势在于配套的Encoder Filter比如H.264、MJPEG编码器在Windows下非常多而且Filter Graph模型天然支持一张图里挂多个采集设备逻辑上会清晰很多。实际项目里我建议直接用DirectShow完成采集编码录制用FFmpeg完成。DirectShow只管拿到YUV或MJPEG的原始帧FFmpeg负责封装成MP4文件。这样做的好处是把“采集”和“编码”解耦后续如果换系统或者换需求只替换其中一个环节即可。如果非要用DirectShow一路打通到MP4录制会遇到编码器兼容问题不同编码Filter之间匹配性很差折腾起来非常耗时。2. 开发环境搭建与初始化避坑2.1 在Win10下安装VS2015的常见问题Win10系统下安装VS2015的问题确实不少最典型的是安装时卡在“正在准备安装”这一步原因多半是旧版补丁冲突或者Windows组件缺了。我的处理方式比较直接先到系统设置里把“开发人员模式”打开再用管理员身份运行安装程序如果卡住就清理临时目录重启后再装。VS2015安装完成后需要确认C工具集是否正确。打开Visual Studio Installer看是否勾选了“Windows 8.1 SDK”和“Visual C工具集”这两个是编译DirectShow和Qt项目的关键。如果没装后面编译会报大量关于strmbase.h、dshow.h找不到的致命错误。一个重要的坑是Win10会对VS2015的调试器权限做限制尤其是进行DirectShow开发时有时候运行到COM接口调用时突然退出但断点根本打不到。我后面采用了一个土办法调试时把程序目录直接放到C盘根目录下避开UAC和权限问题虽然操作上难看了点但确实能少掉很多莫名其妙的灵动。2.2 Qt插件初始化失败的根治方法老Qt项目在Win10上跑起来最容易见到的错误字眼是“windows no qt platform plugin could be initialized. reinstalling the applicat”这个报错看着吓人实际上就是指不到platforms目录下的qwindows.dll。排查顺序我建议是先确认编译环境运行目录下有没有platforms文件夹里面有没有qwindows.dll。确认platforms文件夹的架构位数跟当前程序一致。不同位数混用会在运行时直接报这个错误。确认所有Qt相关DLL是否放在应用程序exe同目录下重复放置多个版本极容易导致误加载。如果你用windeployqt工具打包这个错误通常不会出现。具体命令为windeployqt.exe yourApp.exe这个命令会自动找出exe依赖的所有Qt模块并把插件、翻译文件、运行库都拷贝到exe所在目录。不过要注意windeployqt并不能帮你把DirectShow相关的自定义Filter也拷贝出来这部分要自己手写部署脚本。2.3 COM组件的初始化时机DirectShow是建立在COM组件之上的。很多新人写了采集代码打开设备时没问题但录制视频一段时间后崩溃根本原因是没有在正确的线程里初始化COM或者多次初始化释放不配对。我们项目里规定每个使用DirectShow的线程在创建Filter Graph之前必须调用一次CoInitializeEx(NULL, COINIT_MULTITHREADED);线程结束前再对应调用CoUninitialize();这一点很容易被忽略因为单线程Demo跑起来没问题一旦开了多路摄像头每个采集线程都有自己的COM模型时缺少初始化就会出现随机性崩溃。3. 多摄像头采集的整体设计与线程模型3.1 音频“一设备一线程”的设计取舍多路USB摄像头并发采集第一反应往往是在一个线程里循环打开、采集、显示。实测下来不推荐原因有两个USB摄像头读取是阻塞式操作一路摄像头卡顿会拖慢其他摄像头的读取节奏。录制视频时编码器处理耗时较长如果全塞一个线程界面会严重掉帧。推荐方案是“一设备一线程”加“帧缓冲队列”。每个摄像头创建独立的采集线程线程内的Filter Graph只负责采集原始帧然后把帧丢进一个大小有限的环形队列中录制线程从队列里取帧编码。这里有个取舍线程数等于摄像头数再加上一个合成/录制线程开4路摄像头就是5个线程对于现代CPU来说压力很小。关键是每路采集线程要绑定到独立设备不要在采集逻辑里做任何视频编码操作采集线程的循环里只做读帧和拷贝。3.2 帧缓冲队列与丢帧策略多路视频录制时如果编码跟不上采集速度缓冲区就会无限增长最终内存爆炸。合理的办法是给每路视频队列设定固定容量比如60帧约2秒一旦队列已满就直接丢弃新帧而不是阻塞采集线程。这个策略我称之为“丢帧保实时”在工业监控和实验记录场景下非常合理。保留最近的数据丢掉过期数据比整条链路卡死要安全得多。代码层面用Qt的QQueue加QMutex即可实现不需要引入额外依赖。但要注意丢帧策略不能盲目用到“录制模式”。如果项目要求完整无损录制每一帧就需要把采集帧率和编码能力做匹配比如摄像头采集设定在25fps编码采用高质量预设保证每秒能处理30帧以上留出富余量不能压着极限跑。3.3 界面刷新与采集线程的分离Qt界面上的视频显示必须位于主线程GUI线程而采集发生在子线程这种场景需要跨线程更新画面。最稳妥的方式是用Qt信号槽机制采集线程在拿到新帧后发射一个携带QImage指针的信号主线程槽函数负责更新界面。信号槽默认是AutoConnection跨线程时自动变成QueuedConnection优化效率足够。这里有个性能细节不要在信号里传递大体积的QImage值要用指针或者共享指针否则会反复拷贝图像数据多路视频打开后内存和CPU占用率会显著上升。实测参数供参考4路720P摄像头每帧约2MB原始数据如果用值传递信号CPU占用率达到25%以上改为指针传递后CPU占用率稳定在8%-10%左右。这个性能差相当可观。4. DirectShow采集核心实现细节4.1 枚举系统中的多个USB摄像头DirectShow操作的第一步是枚举系统中所有视频输入设备。一般人不注意的是枚举结果里包含了很多没有被物理连接但驱动存在的虚拟设备所以枚举后需要做过滤。核心API思路是使用ICreateDevEnum和IEnumMoniker遍历设备集合。代码如下这段代码可以拿到所有视频输入设备的友好名称ICreateDevEnum* pCreateDevEnum NULL; IEnumMoniker* pEnumMoniker NULL; CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)pCreateDevEnum); pCreateDevEnum-CreateClassEnumerator(CLSID_VideoInputDeviceCategory, pEnumMoniker, 0); IMoniker* pMoniker NULL; while (pEnumMoniker-Next(1, pMoniker, NULL) S_OK) { IPropertyBag* pPropBag NULL; pMoniker-BindToStorage(0, 0, IID_IPropertyBag, (void**)pPropBag); VARIANT varName; VariantInit(varName); pPropBag-Read(LFriendlyName, varName, 0); // 这里是设备的显示名称例如 USB Camera // 保存pMoniker以用于后续创建Filter VariantClear(varName); pPropBag-Release(); pMoniker-Release(); }这段代码的完整度不足以直接用到生产环境但核心枚举逻辑就这样。拿到pMoniker后调用pMoniker-BindToObject可以创建出对应的DirectShow Filter再添加到Filter Graph中。经验提醒多路摄像头的“设备号”一定要用pMoniker的持久标识符而不是简单的索引0、1、2。USB设备在系统唤醒后顺序可能发生改变如果按索引写死很容易出现画面错乱A摄像头显示成B的内容。4.2 建立Filter Graph并绑定设备到渲染器每路摄像头独立建立一个Filter GraphGraph内部至少包含三个组件采集Filter来自设备枚举结果Sample Grabber抓取帧数据Null Renderer或Smart Tee让数据流通起来Sample Grabber是DirectShow里最常用的“偷帧”组件它可以在数据流经时回调到我们的代码里。配置Sample Grabber需要设置媒体类型我用的是RGB24格式这样可以省略掉格式转换回调里直接得到QImage可用的像素数据。建立Graph的典型调用顺序CoCreateInstance(CLSID_FilterGraph)通过QueryInterface拿到IGraphBuilder绑定摄像头Filter并AddFilter到Graph创建Sample Grabber Filter并AddFilter连接采集Filter的Output Pin到Sample Grabber的Input Pin连接Sample Grabber的Output Pin到Null Renderer调用IMediaControl的Run方法启动采集这里有一个最常见的坑很多摄像头默认输出格式是MJPEG或NV12Sample Grabber需要能处理压缩格式或者做转换。我在代码里选择让Sample Grabber主动请求RGB24DirectShow会尝试在内部添加Color Converter Filter。如果连接失败多半是摄像头的输出格式列表里直接不支持RGB24这种时候Safe要让Sample Grabber接收原始格式然后在回调里用算法转换。4.3 视频回调中如何提高处理效率Sample Grabber回调函数里时间非常宝贵。很多Demo代码直接把图像写文件、做图像识别、甚至加滤镜导致回调阻塞然后采集线程就开始丢帧录出来的视频卡顿。正确做法是回调函数里只做两件事从回调缓冲拷贝图像到自建缓冲。发射信号通知其他线程来处理。回调里的缓冲要提前申请好不要在回调里new内存或者扩大容器这种动态分配在做监控视频流处理上是禁止的。如果你希望录制视频在回调里拷贝完图像后不要做任何阻塞操作把帧丢进那路摄像头对应的帧队列录制线程自行从队列取数据这样视频流会非常稳定。5. 多路USB摄像头视频录制实现5.1 录制方案对比DirectShow Encoder vs FFmpeg录制视频这步我自己经历过多轮方案尝试。最早用DirectShow的Windows Media Encoder Filter做出来的AVI文件能用但不够通用而且编码器版本一换文件就打不开。后来改用FFmpeg做录制稳定性和兼容性立刻上了一个数量级。从工程角度说FFmpeg对多重格式支持完善MP4封装、H.264编码器都是标配。在Windows下可以用avformat_open_outputavcodec系列接口Qt项目中常见的使用方式是直接集成FFmpeg库。推荐集成方式在VS2015工程里链接FFmpeg的lib头文件用FFmpeg官网版本。注意一点老版本VS2015对FFmpeg头文件的C语言特性支持很好基本无冲突。准备音视频数据时OpenCV里做出来的C封装类有很多直接复用即可不需要自己从零封装。5.2 多路录制文件的封装与命名多路摄像头录制视频除了技术实现还要考虑文件管理。一盘散沙很容易录成一大堆乱七八糟的文件后期寻找对应录像要疯掉。我在项目里是这样设计的每路摄像头单独生成一个文件。文件名格式为“摄像头编号_日期_时间.avi”例如CAM01_20240918_153000.avi。启动录制时在主界面显示当前每一路的录制状态。录像存盘时按天创建目录每天一个文件夹。这种管理方式看起来基础实际维护时非常省心。因为项目不止录一路如果是4路摄像头同时录制统一格式命名可以很快定位到某一台设备的某一段时间。5.3 启停录制的原子性与文件完整性视频录制的启动和停止逻辑上要保证“原子性”。意思是启动时所有设备必须在同一时间开始写入停止时必须在同一时间完成文件写入。否则会出现某一路文件只有几秒空白另一路却录了十几分钟。实现方法是设置一个控制位由主线程统一下发“开始/停止”指令。采集线程读取该标志位后自己控制对应行为。要特别防止停止录制时还有帧文章正在队列里等待编码正确顺序是先停采集再清空队列最后关闭编码器并写入文件尾部信息。如果中途程序崩溃未正常关闭的AVI文件会损坏。为了降低损失可以在录制时周期性写索引信息。FFmpeg的AVFMT_FLAG_AUTO_BSF也可以尽量避免这种问题但最关键的还是代码逻辑上保证录制结束后能正常完成文件收尾。6. 常见问题与排查技巧实录6.1 录制出来的视频播放花屏或绿屏这个问题多数是格式转换的错误。DirectShow采集回调里拿到的Buffer格式跟预期不一致比如代码里按RGB24处理实际上来的是YUV420P数据显示出来自然花屏。解决思路是调试时先打印媒体的AM_MEDIA_TYPE信息确认实际格式再做转换。如果是YUV系列可以用libyuv转换这个库在Windows下的表现非常稳定支持各种YUV与RGB互转还做了CPU优化性能比手写循环高很多。6.2 摄像头打开失败或打开后画面黑屏打开失败有以下常见原因摄像头被其他程序占用比如Windows相机应用没关。摄像头连接USB 3.0口但带宽不足。设备枚举后没有正确Release导致句柄泄漏。USB摄像头同时工作会抢占带宽4路1080P很难全部跑起来。实测中我把分辨率降到720P帧率设到15fps4路完全稳定。如果你需要高分辨率最好用支持UVC的工业相机手动指定带宽预留不能指望普通USB摄像头能支撑太高负荷。黑屏问题多半是Graph运行没有进入Running状态。检查IMediaControl的Run返回值同时查看DSHOW的日志输出。常见原因是某一路的Filter Graph里没有正确连接Sample Grabber导致数据流被堵死。6.3 Windows下Qt程序打包发布时Qt库缺失老项目的坑往往还集中在打包上。开发环境能跑换台电脑就报“windows no qt platform plugin could be initialized”。这个问题的核心是缺少platforms插件或者是插件与程序位数不一致。打包时建议用windeployqt工具自动化处理Qt运行库手动复制DLL不仅容易漏还会多出很多冗余文件。再用依赖扫描工具检查一遍把缺失的VC运行库也一并拷贝进去。这样到客户电脑上直接解压运行即可不需要安装Visual Studio环境。如果你用的是MSVC编译的Qt记得把vc_redist.x64.exe一起打进安装包否则目标机器没有VC运行库程序会在启动阶段直接崩溃。6.4 多路状态监控下的性能优化实际运行监控程序时CPU和内存都是有限的要留一部分给系统的其他业务。我自己常做的优化有只在界面上显示正在处理的帧不显示时立即释放。降低界面刷新频率比如每秒刷新15帧人眼基本无感知。用定时器定期清理录像文件防止磁盘空间被录满。给采集线程设置Sleep(10ms)降低空转损耗。别小看这些优化4路摄像头同时录制时如果每路都按60fps去刷新和编码再好的机器也会卡顿回落到15fps后一切都会变得流畅许多。7. 实操经验小结回到开头老同学的困惑我用这套方案帮他完成了4路USB摄像头的采集与录制系统连续运行了72小时未出现一次死机或重启。整个项目大概花了两周时间绝大部分时间消耗在摄像头兼容性和格式转换上核心架构反而是一次性搭完就没有再动过。最后再分享一个项目习惯在开发过程中把每路摄像头都固定一个专门的采集测试程序单独测试通过后再集成到总程序里。多路并发的坑往往是“组合式”出现的先把单路的边界条件都摸清楚再组合起来排查会高效得多。写这类系统最怕的就是所有问题混在一起最后一锅粥谁也说不清分开测试、逐个击破才是正路。本文还有配套的精品资源点击获取