Delphi 2010老项目集成USB摄像头:DSPack 2.34实战指南

发布时间:2026/10/26 2:21:30
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配合IEnumMoniker,DSPack 把它包成了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必须是设备枚举列表里的值
帧率AvgTimePerFrame400000(25fps)单位 100ns,越小越快
像素格式bmiHeader.biBitCount24抓帧存图用 24 位最省事
缓冲区数SampleGrabber.BufferCount1~3多了占内存,少了易丢帧

我一般先用设备默认参数把链路跑通,确认画面正常,再去调分辨率和帧率。上来就设 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都有对应Free,FreeIt统一设True交给 DSPack 回收,文件 IO 挪到队列里由线程处理。另外TSysDevEnum这类对象用完即Free,别图省事挂在全局。

5. 进阶:把抓帧做成可用的存档流程

链路跑通之后,真正决定这套东西能不能上生产的是存档流程的稳定性。我现在的做法是把OnBuffer里的工作压到最小:只做一次内存拷贝进环形队列,然后立刻FreeIt := True返回,写盘、编码、命名全部交给一个独立的工作线程。队列长度给 8~16 帧,满了就丢最旧的,保证采集端永远不被 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 的视频采集需求,都会先把设备枚举、预览、抓帧三段分开验证,任何一段不通就不往下走,省得问题叠在一起分不清是谁的锅。希望帮到你。

本文还有配套的精品资源,点击获取