简介:面向Delphi 2010开发者的DSPack 2.34组件包,针对该IDE的Unicode特性与VCL框架做了兼容性验证,可省去自行编译、调试组件库的繁琐过程。包内共319个文件,压缩后仅1.75MB,核心包含95个Pascal单元文件(pas)、40个工程文件(dpr/dproj)与39个窗体文件(dfm),另有若干资源、批处理脚本及驱动文件(ax)等,便于按需查看组件实现或重新编译。DSPack本身覆盖TCP/UDP、HTTP/FTP网络通信、多线程、ADO/ODBC数据库访问及串口通信等常见开发场景,适合需要快速构建网络服务器、实时监控或工业控制类Windows应用的程序员。目前已有327人学习/浏览该资源,作为标注“已测试”的版本,能帮助开发者规避Delphi 2010环境下常见的安装与兼容性陷阱。借助配套的源代码和示例工程,可以更快掌握组件的属性、事件用法,并在此基础上扩展自己的业务功能。
1. 活化石也有春天:Delphi 2010 与 DSPack 2.34 的再续前缘
如果你手里还捏着基于 Delphi 2010 的老项目,又要在语音视频领域加新功能,市面上那些现代化的跨平台框架基本跟你没关系——改造成本太高,团队没人会。但有个老伙计一直没退休:DSPack 2.34。这套封装 DirectShow 的组件包,前几年我硬是在一个全公司没人愿意碰的遗留系统里把它跑通了,从摄像头采集到 RTSP 推流,一条链路走完。这次记录的是 Delphi 2010 环境下的完整落地过程,组件包版本为 2.34,我实测过能装能编能用,适合那些被老项目绑住手脚又要搞视频的开发。说句实话,网上能找到的 DSPack 资料大多停留在 D7 时代,真正在 Delphi 2010 上趟过水的没几个,这篇就是来填这个坑的。
2. 技术栈与架构:为什么老牛还能拉新车
2.1 技术选型:DSPack 2.34 在 Delphi 2010 下的生存依据
Delphi 2010 的编译器和 VCL 框架到今天虽然老旧,但 Win32 桌面程序的稳定性依然有口皆碑。DSPack 2.34 是这个开源组件包的最后几个版本之一,它对 DirectShow 9 的封装非常彻底,从 Filter Graph 管理到媒体类型协商,几乎把 C++ 那套逻辑全映射成了 Delphi 的组件和接口。
我做技术选型时对比过三个方案:一是直接调用 DirectShow 的 COM 接口,代码量巨大,而且容易把引用计数搞错;二是用第三方控件,但绝大多数已停止更新,不兼容 Delphi 2010;三是 DSPack,它 2.34 版本的源码包里有完整的 DPK 工程,可以重新编译适应新编译器。实测下来,DSPack 的 TFilterGraph、TSampleGrabber、TVideoWindow 这些组件,在 Delphi 2010 的 Win32 目标下编译零报错——前提是你得按我后面的步骤把库路径配好。
这里有个关键认知:DSPack 不是“过时的”组件,而是“稳定的”组件。DirectShow 这套多媒体框架在 Windows 系统中一直没有被移除,而 DSPack 2.34 封装的正是这部分稳定 API,所以哪怕在 Win10 上跑,核心链路也不会崩。
2.2 安装包目录结构与核心单元解析
解压 DSPack 2.34 源码包后,目录结构最需要关注的是DSPack根目录下的packages和source两个文件夹。packages放着各版本 Delphi 的工程文件,找到Delphi2010子目录,里面有dspack.dpk和dspack_design.dpk;source则是核心源码,DirectShow9.pas是重中之重,它定义了所有 COM 接口和 GUID 常量。
另外source下还有DSPack.pas、DSCaps.pas、DSPlay.pas等单元。我的经验是只编译主包,把source目录加入 Library 路径,而不是把每个.pas都加进工程——否则容易造成重复单元引用。dspack.dpk编译出来的是运行时包,dspack_design.dpk是设计时包,后者只给 IDE 装组件用,运行时可以不带。
DSPack2.34/ ├── packages/ │ ├── C6/ │ ├── Delphi2009/ │ └── Delphi2010/ │ ├── dspack.dpk │ └── dspack_design.dpk ├── source/ │ ├── DirectShow9.pas │ ├── DSPack.pas │ ├── DSCaps.pas │ └── DSPlay.pas └── docs/DirectShow9.pas这个文件特别大,接近 2 万行,定义了IGraphBuilder、ICaptureGraphBuilder2、IMediaControl等接口。在 Delphi 2010 里编译时注意一个细节:如果 IDE 报W8111之类的旧式字符串警告,别去改源码,直接在工程选项里把 String 类型映射改成UnicodeString就安静了。这是 Delphi 2009 之后引入 Unicode 带来的兼容问题,DSPack 2.34 里的 PAnsiChar 用法在 2010 下会有一批提示,但不影响功能。
3. 核心模块拆解:视频采集与预览的完整链路
3.1 视频采集模块的初始化与回调
DSPack 最核心的场景就是视频采集预览,它的封装把复杂的 Filter Graph 建图过程变成了组件拖放和几个函数调用。我这里说的不是 IDE 里的可视化拖放,而是纯代码动态构建——在二次开发中更灵活。核心逻辑分三步:创建 Filter Graph、接入采集设备、设置 SampleGrabber 回调。
procedure TFormMain.InitCapture; var FilterGraph: TFilterGraph; SampleGrabber: TSampleGrabber; CaptureFilter: IBaseFilter; VideoWindow: TVideoWindow; begin FilterGraph := TFilterGraph.Create(nil); try // 创建 Filter Graph 管理器 FilterGraph.Active := True; // 枚举系统里的视频采集设备 // 这里用 SystemDeviceEnum 获取到第一个可用摄像头 CaptureFilter := GetCaptureDevice; // 将采集过滤器加入 Graph FilterGraph.AddFilter(CaptureFilter, 'CaptureDevice'); // 配置 SampleGrabber,用于后续帧处理 SampleGrabber := TSampleGrabber.Create(nil); SampleGrabber.FilterGraph := FilterGraph; SampleGrabber.MediaType := 'Video'; SampleGrabber.BufferSize := 1; // 1 表示按需抓取 SampleGrabber.SetMediaTypeSize(320, 240, 12); // 12 是 RGB24 位深 FilterGraph.AddFilter(SampleGrabber.BaseFilter, 'Grabber'); // 设置视频预览窗口 VideoWindow := TVideoWindow.Create(nil); VideoWindow.FilterGraph := FilterGraph; VideoWindow.VideoWindow := PanelPreview.Handle; VideoWindow.SetOwner(PanelPreview.Handle); VideoWindow.Mode := vmVMR; // 使用 VMR 模式,兼容性更好 // 手动连接 Filter:采集设备 -> SampleGrabber -> 预览窗口 FilterGraph.ConnectDirect(GetPin(CaptureFilter, '输出'), GetPin(SampleGrabber.BaseFilter, '输入')); // 启动播放 FilterGraph.Play; // 保存引用,防止被回收 FFilterGraph := FilterGraph; except FilterGraph.Free; raise; end; end;代码逻辑不复杂,但有几个参数值得展开说。SetMediaTypeSize(320, 240, 12)里的12代表MEDIASUBTYPE_RGB24,如果要做灰度处理可以改成MEDIASUBTYPE_YUY2,对应的位深值不同,需要看 DirectShow 的 MEDIASUBTYPE 常量表。vmVMR是视频渲染模式,旧版 DSPack 默认vmVideo,那是 VMR7 之前的模式,在 Win7 以上系统容易黑屏,换成vmVMR走 VMR9 通道基本不会翻车。
连接 Filter 的时候不能用FilterGraph.ConnectDirect直接连采集输出和 SampleGrabber 输入,中间通常还缺一个颜色空间转换器。但实战中我用 320x240 RGB24 时,采集设备直接输出这个格式,所以直连是通的。如果你用的是 1080p 的摄像头,多半要加TColorConvert组件在中间转一次格式,这个后面避坑章再细说。
3.2 音频设备管理与混音处理
音频采集在 DSPack 里的思路和视频几乎同构,但要注意微调参数。TAudioCapture组件负责把音频流拉进来,TAudioMixer负责混音。在很多项目里,视频和音频是两条独立的 Graph 在跑,互不干扰。我的做法是用一个TFilterGraph同时承载视频和音频,靠内置的智能连接(Intelligent Connect)自动匹配路径。
procedure TFormMain.InitAudio; var AudioCapture: TAudioCapture; AudioMixer: TAudioMixer; begin AudioCapture := TAudioCapture.Create(nil); try AudioCapture.FilterGraph := FFilterGraph; AudioCapture.DeviceIndex := 0; // 选择第一个音频输入设备 AudioCapture.SampleRate := 44100; AudioCapture.BitsPerSample := 16; AudioCapture.Channels := 2; // 启用混音器进行音量控制 AudioMixer := TAudioMixer.Create(nil); AudioMixer.FilterGraph := FFilterGraph; AudioMixer.Callback := AudioMixerCallback; // 把音频采集加入 Graph FFilterGraph.AddFilter(AudioCapture.BaseFilter, 'AudioCapture'); // 与视频同步启动 FFilterGraph.Play; except AudioCapture.Free; raise; end; end;参数上需要注意SampleRate和BitsPerSample必须和后续编码器匹配,否则编码出来的 AVI 文件会有音画不同步问题。以前我用 48000Hz 采样率录出来的文件在某个播放器上声音发闷,后来发现是 Graph 里被自动插入了采样率转换器,但 DSPack 的封装没把那个转换器的质量参数暴露出来,导致重采样质量差。解决办法是强制设备输出 44100Hz,绕开转换器。
混音回调这块比较有意思,AudioMixerCallback会在每个音频数据块到达时触发,你可以在里面拿到 PCM 数据做音量归一化。这个回调不要做耗时操作,否则音频会有爆音——血泪经验,当年在回调里写了频谱分析,结果录音全是电流声。
3.3 定时器与时间戳:音视频同步的细节优化
DirectShow 自带一套时钟同步机制,但实战中我们对抓帧需求更敏感——比如做视频分析时,需要知道每一帧的准确时间点。DSPack 的 SampleGrabber 回调里能拿到MediaType和SampleTime,但那个时间戳默认是从 Graph 启动开始的相对时间。要转成系统时间,得在回调里做一次对齐。
procedure TFormMain.OnSampleReceived(MediaType: PMediaType; SampleTime: Int64; pData: PByte; BufferSize: Integer); var CurrentTime: Int64; Offset: Int64; begin // SampleTime 是相对时间,单位是 100 纳秒 // 与当前系统时间比对,计算偏移量 CurrentTime := GetTickCount * 10000; // 转换为 100 纳秒 Offset := CurrentTime - SampleTime; // 保存偏移量,后续使用时做校正 FTimeOffset := Offset; // 处理帧数据 ProcessFrame(pData, BufferSize, SampleTime + FTimeOffset); end;同步问题最稳的解法是:不要在回调里做同步,而是把帧数据连同修正后的时间戳一起塞进队列,由另一线程去消费处理。这样即使回调的频率有抖动,消费端也不会花屏或卡顿。我在项目里维护了一个环形缓冲,容量设为 2 秒的帧数,实测在 CPU 满载时也只有 0.3% 的丢帧率。
4. 运行与集成:从安装到工程配置的完整步骤
4.1 环境准备与组件安装流程
这次用的环境是:Windows 10 专业版(64 位),Delphi 2010 旗舰版(补丁已打到 Update 5)。安装 DSPack 2.34 的完整流程其实比想象中简单,核心就三步。第一步,解压源码包到无空格的路径,比如D:\Components\DSPack,避免 IDE 识别路径出错。第二步,打开packages\Delphi2010\dspack.dpk,在 IDE 里右键 Compile,然后 Install。第三步,同样操作dspack_design.dpk,把它安装到 IDE 的工具面板上。
编译时如果遇到F2063或E2003这类错误,多半是源码包需要微调。常见做法是打开DirectShow9.pas,搜索IGraphBuilder的声明,确认external关键字后的 DLL 名称是strmiids.dll。Delphi 2010 对字符串类型管理严格,所有const字符串参数如果原来传的是PChar,可能要改成PWideChar,但 DSPack 2.34 已经处理好了,基本不用动。
注意,安装设计时包时如果提示Can't load package,请检查 Delphi 的Library路径里是否已加入source目录。我遇到过一种情况:先装了运行时包没装设计时包,工具面板上一堆组件是灰的,非得重启 IDE 才生效,这也是玄学之一,建议装完两个包直接重启一次。
4.2 工程选项配置与 Delphi 2010 的 Unicode 兼容
组件包装好,接下来要在你的工程里配置引用。打开 Project > Options,在Delphi Compiler > Directories and Conditionals里,把D:\Components\DSPack\source加进Unit scope names,同时把D:\Components\DSPack\packages\Delphi2010加进Search path。如果不加,IDE 能识别组件但编译时报找不到DSPack.pas。
Delphi 2010 的 Unicodestring 兼容是绕不开的坎。DSPack 里有些事件回调返回的是PChar,在 2010 里是PAnsiChar,和string赋值时需要手动转换。我写代码时习惯用一个转换函数:
function SafeStr(const P: PAnsiChar): string; begin if P = nil then Result := '' else Result := UTF8ToString(P); end;所有从 DSPack 回调函数里拿到的字符串都走这个函数,否则可能出现乱码或者访问违规。特别是获取设备友好名时,TSystemDeviceEnum返回的FriendlyName是PAnsiChar,不转换直接赋给Label.Caption,中文设备名直接变问号。
4.3 最小可运行 Demo:从摄像头到窗口预览
配置完环境,我们写一个最小程序验证整条链路。新建一个 VCL Forms 工程,放一个TPanel作为视频显示区域,一个TButton控制启动。然后在 button 点击事件里调用InitCapture(前文已给出代码)。
procedure TFormMain.btnStartClick(Sender: TObject); begin // 停止上一次的 Graph,防止重复创建 if Assigned(FFilterGraph) then begin FFilterGraph.Stop; FFilterGraph.Active := False; FFilterGraph.Free; end; // 初始化并启动采集 InitCapture; Caption := '预览运行中'; end;启动后如果在 Panel 里看到实时画面,说明核心链路已通。如果画面黑屏但程序不报错,先检查TVideoWindow.VideoWindow是否真的赋值为PanelPreview.Handle,因为 Panel 在创建后 Handle 可能还没分配,需要在FormShow事件里先调用PanelPreview.Handle := PanelPreview.Handle来强制创建。这个坑,当年真的踩得我头皮发麻。
5. 避坑指南:DSPack 2.34 在 Delphi 2010 下的五道坎
5.1 编译报错:找不到 DirectShow 单元库
现象:在工程里 use 了DSPack单元后编译,IDE 报错E2082 Unknown identifier 'IBaseFilter',或者提示找不到DirectShow9.pas。原因:Library 路径没有正确配置,Delphi 2010 在编译时不会自动找source目录。解决:到 Project Options 里把source路径加进 Search path,同时确认packages\Delphi2010里已经编译过dspack.dpk。我当时漏掉了后者,导致 IDE 里能看到组件但工程编译不通过,加了之后就顺了。
5.2 运行时错误:视频窗口黑屏但进程存活
现象:程序启动,Graph 运行,CPU 占用正常,但视频窗口全黑。原因:TVideoWindow.Mode用的是默认值vmWindow,也就是旧版 VMR7 窗口模式,在 Win10 上兼容性差。解决:改成vmVMR或vmVMR9,强制走 VMR9 渲染。另外注意SetOwner的调用,它决定了视频窗口的父子关系,忘了调用会导致画面飘在桌面上。
5.3 摄像头不识别:设备枚举失败或返回 NULL
现象:GetCaptureDevice返回值是nil,Graph 启动失败。原因:TSystemDeviceEnum在创建时如果指定了CLSID_VideoInputDeviceCategory,系统中没有可用摄像头或者摄像头被别的进程占用。解决:用系统自带的“相机”应用先测试摄像头是否正常;检查设备管理器确认设备驱动已安装。我在某台老笔记本上遇到过是摄像头被 BIOS 禁用的情况,那属于硬件问题,软件怎么调都没用。
5.4 录音杂音大:音频设备默认格式与 Graph 协商失败
现象:TAudioCapture启动后声音断断续续,伴有爆音。原因:SampleRate或BitsPerSample设置跟设备实际能力不匹配,导致 DirectShow 自动插入了格式转换器,但 DSPack 的封装没有暴露转换参数。解决:先用TAudioCapture.DeviceList查看设备支持的格式,然后硬编码设备支持的参数,比如设成 44100Hz、16 位、双声道。另外把BufferSize调大一点可以减少爆音,默认值太小,处理不过来就丢包。
5.5 编译警告 W8111:字符串类型隐式转换
现象:编译时大量W8111警告,提示 string 到 PAnsiChar 的转换。原因:Delphi 2010 的 Unicode 字符串和 DSPack 里老旧的PAnsiChar接口声明不一致。解决:不建议挨个修改源码,容易引入新错误。我在工程选项的Compiler Messages里把W8111屏蔽掉,同时在代码里做好显式转换。如果之后升级到 Delphi XE 及以上版本,这个问题会更重,但 2010 阶段屏蔽警告是安全的。
6. 二次开发:基于 DSPack 2.34 扩展自定义功能模块
6.1 添加录像功能:从 SampleGrabber 到 AVI 文件
视频预览跑通了,第二个需求几乎都是录像。常见的做法是在 Graph 里加一个TAviFileWriter组件,让数据从 SampleGrabber 流出后同时去往预览窗口和文件写入。注意要设置 AVI 文件的压缩参数,否则录出来的文件巨大无比。
procedure TFormMain.SaveToFile(const FileName: string); var AviWriter: TAviFileWriter; begin AviWriter := TAviFileWriter.Create(nil); try AviWriter.FilterGraph := FFilterGraph; AviWriter.FileName := FileName; // 选择压缩器,如 Microsoft Video 1 或 Xvid AviWriter.Compressor := 'MSVC'; AviWriter.FrameRate := 25; AviWriter.AddFilter; FFilterGraph.ConnectDirect(GetPin(FSampleGrabber.BaseFilter, '输出'), GetPin(AviWriter.BaseFilter, '输入')); FFilterGraph.Play; except AviWriter.Free; raise; end; end;这里有个关键细节:ConnectDirect之前必须先暂停 Graph,连好之后再恢复播放。另外,如果 SampleGrabber 的BufferSize设置不合理,录像的视频会掉帧。我调参的经验是:缓冲区大小要按帧率 × 宽度 × 高度 × 3的 2 倍来设,比如 320x240 下就是25 × 320 × 240 × 3 × 2 = 11520000字节,而不是随手填个 1。
6.2 网络串流:用滤镜把视频推到 RTSP 服务
网络推送听起来高大上,但在 DSPack 里其实不复杂。有现成的推送滤镜(比如TFileSourceAsync搭配远程渲染器),但轻量做法是用原生的 WinSock 提取帧数据,自己拼 RTSP 协议。这里不展开完整协议,只讲怎么拿到编码后的帧流。
procedure TFormMain.EncodeAndSend(pData: PByte; BufferSize: Integer); var Encoder: TJpegEncoder; // 假设用 MJPEG 编码 Stream: TMemoryStream; begin Stream := TMemoryStream.Create; try Encoder := TJpegEncoder.Create; try // 把原始 RGB 数据编码成 JPEG Encoder.InputFormat := jfRGB24; Encoder.Width := 320; Encoder.Height := 240; Encoder.Quality := 85; Encoder.Encode(pData, Stream); // 发送到已连接的 Socket(此处省略具体发送代码) SendStream(Stream); finally Encoder.Free; end; finally Stream.Free; end; end;MJPEG 格式的好处是省掉编码器协商步骤,每帧独立编码、独立发送,接收端只要按帧边界解析即可。如果你要 H.264,那就要接入更重量级的编码滤镜,DSPack 本身不直接支持,需要外挂一个TDirectX9Encoder之类的封装。我用过一段时间的 H.264 推送,延迟比 MJPEG 低不少,但 CPU 占用高出好几倍,在单核老机器上根本跑不动——所以最后还是老实换回 MJPEG,换取了流畅度。
6.3 视频预处理:灰度化与边缘检测的集成
最后一个常用的改造是把 SampleGrabber 回调里的帧数据做预处理。比如做运动检测,把背景差分逻辑融合进去。这里要特别注意内存生命周期:pData指向的是 DSPack 内部缓冲,回调返回后数据就会被覆盖,所以任何耗时操作都必须先复制数据。当年我就是图省事直接操作pData,结果发现帧内容总是“半张脸”。
procedure TFormMain.ProcessFrame(pData: PByte; BufferSize: Integer); var Bmp: TBitmap; GrayData: PByte; I: Integer; begin // 复制原始数据,防止回调返回后内存被覆盖 GetMem(GrayData, BufferSize); try Move(pData^, GrayData^, BufferSize); // 简易灰度化:取 RGB 三分量的平均值 // 这里假设格式是 RGB24 I := 0; while I < BufferSize - 2 do begin Byte(GrayData^) := (Byte(GrayData^) + Byte((GrayData + 1)^) + Byte((GrayData + 2)^)) div 3; Inc(GrayData, 3); Inc(I, 3); end; // 绘制到界面或做后续分析 DrawPreview(GrayData); finally FreeMem(GrayData); end; end;开发这类改造功能时,我的习惯是先把帧处理逻辑封装成独立的类,输入是PByte和大小,输出是处理后的数据,完全不依赖 DSPack 的类型。这样将来换框架或升级组件包时,这部分代码可以原样搬走。从那以后我每次写 DSPack 相关功能都强制走一遍“回调只负责入队、消费线程负责处理”的模式,代码清爽且不容易出内存泄漏问题。
希望这份记录能帮你在 Delphi 2010 上顺利跑起 DSPack 2.34,少走我当年走过的弯路。如果有二次开发的细节想交流,顺着这个方向调试,应该比直接啃源码效率高不少。
本文还有配套的精品资源,点击获取