简介:这份资源是适配 Delphi 2010 的 DSPack 2.34 组件包,面向使用 VCL 框架开发 Windows 桌面程序的 Delphi 开发者,尤其是需要处理网络通信、多线程、数据库访问与串口通信的中级开发者。包内共 319 个文件,以 95 个 pas 单元、40 个 dpr 工程、39 个 dfm 窗体、11 个 res 资源及 9 个 dcu 编译单元为主,另含 dpk 包工程、dproj 项目文件与批处理脚本,压缩包约 1.75MB,结构完整,便于直接导入 IDE 组件面板。资源标注已测试,可降低与 Delphi 2010 Unicode 特性的兼容风险。已有 329 人学习下载,说明其在社区中有一定验证基础。读者可从中获得可直接编译的组件源码与示例工程,用于搭建服务器应用、实时监控、网络聊天室等场景,并借助示例窗体与事件处理代码快速理解组件用法,减少自行摸索与排错的时间。
1. 老 Delphi 项目要接摄像头,为什么我翻出了 DSPack 2.34
手上有个维护了十来年的 Delphi 桌面系统,客户突然要求加一路 USB 摄像头预览和抓帧。听起来是个小需求,真动手才发现麻烦:Delphi 2010 是 2009 年前后的产物,现在主流的媒体框架要么只给 C++ 接口,要么依赖一堆新版本运行库,往这个老工程里塞,编译期就开始翻车。更现实的问题是,客户机器上装的是 32 位 Windows,DirectShow 反而是最稳的那条路——系统自带,不用额外部署解码器,延迟也低。
DSPack 就是 Pascal 世界对 DirectShow 的一层封装。2.34 这个版本之所以被反复提起,是因为它是少数还能在 Delphi 2010 下顺利编译、且组件面板能正常注册的版本。这份资源解决的不是"教你写多媒体程序",而是"让一个卡在老工具链上的项目,用最小改动接上摄像头采集、预览、抓图这条链路"。适合谁:手里有 Delphi 7 到 2010 区间工程、需要做视频采集或文件播放、又不想重写整个技术栈的从业者。如果你是新项目,直接上更现代的方案,别在这里绕。
2. 装之前先搞懂:DSPack 到底封装了 DirectShow 的哪几层
2.1 Filter Graph 模型与 DSPack 的对应关系
DirectShow 的核心是 Filter Graph:源过滤器(Source Filter)产出数据,中间过滤器做解码或变换,渲染过滤器(Renderer)负责显示或写文件,三者用 Pin 连起来,由 Filter Graph Manager 统一调度。DSPack 把这套东西映射成了 Pascal 的类:TFilterGraph管图的创建、运行、停止,TFilter对应单个过滤器,TPin对应连接点,TMediaSample对应一帧数据。
理解这层映射很关键,因为后面所有排错都绕不开它。比如你调FilterGraph1.Active := True失败,本质是 Graph Manager 的Run返回了错误码,DSPack 只是把它翻译成了一个异常。再比如抓帧拿不到数据,问题往往出在 SampleGrabber 这个中间过滤器没被正确插进链路,而不是 DSPack 本身有 bug。
常见做法是先用 GraphEdit 这类工具把链路搭出来、跑通,再照着在代码里复现。GraphEdit 里能看到每个 Filter 的 CLSID 和 Pin 的媒体类型,这些信息在 DSPack 里都能对上。我一般会先在 GraphEdit 里确认"摄像头 → SampleGrabber → 视频渲染器"这条链路能出画面,再动手写 Pascal,能省掉一大半瞎猜的时间。
2.2 为什么是 2.34,而不是更新的版本
DSPack 后续版本对编译器和运行库的要求在往上走,Delphi 2010 的 RTL 和字符集处理(它已经是 Unicode 版本了)跟早期版本有差异。2.34 的源码里,字符串处理和接口声明基本能在这个编译器下过,组件包也能正常 Install 进 IDE。更新的版本不是不能用,而是你可能要改一堆PChar和AnsiString的兼容问题,改到最后发现还不如退回 2.34。
这里有个选型判断:如果你的工程是 Delphi 7 时代的 ANSI 代码,迁到 2010 本身就要处理 Unicode 转换,DSPack 2.34 在这方面的坑相对少;如果你已经在用更新的 Delphi 版本,那 2.34 未必是最优解,得看具体编译报错再定。别迷信"某个版本万能",版本匹配是试出来的。
2.3 安装与组件注册的实操步骤
拿到源码包后,先别急着打开工程。目录里通常有运行期包(Runtime Package)和设计期包(Design Package)两部分,运行期包是程序运行时依赖的,设计期包负责把组件注册到 IDE 面板。顺序错了,IDE 会报"找不到单元"。
# 假设源码解压在 D:\libs\dspack234 # 第一步:先编译运行期包,产物是 .bpl # 在 Delphi 2010 里打开 D:\libs\dspack234\Packages\DSPack_D2010.bpl 对应的 .dpk # 注意:不同分发包的 dpk 命名可能不同,按实际文件名来打开对应的.dpk工程后,先 Build 运行期包,确认输出目录里生成了.bpl和.dcp。然后才是设计期包,Install 之后组件面板会出现 DSPack 页签。如果 Install 报错,八成是运行期包的搜索路径没配好。
# 在 IDE 的 Tools > Options > Environment Options > Delphi Options > Library 里 # 把源码的以下目录加进 Library Path: # D:\libs\dspack234\Src\DirectX9 # D:\libs\dspack234\Src\DSPack # 注意路径按你实际解压位置改,别照抄参数说明:Library Path 是编译期找单元用的,跟运行期无关;如果程序发布到客户机,还需要把.bpl一起带上,或者干脆用静态链接方式把单元编进 exe,省得部署时漏文件。这一步的坑在于,很多人只加了设计期路径,编译自己工程时又报找不到DSPack.pas,回头查半天。
提示:安装前先把 IDE 关掉重开一次,确保没有残留的包加载状态,否则 Install 可能静默失败。
3. 从零搭一条采集链路:预览、抓帧、存文件
3.1 枚举摄像头并绑定到 FilterGraph
第一步是把系统里的视频输入设备列出来。DSPack 提供了TSysDevEnum来做这件事,它封装的是系统设备枚举器,能按类别(视频输入、音频输入等)过滤。
uses DSPack, DirectShow9; var DevEnum: TSysDevEnum; I: Integer; begin DevEnum := TSysDevEnum.Create(CLSID_VideoInputDeviceCategory); try for I := 0 to DevEnum.Count - 1 do // GetMoniker 拿到设备标识,GetFriendlyName 拿到显示名 Memo1.Lines.Add(DevEnum.GetFriendlyName(I)); finally DevEnum.Free; end; end;逻辑说明:CLSID_VideoInputDeviceCategory是设备类别常量,换成音频类别就能枚举麦克风。Count是当前接入的设备数,注意它是在创建枚举器那一刻的快照,热插拔后要重新创建才能刷新。参数上,GetMoniker返回的接口后面要传给TFilter的BaseFilter属性,用来把具体设备绑进图里。
绑定设备时,常见写法是给TFilter设置Moniker或者直接用FilterGraph1.RenderFile走文件播放。采集场景下,我一般显式创建源过滤器再连 SampleGrabber,链路可控,出问题好定位。
3.2 插入 SampleGrabber 抓帧的正确姿势
抓帧是这类需求里最容易翻车的点。SampleGrabber 必须插在源和渲染器之间,而且要设置回调接口,否则它只是个透明通道,拿不到数据。
var Grabber: TSampleGrabber; GrabberFilter: TFilter; begin Grabber := TSampleGrabber.Create(nil); GrabberFilter := TFilter.Create(nil); try // 创建 SampleGrabber 过滤器并挂到图上 GrabberFilter.BaseFilter := Grabber as IBaseFilter; FilterGraph1.GraphBuilder.AddFilter(GrabberFilter.BaseFilter, 'Grabber'); // 设置回调,OnBuffer 里处理每一帧 Grabber.OnBuffer := GrabberBuffer; // 再把源和渲染器接上,顺序:源 -> Grabber -> 渲染器 finally // 释放顺序要和创建相反 end; end;逻辑说明:OnBuffer是 DSPack 封装的回调,每来一帧触发一次,参数里带TMediaSample,可以取到缓冲区指针和大小。参数上要注意,回调是在 DirectShow 的流线程里执行的,不能在里面直接操作 VCL 控件,得用Synchronize或者投递消息回主线程,否则界面会卡死甚至崩。这是血泪经验,第一次写的人几乎都会踩。
另一个细节:SampleGrabber 的媒体类型要跟源协商一致。如果摄像头输出的是 YUY2 而你按 RGB24 去解析,画面会花。稳妥做法是在连接前查询AM_MEDIA_TYPE,按实际格式处理,或者插入一个颜色空间转换过滤器。
3.3 预览与抓帧存盘的完整流程
把链路串起来后,预览就是让渲染器工作,抓帧就是在回调里把当前帧拷出来。存盘时注意图像格式,BMP 最省事,JPEG 要额外编码。
procedure TForm1.GrabberBuffer(Sender: TObject; Sample: TMediaSample); var Bmp: TBitmap; begin if Sample = nil then Exit; Bmp := TBitmap.Create; try // 把样本转成位图,具体转换依赖媒体类型 SampleToBitmap(Sample, Bmp); // 存盘或显示,注意线程安全 Bmp.SaveToFile('D:\capture\frame_' + FormatDateTime('hhnnsszzz', Now) + '.bmp'); finally Bmp.Free; end; end;逻辑说明:文件名用毫秒时间戳,避免同一秒内多帧覆盖。参数上,SampleToBitmap是示意,实际要按AM_MEDIA_TYPE的subtype分支处理,RGB 和 YUV 的转换逻辑完全不同。存盘频率高时,磁盘 IO 会成为瓶颈,常见做法是回调里只做内存拷贝,另起一个线程写文件,别在流线程里做重活。
注意:抓帧回调里不要做耗时操作,也不要直接
Application.ProcessMessages,这两条都会让采集卡顿甚至丢帧。
4. 这些坑我替你踩过了:编译、运行、部署的排查清单
4.1 编译期报"找不到单元"或接口不匹配
现象:打开示例工程,编译报File not found: 'DSPack.dcu',或者接口方法签名对不上。
原因:Library Path 没配全,或者混用了不同版本的源码单元。DSPack 的单元分散在Src\DirectX9、Src\DSPack等子目录,少加一个就找不到。接口不匹配多半是工程里残留了旧版本的.dcu,编译器优先用了缓存。
解决:清掉工程目录下所有.dcu,在 IDE 里确认 Library Path 包含全部源码子目录,然后 Rebuild。如果还报接口错,检查是不是同时引用了两个版本的 DSPack 单元,把多余的路径删掉。
4.2 运行时报"未找到指定模块"或类未注册
现象:程序在开发机跑得好好的,拷到客户机一启动就弹异常,提示某个 CLSID 未注册。
原因:DirectShow 的过滤器分系统自带和第三方两类。系统自带的(如视频渲染器)一般没问题,但如果你用了第三方解码器或采集卡的私有过滤器,客户机上没装对应驱动或运行库,就会报这个。
解决:先确认链路里每个 Filter 的 CLSID 在客户机上是否存在,用 GraphEdit 在目标机器上复现一遍链路最快。部署时把依赖的运行库一起打包,别假设客户机环境跟你一样。
4.3 预览有画面但抓帧全是黑图
现象:渲染器显示正常,但 SampleGrabber 回调拿到的数据解析出来是黑的或者花的。
原因:媒体类型没协商对,最常见的是源输出 YUY2 而代码按 RGB 解析;另一种是 SampleGrabber 被插在了渲染器之后,拿不到原始数据。
解决:在连接前打印AM_MEDIA_TYPE的subtype,确认实际格式;检查过滤器连接顺序,确保是"源 → Grabber → 渲染器"。如果格式确实不匹配,插一个颜色空间转换过滤器,别硬解析。
4.4 长时间运行后内存上涨或卡死
现象:跑十几分钟后程序越来越卡,任务管理器里内存持续上涨。
原因:回调里创建的TBitmap或样本没释放,或者跨线程操作 VCL 导致消息队列堆积。DSPack 的样本对象有引用计数,手动管理时容易漏。
解决:回调里用try...finally保证释放,界面更新统一投递到主线程。用任务管理器或 FastMM 观察内存曲线,定位泄漏点。这条没有捷径,只能靠代码审查加实测。
4.5 部署到 64 位系统时的位数问题
现象:32 位程序在 64 位 Windows 上能跑,但某些采集设备枚举不到。
原因:部分设备的 DirectShow 过滤器只有 32 位版本,64 位进程加载不了;反过来也一样。Delphi 2010 编出来的是 32 位程序,跟 32 位过滤器匹配,但如果客户机装的是纯 64 位驱动,就可能枚举失败。
解决:确认目标程序的位数和过滤器位数一致,必要时用 32 位编译目标。部署前在目标机器上跑一遍设备枚举,别等上线才发现。
5. 进阶:把采集封装成可复用单元的几个技巧
链路跑通只是开始,真正要落地到工程里,得考虑复用和稳定性。我一般会把采集逻辑抽成一个独立的类,对外只暴露"开始预览""停止""抓一帧"三个方法,内部管理 FilterGraph 的生命周期。这样换设备、换分辨率时,改动集中在一处,不会牵一发动全身。
分辨率切换是个典型场景。直接改源过滤器的输出格式往往不生效,正确做法是先 Stop 图,断开连接,重新协商媒体类型,再重新连接并 Run。顺序错了,图会处于半死状态,表现为预览黑屏但没报错。下面是个示意:
procedure TCapture.SetResolution(W, H: Integer); begin FGraph.Stop; FGraph.Disconnect; // 断开现有 Pin 连接 FSource.SetOutputSize(W, H); // 按实际封装调整,参数名以源码为准 FGraph.Connect; // 重新协商并连接 FGraph.Run; end;参数说明:SetOutputSize是示意方法,实际要操作源过滤器的IAMStreamConfig接口,枚举支持的格式再选最接近的。不是所有摄像头都支持任意分辨率,枚举出来的列表才是可用的,硬设一个不支持的值会连接失败。
验证方法上,我习惯做一个最小可复现的 Demo:只放一个按钮枚举设备、一个按钮开始预览、一个按钮抓帧存盘。每次改完封装,先跑这个 Demo,确认三条链路都正常,再往主工程里合。这比在主工程里调试快得多,因为主工程的依赖太多,报错信息容易被淹没。
还有一个容易被忽略的点:程序退出时的清理顺序。先 Stop 图,再释放 SampleGrabber,最后释放 FilterGraph,顺序反了会偶发访问违例。这类问题在开发机上不一定复现,客户机上跑久了才暴露,属于典型的"后悔药没处买"。从那以后我每次封装采集模块,都会在Destroy里按固定顺序走一遍清理,并且加日志确认每一步都执行到了,希望帮到你。
本文还有配套的精品资源,点击获取