Delphi 2010老项目集成USB摄像头:DSPack 2.34实战指南 简介这份资源是面向 Delphi 2010 开发者的 DSPack 2.34 组件包已在实际环境中测试通过主要解决该版本 IDE 下网络通信、多线程、数据库访问与串口操作等组件的兼容性问题适合需要快速搭建高性能 Windows 应用的中级开发者使用。压缩包为 rar 格式共 319 个文件约 1.75MB其中 95 个 pas 单元与 40 个 dpr 工程文件构成核心源码与示例39 个 dfm 窗体、11 个 res 资源及 9 个 dcu 编译单元配合 6 个 dpk 包文件另有 bat 注册脚本与 dproj 工程配置便于按组件包方式安装导入。资源已有 329 人学习下载说明在 Delphi 社区中具备一定参考价值。读者可获得经测试的完整组件源码、可直接编译的示例工程与注册脚本省去自行适配 Unicode 的调试成本并借助示例窗体理解 TCP、UDP、串口等组件的属性设置与事件处理方式为服务器程序、实时监控或网络聊天类项目提供可复用的基础。1. 老 Delphi 项目要接摄像头为什么我翻出了 DSPack 2.34手上有个维护了十来年的 Delphi 2010 桌面系统客户突然要求接入 USB 摄像头做本地预览和抓帧存档。第一反应是找现成的视频采集组件结果发现新版组件基本都奔着 FMX 和高版本 Delphi 去了VCL 老工程要么编译不过要么运行时直接抛异常。折腾一圈之后我把目光放回了 DSPack——一个基于 DirectShow 的 VCL 封装库2.34 这个版本恰好能在 Delphi 2010 下编译通过、组件能正常注册到面板上。这篇笔记记录的就是我拿这份已测试的 DSPack 2.34 在 Delphi 2010 里跑通摄像头预览、抓帧、设备枚举的完整过程包括组件安装、Filter Graph 搭建、参数怎么调以及几个让我返工两次的坑。适合还在维护 Delphi 2010 及相近版本 VCL 工程、需要本地视频采集能力的从业者参考。DSPack 的本质是把 DirectShow 那套 COM 接口Filter Graph Manager、Capture Graph Builder、各种 Filter包成了 Delphi 里能直接拖放的组件和类。你不用自己写CoCreateInstance、不用手撸IBaseFilter的接口查询FilterGraph、VideoWindow、SampleGrabber这些组件摆上去属性设一设事件接一接摄像头画面就能出来。代价是它对版本和平台比较挑Delphi 2010 属于它还能好好工作的区间再往上到 XE 系列就开始出现字符集和包结构上的摩擦。所以这份 2.34 的价值不在于“新”而在于“在这个特定版本上验证过能用”对老工程来说这比什么都实在。2. 装进 Delphi 2010包编译与组件注册的完整路径2.1 先搞清楚包里有什么DSPack 2.34 的源码包结构大致分三层底层是DirectShow9.pas这类接口声明单元中间是DSPack.pas里的组件与类实现最上面是若干.dpk设计期包。安装的核心动作就是把设计期包编译出来、注册进 IDE让组件面板出现 DSPack 页签。动手前先确认两件事Delphi 2010 的安装目录里Lib和Include路径正常以及系统里 DirectShow 的运行库齐全Windows 自带一般不用额外装。我一般会先把整个源码目录复制到一个不含中文和空格的路径下比如D:\dev\dspack234路径带空格是后面编译报错的高发原因提前规避掉。打开 IDE 之前建议先用文本编辑器扫一眼.dpk文件里的requires段确认它引用的rtl、vcl、designide这些包名和你的版本对得上。Delphi 2010 的包命名和更早版本有差异如果requires里写的是老名字编译会直接卡在“找不到某某包”。这一步花两分钟能省掉后面半小时的猜谜。2.2 编译并注册设计期包在 IDE 里通过File → Open打开设计期包工程通常是DSPack_D2010.dpk或名字相近的那个然后走Project → Build。编译通过后右键工程选Install组件就会注册到面板。如果 Build 阶段报错先看错误行落在哪个单元常见的是字符集相关的PChar与PWideChar不匹配这类问题在 2.34 里大多已经被条件编译指令处理过剩下的多半是路径或包引用问题。// 手动注册组件时的典型写法一般用 IDE 的 Install 即可此处仅作排查参考 procedure Register; begin RegisterComponents(DSPack, [ TFilterGraph, TVideoWindow, TSampleGrabber, TSysDevEnum, TFilter, TBaseFilter ]); end;上面这段是设计期注册的逻辑RegisterComponents的第一个参数是面板页签名第二个参数是组件类数组。正常情况下 IDE 的 Install 会自动调用它你不需要手写。之所以贴出来是因为有时候 Install 成功但面板没刷新或者你想把组件挂到自定义页签下就需要知道注册入口在哪。参数上没什么可调的页签名改成你习惯的名字即可但别和已有页签重名否则会出现组件“消失”的假象。2.3 运行时依赖与工程搜索路径组件装好只是设计期的事真正编译你的业务工程时还需要把 DSPack 的源码目录加进Project → Options → Delphi Compiler → Search path。我一般加两条源码根目录和Src子目录如果你的包是这么分的。漏加的表现是编译时报File not found: DSPack.dcu这时候别急着怀疑包没装好先查搜索路径。提示设计期包安装成功不代表运行时单元可见两者是分开的。装完包后新建一个空 VCL 工程拖一个TFilterGraph上去编译一次能过就说明路径没问题。还有一个容易被忽略的点Delphi 2010 默认输出路径和单元输出路径可能被设到了$(BDSCOMMONDIR)之类的公共目录导致你明明编译了包业务工程却链接到旧版本。我的习惯是把包的Dcu output directory显式指到源码目录下的dcu文件夹业务工程的搜索路径也指向那里版本关系一目了然。3. 搭一条能出画面的 Filter Graph枚举、预览、抓帧3.1 用 TSysDevEnum 枚举摄像头设备DirectShow 里枚举采集设备的标准做法是ICreateDevEnum配合IEnumMonikerDSPack 把它包成了TSysDevEnum。用之前先设Category属性摄像头对应CLSID_VideoInputDeviceCategory。枚举出来的每一项有FriendlyName这就是你在系统设备管理器里看到的名字拿它做下拉框最直观。var DevEnum: TSysDevEnum; I: Integer; begin DevEnum : TSysDevEnum.Create(CLSID_VideoInputDeviceCategory); try // 刷新一次避免热插拔后列表过期 DevEnum.Refresh; for I : 0 to DevEnum.CountFilters - 1 do cbDevice.Items.Add(DevEnum.Filters[I].FriendlyName); finally DevEnum.Free; end; end;Create传入 Category 的 CLSID 决定枚举哪一类设备音频采集换成CLSID_AudioInputDeviceCategory即可。Refresh这一步别省USB 摄像头拔插之后不刷新列表里会残留已经失效的项用户选中后建图直接失败。CountFilters和Filters[I]是配套的索引从 0 开始。这段代码放在窗体OnCreate或者一个“刷新设备”按钮里都行注意DevEnum用完必须Free它内部持有 COM 对象泄漏了不会立刻报错但长时间运行会积累。3.2 预览链路FilterGraph VideoWindow预览的最小链路是“采集源 Filter → 预览渲染器”DSPack 的TFilterGraph提供了Active属性来启停整条图TVideoWindow负责把画面贴到某个窗口句柄上。典型写法是先FilterGraph.ClearGraph再FilterGraph.Active : True然后把VideoWindow.FilterGraph指过去、设VideoWindow.WindowHandle。顺序错了会出现黑屏但无异常的情况这是新手最容易卡住的地方。procedure TFormMain.StartPreview; begin if FilterGraph.Active then FilterGraph.Active : False; FilterGraph.ClearGraph; // 指定采集设备名字来自上一步的枚举结果 FilterGraph.InputFilterName : cbDevice.Text; FilterGraph.InputFilterType : ftVideoInput; VideoWindow.FilterGraph : FilterGraph; VideoWindow.WindowHandle : pnlVideo.Handle; // 用一个 TPanel 承载画面 VideoWindow.Align : alClient; FilterGraph.Active : True; end;InputFilterName必须和枚举出来的FriendlyName完全一致差一个字符都会导致建图失败所以实际项目里我一般把枚举结果的索引或 moniker 存下来而不是靠字符串匹配。InputFilterType设成ftVideoInput告诉图管理器这是视频采集源。WindowHandle指向的容器最好是个TPanel或TPaintBox别直接指窗体句柄否则画面会盖住其他控件。Active : True是真正触发建图和启动的开关它内部会走RenderStream之类的流程失败时不一定抛异常所以后面要配状态检查。3.3 抓帧SampleGrabber 的 Buffer 回调预览只是看业务要的是把某一帧存下来。DSPack 里抓帧靠TSampleGrabber它插在采集源和渲染器之间每来一帧就触发OnBuffer事件把原始像素数据交给你。关键参数是FilterGraph关联和MediaType设置后者决定你拿到的是 RGB 还是 YUV。业务侧要存 BMP/JPG就设成 RGB24省得自己转。procedure TFormMain.SampleGrabberBuffer(Sender: TObject; const Buffer: Pointer; BufferLen: Integer; var FreeIt: Boolean); var Bmp: TBitmap; begin if not GrabRequested then Exit; // 只在需要抓帧时处理避免每帧都拷贝 Bmp : TBitmap.Create; try Bmp.PixelFormat : pf24bit; Bmp.Width : SampleGrabber.OutputSize.Width; Bmp.Height : SampleGrabber.OutputSize.Height; // Buffer 是自顶向下的 RGB 数据按行拷贝 CopyMemory(Bmp.ScanLine[0], Buffer, BufferLen); Bmp.SaveToFile(D:\capture\frame_ FormatDateTime(hhnnsszzz, Now) .bmp); finally Bmp.Free; end; GrabRequested : False; FreeIt : True; // 交还缓冲区所有权 end;Buffer指向的是当前帧的像素数据BufferLen是字节数RGB24 下等于宽 × 高 × 3。FreeIt这个参数很关键设成True表示由 DSPack 回收这块内存设False则要你自己释放搞反了不是泄漏就是访问越界。OutputSize要在图启动之后读才有值启动前读是 0。GrabRequested是我加的一个标志位因为OnBuffer是每帧都触发的不设开关的话会疯狂写盘几秒钟就把磁盘塞满。实际项目里更稳的做法是把 Buffer 拷到一个队列由工作线程慢慢存避免在回调里做文件 IO 拖慢采集。3.4 参数怎么调分辨率、帧率与 MediaType分辨率不是随便设的得看摄像头支持哪些。DSPack 里可以通过TSysDevEnum拿到设备的IAMStreamConfig接口枚举它支持的VIDEOINFOHEADER列表再挑一个匹配的设进去。帧率同理藏在AvgTimePerFrame里单位是 100 纳秒25fps 就是 400000。设了设备不支持的分辨率建图会失败或者自动回退到默认值表现是画面比例不对。参数位置常见取值说明分辨率VIDEOINFOHEADER.bmiHeader640×480、1280×720必须是设备枚举列表里的值帧率AvgTimePerFrame40000025fps单位 100ns越小越快像素格式bmiHeader.biBitCount24抓帧存图用 24 位最省事缓冲区数SampleGrabber.BufferCount13多了占内存少了易丢帧我一般先用设备默认参数把链路跑通确认画面正常再去调分辨率和帧率。上来就设 1080p 很容易因为设备不支持而卡在建图阶段排查起来反而绕远路。缓冲区数量在抓帧场景下建议给 2单缓冲在写盘稍慢时会丢帧。4. 避坑与排查五个让我返工的具体问题4.1 组件装上了但业务工程编译报找不到单元现象是设计期面板有 DSPack 页签拖组件也正常但一编译业务工程就报File not found DSPack.dcu。原因是设计期包安装只影响 IDE业务工程的搜索路径是独立的。解决是把源码目录和 dcu 输出目录都加进Search path并且确认 dcu 输出目录和搜索路径指向同一个地方。我吃过一次亏包编译到了公共目录搜索路径却指向源码目录结果链接到的是更早的一份旧 dcu行为诡异了好一阵。4.2 预览黑屏但没有任何异常现象是Active : True执行完不报错TVideoWindow区域却一片黑。原因通常是WindowHandle赋值时机不对或者VideoWindow.FilterGraph没在Active之前关联好。解决顺序固定为先ClearGraph再设InputFilterName和InputFilterType然后关联VideoWindow并设句柄最后才Active : True。另外确认承载画面的TPanel已经创建完毕在窗体OnCreate里太早设句柄也可能拿到无效值。4.3 抓帧存出来的图是花屏或上下颠倒现象是保存的 BMP 打开后颜色错乱或者图像倒置。原因是像素格式没对齐或者 Buffer 的行序和TBitmap期望的不一致。RGB24 下BufferLen应等于宽 × 高 × 3如果设备实际输出的是 YUY2 而你按 RGB 解析必然花屏。解决是显式把SampleGrabber的MediaType子类型设成MEDIASUBTYPE_RGB24让 DirectShow 帮你做转换倒置问题则检查bmiHeader.biHeight的正负负值表示自顶向下拷贝时按行处理即可。4.4 拔插摄像头后程序崩溃现象是运行中拔掉 USB 摄像头程序抛访问违例或者直接退出。原因是设备失效后 Filter Graph 还持有已释放的接口后续操作踩空。解决是在窗体关闭或切换设备前先把FilterGraph.Active : False再ClearGraph最后释放SampleGrabber和VideoWindow的关联。枚举列表也要在每次打开设备前Refresh别用缓存的旧项去建图。这个坑在客户现场演示时最容易翻车因为用户真的会随手拔线。4.5 长时间运行内存持续上涨现象是预览开着几小时任务管理器里内存一路涨。原因多半是OnBuffer里创建的对象没释放或者FreeIt设成了False却没手动释放 Buffer。解决是回调里所有Create都有对应FreeFreeIt统一设True交给 DSPack 回收文件 IO 挪到队列里由线程处理。另外TSysDevEnum这类对象用完即Free别图省事挂在全局。5. 进阶把抓帧做成可用的存档流程链路跑通之后真正决定这套东西能不能上生产的是存档流程的稳定性。我现在的做法是把OnBuffer里的工作压到最小只做一次内存拷贝进环形队列然后立刻FreeIt : True返回写盘、编码、命名全部交给一个独立的工作线程。队列长度给 816 帧满了就丢最旧的保证采集端永远不被 IO 阻塞。这样即使磁盘偶尔卡顿预览也不会跟着顿。验证方面我一般跑三个检查一是连续预览 30 分钟看内存曲线是否平稳二是抓帧 200 张后抽查文件头确认宽高和像素格式符合预期三是拔插设备三次确认每次重新枚举后都能正常建图。这三个检查覆盖了内存、数据正确性和设备热插拔基本能挡住大部分现场问题。// 工作线程里消费队列的骨架 procedure TSaveThread.Execute; var Frame: PFrameData; begin while not Terminated do begin if Queue.Pop(Frame) then begin try SaveFrameToDisk(Frame); // 真正的写盘在这里不占采集回调 finally FreeMem(Frame); end; end else Sleep(5); end; end;Pop是非阻塞的队列空就睡 5 毫秒再试避免空转吃满一个核。SaveFrameToDisk里做格式转换和文件命名失败要记日志而不是抛异常否则线程一挂整个存档就停了。队列本身要加锁或者用无锁结构OnBuffer在采集线程里跑和工作线程不是同一个上下文这点不注意就是随机崩溃的来源。从那以后我每次接这类老版本 Delphi 的视频采集需求都会先把设备枚举、预览、抓帧三段分开验证任何一段不通就不往下走省得问题叠在一起分不清是谁的锅。希望帮到你。本文还有配套的精品资源点击获取