简介:这是专为 Delphi 2010 验证可用的 DSPack 2.34 组件包,主要面向基于该 IDE 的 Windows 桌面应用开发者,解决组件库与版本环境的兼容问题。DSPack 提供网络通信、多线程、数据库访问、串口通信等常用组件,适合构建服务器程序、实时监控或工业控制系统。资源以 RAR 压缩包形式提供,大小 1.75 MB,共 319 个文件,其中包含 95 个 Pascal 源文件、40 个工程文件、39 个窗体文件,以及组件包定义、注册脚本和示例程序等,文件类型覆盖了从源码设计到编译部署的完整链路,结构清晰。该版本已通过实际测试,导入 IDE 后即可直接使用,有效降低配置门槛。目前已有 327 人学习下载,对 Delphi 2010 开发者而言是一份实用且省时的组件资源。
1. 为什么还在用 DSPack 2.34:Delphi 2010 视频模块的现实选择
Delphi 2010 做视频播放,官方没给现成控件,能选的库屈指可数。DSPack 2.34 是 DirectShow 的完整封装,覆盖文件播放、摄像头采集、截图抓帧,而且不需要额外安装 DirectX SDK。很多老项目从 Delphi 7 迁移到 2010 时,播放器还是靠它撑着,因为这套接口稳定、占用小。适合谁?手里有 Delphi 2010 老工程要维护、或者想快速拉通一个本地视频播放和采集功能的开发者,以及做二次开发的人。下面从安装到调用,按我实测的路径走一遍。
2. 安装部署:让 DSPack 2.34 在 Delphi 2010 里跑起来
拿到 DSPack 2.34 后,第一步不是建工程,而是先把源码里的包编译一遍。这步很多人卡住,因为 DSPack 2.34 的发布包默认是按 Delphi 6/7 时代的目录结构组织的,直接打开 DPK 文件到 2010 里编译会报出几十个版本不匹配的错。我的习惯是先把整个目录拷到固定路径,比如 D:\libs\DSPack2.34,然后按下面的顺序处理。
2.1 看清目录结构:旧包编译前要做的三件事
DSPack 2.34 解压出来大致有这些目录:\Source、\Packages、\Examples,其中 Packages 里是运行时包和设计期包。因为年代久远,里面既有 D6/D7 的 .dpk,也有后来补的 D9 等版本,但 2010 的编译器是 Unicode 版,和早期 Ansistring 时代的源码混在一起,直接编很容易报错。我一般会先做三件事:
- 把整个目录里的 .dpk 文件按名字归类,找出对应 Delphi 2010 的版本。通常 DSPack 包里会有带 D10 或 D2010 后缀的文件,如果没有,就用最新的那个 Dpk 改。
- 检查源码里的字符串类型,凡是 PChar 转 PAnsiChar 的地方,在 2010 下需要手工调整。
- 确认 IDE 的 Library 路径里没有残留旧版本的 DSPack 路径,避免编译器抓到旧 DCU。
这三步做完,再开始编译包,比直接双击 DPK 要稳得多。下面用一个命令行方式说明编译顺序,实际上 IDE 里也能操作,但命令行更适合看清依赖顺序。
cd /d D:\libs\DSPack2.34\Packages dcc32.exe -JD2010 DSPack_RT.dpk dcc32.exe -JD2010 DSPack_DT.dpk说明一下:第一行是编译运行时包,第二行是编译设计期包,-J 参数让 dcc32 按指定版本生成。实际在 IDE 里编译时,顺序也是先编译运行时包、再装设计期包。如果 DSPack_RT.dpk 编译失败,别急着往下走,先把错误列表里的 .pas 文件对应到 \Source 目录,逐个打开检查有没有用到不支持的旧 API——多数报错是 DirectShow 接口的 GUID 变了,或者是类型转换没写。
2.2 编译运行时包与设计期包:顺序、开关和常见报错映射
编译时有几个关键开关我先说结论:一是关闭代码页映射,二是打开 Unicode 编译开关。Delphi 2010 默认已经是 Unicode,但 DSPack 2.34 很多单元是用 WideString 混着 AnsiString 写的,如果不把“字符串语法检查”设为“Extended”,会冒出大量无法重载的 Operator 错误。另外一个容易翻车的点:DSPack 里自带一个 DirectShow 接口定义单元 DSIntf.pas,和系统里的 D3DX10 之类没有交集,但如果你之前装过其他 DirectX 库,可能造成 DCU 冲突。遇到这种情况,最快的解决方案是打开 DSPack 的运行时包项目,在条件编译里加一个:
{$DEFINE USEDSPACK}然后重新编译全部单元,这个宏的作用是让源码内部用自己的接口声明,不引用外部 D3D 版本。编译通过后,设计期包也要单独编译一次,因为 2010 的 IDE 需要设计期包来把组件注册到工具箱。装完设计期包,正常情况下工具栏会出现 DSTrackBar、TVideoWindow 等组件。如果没出现,多半是包编译成功了但没注册,可以手动执行:
regsvr32.exe /u DSPack_DT.bpl regsvr32.exe DSPack_DT.bpl这里 regsvr32 是注册 BPL 组件包。现实中很多人在这一步卡住,是因为没有安装运行时包却被设计期包引用,导致 IDE 启动时报找不到 DSPack_RT.bpl。所以顺序必须是 RT 先编、DT 后装,且 RT 编译后的 BPL 要放到 IDE 能加载的目录里,比如系统 System32 或 IDE 的 Bin 目录。
在编译顺序上,我的做法是用批处理脚本串起来,这样重装系统后能一键恢复环境。脚本核心就五行,大家可以根据实际路径改:
set DSPACK_PATH=D:\libs\DSPack2.34 cd /d %DSPACK_PATH%\Packages dcc32 -CC -JPHNE -JD2010 DSPack_RT.dpk dcc32 -CC -JPHNE -JD2010 DSPack_DT.dpk start /b "" "C:\Program Files\Embarcadero\RAD Studio\7.0\bin\regsvr32.exe" DSPack_DT.bpl这段脚本里,-CC 表示生成控制台下的包,-JPHNE 是编译开关的集合,实际含义是“建立发布版并带调试符号”,更常用的做法是在 IDE 里直接用 Build,因为 IDE 能显示更详细的错误上下文。命令行脚本适合熟悉 dcc32 的人,新手还是打开 DPK 点右键编译更直观。记住一个原则:RT 包编译失败就不要继续装 DT 包,因为设计期包只是把组件暴露到 IDE,它本身依赖 RT 包里的类实现。
2.3 配置 Library Path:让 DCU 和源码都被找到
包编译成功只是第一步,为了以后每个工程都能直接用 DSPack,必须把源码目录加到 Delphi 2010 的 Library Path 里。打开 IDE 菜单 Tools | Options | Library,点右边的小文件夹图标,添加两个路径:
| 路径 | 用途 |
|---|---|
| D:\libs\DSPack2.34\Source | 让源码可被检索,断点也能进 |
| D:\libs\DSPack2.34\Packages | 让 DCU 文件可被链接 |
这里有个容易踩的细节:路径中不要包含中文或空格,否则 Delphi 2010 的 IDE 在更新工程时偶尔会做路径字符串拼接,导致 “File not found” 的玄学报错。加完路径后,先重启 IDE,再新建一个 VCL Forms 工程,在代码里写 uses DSPack; 测试一下编译是否通过。如果编译通过,说明环境已经就绪,可以进入播放器开发。这一步我花的时间比预想久,因为之前图省事只加了一个 Source 路径,运行时报缺 DCU,一度怀疑安装包坏了。
2.4 跑通自带 Demo:用示例工程验证一切正常
安装后不需要自己从头写,DSPack 包里的 Examples 目录有现成的播放器 Demo。打开 \Examples\Player 里的 Player.dpr,编译运行。如果环境和路径都正确,这个 Demo 会直接用 FilterGraph 完成打开、播放、暂停、停止。如果 Demo 编译不过,说明前面的库路径或包编译有问题,回去检查。我一般用这个 Demo 作为“安装成功”的判定标准,而不是自己在空白窗体上写测试代码,因为 Demo 把事件处理都写全了,能跑通就证明环境没问题。这里也可以看到 DSPack 2.34 自带的经典代码风格——用 TFilterGraph.RenderFile 加 VideoWindow 的简单组合。
3. 播放器实战:用 TFilterGraph 和 VideoWindow 拉通视频链路
环境就绪后,写一个最小播放器其实不难。DSPack 的核心是 TFilterGraph,它封装了 DirectShow 的 Filter Graph Manager,所有媒体数据流都通过滤镜链串联。这里要理解两个概念:Filter 是处理媒体数据的模块,Pin 是 Filter 之间的连接点。我们只需要三个组件:TFilterGraph、TVideoWindow、TDSTrackBar(可选)。TFilterGraph 承担创建和调度滤镜的职责,TVideoWindow 负责视频显示。
3.1 初始化 COM 与 FilterGraph:最小可播放代码
Delphi 2010 的项目在 Application.Initialize 时一般会调用 CoInitialize,但 DSPack 的 FilterGraph 在创建时也需要 COM 环境,所以我习惯在 FormCreate 里显式初始化一次。代码放下面,这是可以直接粘贴到空白窗体里的最小版本:
procedure TForm1.FormCreate(Sender: TObject); begin // 显式初始化 COM,保证线程模型为 STA CoInitialize(nil); // 创建 FilterGraph,DSPack 内部会创建 DirectShow 的 Graph FGraph := TFilterGraph.Create(nil); // 连接视频窗口,重要:必须先 SetDisplayMode 再绑定 Handle FGraph.Active := False; VideoWindow := TVideoWindow.Create(Self); VideoWindow.FilterGraph := FGraph; VideoWindow.Mode := vwNormal; VideoWindow.Left := 10; VideoWindow.Top := 10; VideoWindow.Width := 640; VideoWindow.Height := 480; end; procedure TForm1.FormDestroy(Sender: TObject); begin FGraph.Active := False; FGraph.Free; CoUninitialize; end;这段代码的要点是:FGraph 先设为 Active := False,因为此时还没有渲染任何文件;VideoWindow 的 FilterGraph 属性必须在创建后立即赋值,否则 DSPack 不知道视频输出到哪个窗口。CoInitialize 和 CoUninitialize 必须配对,FormDestroy 里先停掉 FilterGraph 再释放,顺序反了容易出现偶发崩溃。另外注意 VideoWindow 是放在窗体上的组件,需要手动把它的 Parent 设为窗体并设置 Visible。
3.2 渲染文件与播放控制:完整示例
有了上面的基础,接下来实现打开文件并播放。DSPack 的 FilterGraph 有一个很省事的方法:RenderFile。它会把文件自动匹配到合适的源 Filter,然后构建完整的解码链路。下面是 Button1 的 OnClick 事件:
uses DSUtils, DSPack; procedure TForm1.Button1Click(Sender: TObject); var FileName: string; begin if OpenDialog1.Execute then begin FileName := OpenDialog1.FileName; FGraph.Active := False; FGraph.ClearGraph; // 渲染文件,内部会自己选择解码器 FGraph.RenderFile(FileName, nil); // 播放前必须把采集设备或文件源的第一针拉到窗口 VideoWindow.FilterGraph := FGraph; FGraph.Active := True; // 开始播放 FGraph.RefGraph; // 强制刷新 Graph end; end;这里 FileName 在 Delphi 2010 下是 UnicodeString,DSPack 内部调用的 RenderFile 接收的是 WideString,所以直接用 string 传参没问题,编译器会自动转换。但有一点要注意:如果文件路径里有空格,某些第三方解码器可能识别失败,表现为渲染成功但播放黑屏。我一般会在 RenderFile 之前调用一个简单封装:
function WidenString(const S: string): WideString; begin Result := WideString(S); end;然后在调用处写成 FGraph.RenderFile(WidenString(FileName), nil)。这不是必须的,但能避免 2010 编译器在 XE 以后版本迁移时的隐式转换警告。播放控制(暂停/停止)直接用 FGraph.Paused 和 FGraph.Stop。如果要实现进度条,用 TDSTrackBar 组件,它的 Position 绑定到媒体时间线,再配合 Timer 刷新。
另外,DSPack 没有直接的 OnPlayEnd 事件,一般通过定时器轮询位置判断。常见做法是放一个 TTimer,每隔 200ms 读取 FGraph.CurrentPosition 和 FGraph.Duration,当位置接近末尾时重置播放状态。参考代码:
procedure TForm1.Timer1Timer(Sender: TObject); begin if FGraph.Active and (FGraph.Duration > 0) then if FGraph.CurrentPosition >= FGraph.Duration - 0.5 then begin FGraph.Stop; // 可选:回到第一帧 FGraph.CurrentPosition := 0; end; end;这样可以处理“播放完界面卡在最后一帧”的常见反馈。很多新手只实现了打开和播放,忘了处理结尾状态,结果视频播完后再点重新播放,画面仍然是最后一帧,被客户当 Bug 报。
3.3 为什么用 DSPack 而不是直接调 DirectShow
有人会问:DirectShow 原生的 IGraphBuilder 接口也够用,为什么要绕一层 DSPack?我的看法是:DSPack 帮我们把 COM 引用计数、媒体事件窗口、VCL 组件封装都处理好了,尤其在 Delphi 2010 下,直接用 COM 接口写枚举音频设备、抓帧回调这些代码,代码量会翻倍。DSPack 的 TSampleGrabber 是现成的,直接拖到窗体上设置回调函数就能抓帧。另一个原因是 DSPack 的 FilterGraph 内部解决了事件线程同步,直接用 IGraphBuilder 时,窗口消息要自己收到 OnGraphEvent 里处理,漏处理会导致播放结束界面不刷新。
而且,DSPack 2.34 在 XP 到 Win10 的兼容性已经被大量老项目验证过,虽然它不再更新,但 DirectShow 本身还留在系统里,系统不会因为少一个 DLL 就挂掉。相比之下,很多新封装库依赖特定运行库版本,部署成本反而高。| 方式 | 代码量 | 事件处理 | 抓帧 | 部署 | | --- | --- | --- | --- | --- | | 原生 DirectShow | 多 | 手动 | 手动 | 无需额外 DLL | | DSPack 2.34 | 少 | 自动 | 拖组件 | 带 BPL | | 其他封装库 | 中 | 不一定 | 一般提供 | 依赖运行库 |
3.4 部署到目标机器:BPL 与解码器依赖
开发机跑通后,要把 exe 发给客户。最小部署文件包括:exe、DSPack_RT.bpl、以及可能的 BorlndMM.dll(如果开了运行时包模式)。把这三个文件放同一目录,目标机器不需要 Delphi,也不需要注册 BPL,Windows 加载 exe 时会从同级目录找到动态库。这里有个坑:如果用了运行时包,exe 里不会包含 DSPack 的代码,必须在客户机器上放置对应版本的 BPL;反过来,如果你编译工程时把所有包都设成运行时包,反而会报“无法定位 DSPack_RT.bpl”。推荐的方式是在工程选项里把 Link with runtime packages 勾选上,并只保留 DSPack 和 VCL,这样 BPL 数量可控。
还有解码器依赖:DSPack 用系统的 DirectShow 滤镜,如果客户机器上缺少 H.264 解码器,MP4 会放不了。常见方案是打包时带上一个开源解码器安装包,或者建议客户安装万能解码器。我自己的项目里会额外判断 RenderFile 是否抛错,抛错时提示用户安装解码器,而不是让程序静默崩溃。
4. 避坑与排查:DSPack 2.34 在 Delphi 2010 下的五个真实翻车现场
这一章直接列出我踩过的坑。每一条都按“现象→原因→解决”展开,方便你对照排查。这些坑不是文档里写的,是编译器和老库打架打出来的血泪经验。
4.1 现象:编译时报错 “File not found: 'DSIntf.dcu'”
原因:DSIntf 是 DSPack 的核心接口单元,Delphi 2010 在编译工程时没有搜索到该 DCU,常见于 Library Path 只加到了 Source 一级目录,但 Source 下还有子目录 Controls、Filters 等,编译器只按 Library Path 精确查找。解决:把 DSPack 源码根目录以及其下的所有子目录全部加入 Library Path,或者直接把整个 D:\libs\DSPack2.34 加进去并勾选“Include subdirectories”。这里要特别说明,Delphi 2010 的 Library Path 不支持通配符,必须逐层添加。我之前偷懒只加了一个根目录,结果编译时只报这个错,误以为包没编译成功,其实 DCU 就在子目录里躺着。
4.2 现象:播放时无画面、黑屏但音频正常
原因:TVideoWindow 没有绑定到 FilterGraph,或者 FilterGraph.Active 在 RenderFile 之前就被设为 True。DSPack 的设计是 Active 控制整个 Graph 的运行状态,必须先渲染再激活。解决:在 RenderFile 之后、Active := True 之前,确保 VideoWindow.FilterGraph := FGraph 这行代码已经执行。还有一个隐藏原因:窗口句柄在窗体还没创建完成时被绑定,导致视频渲染到 0 号句柄。我一般会在 FormShow 里执行绑定动作,而不是 FormCreate,因为这时句柄才稳定。步骤拆开看就是:先在 OnCreate 里建 Graph,在 OnShow 里绑定窗口并激活。
4.3 现象:工程设计期出现 TMediaPlayer 与 DSPack 组件类型冲突
原因:Delphi 自带的 TMediaPlayer 和 DSPack 的 TDSTrackBar 在组件注册时使用了相同的类名或资源 ID,导致 IDE 报“Class already exists”。解决:打开 DSPack 设计期包的源文件,搜索 TMediaPlayer 注册代码,把那个组件从注册列表中移除或改名。具体位置在 Packages 里的 DSPack_DT.pas,找到 RegisterComponents 段,把 TDSTrackBar 之前的旧注册行注释掉。这里需要说明,DSPack 的 TMediaPlayer 封装其实没太大用处,移除后不影响视频播放功能。
4.4 现象:打开含中文或空格路径的文件时报“无效参数”
原因:DSPack 2.34 内部某些 Filter 使用 ANSI 字符串去解析文件名,导致中文路径被错误转码。Delphi 2010 默认字符串是 Unicode,但 DirectShow 的极少数老 Filter 只接收 ANSI 文件名。解决:把文件路径先转为短路径(8.3 格式),用 GetShortPathName API 拿到短路径后传给 RenderFile。具体实现:
function GetShortPath(const ALongPath: string): string; var Buffer: array[0..MAX_PATH] of Char; begin GetShortPathName(PChar(ALongPath), Buffer, MAX_PATH); Result := Buffer; end;这个方法不算优雅,但能绕过绝大多数老解码器的中文路径问题,是遇到 Delphi 2010 和 DSPack 时必备的“后悔药”。如果短路径也不奏效,另一个土办法是先把文件复制到 exe 同目录并重命名为英文名再播放,虽然丑,但在客户现场紧急演示时最有效。
4.5 现象:组件面板里看不到 DSPack 组件
原因:设计期包虽然编译成功,但没有真正安装进 IDE。安装的方式是 Component | Install Packages,然后在列表中 Add 一个 BPL 文件。如果直接双击 DPK 编译并安装,2010 的 IDE 有时不会自动刷新设计期信息。解决:手动注册并重启 IDE。步骤是:先关闭 IDE,把 DSPack_DT.bpl 放到一个固定目录,然后打开 IDE,Component | Install Packages | Add 选择该 BPL,之后重启 IDE。还有一个常见错位:运行时包和设计期包名字同样带 DSPack,IDE 可能认错了 BPL,所以最好给设计期包起一个独立名字如 DSPack_DT.bpl,避免和运行时包 DSPack_RT.bpl 同名。
另外,注册后如果还是看不到,检查 Windows 事件日志里是否有加载 BPL 失败,如果提示缺少运行时库,说明目标机器缺 VC Runtime,装上对应版本的 VC++ 运行库再重启 IDE。这一步比较隐蔽,因为 IDE 本身能启动,只是插件加载失败。
5. 进阶:用 SampleGrabber 抓帧截图,让播放器不只停留在“能放”
播放器跑通之后,最常见的延伸需求是“截一张当前画面”或者“把视频的一帧存成位图”。DSPack 的 TSampleGrabber 正好干这个。
5.1 在滤镜链里插入 SampleGrabber
TSampleGrabber 是一个中转滤镜,需要手动把它连接到播放链中。有两种做法:一种是完全自己构建滤镜图,用 TFilterGraph 的 AddFilter 和 ConnectFilters 手动连接;另一种是让 RenderFile 自动建图,然后用 TSampleGrabber 的 Filter 属性去枚举并插入。后者比较依赖运气,我一般用前一种更可控。手动构建的核心代码:
var SourceFilter: TFilter; Grabber: TSampleGrabber; begin SourceFilter := TFilter.Create(nil); SourceFilter.FilterGraph := FGraph; Grabber := TSampleGrabber.Create(nil); Grabber.FilterGraph := FGraph; // 设置抓帧媒体类型:以 RGB24 为例 Grabber.SetMediaTypeByString('RGB24'); FGraph.AddFilter(SourceFilter, nil); FGraph.AddFilter(Grabber, nil); // 连接源和抓帧器 FGraph.ConnectFilters(SourceFilter.OutputPins[0], Grabber.InputPin); end;注意这里的 SourceFilter 只是一个概念,实际文件源可能包含多个 Filter,所以纯手动模式更适合摄像头采集等相对简单的场景。对于播放文件,推荐先 RenderFile,然后通过枚举 Graph 里的 Filter 找到 SampleGrabber 并设置媒体类型,但这种办法在 2.34 版本里偶尔会失败,原因是没有正确调用 IGraphBuilder 的 AddFilter。如果遇到这种情况,建议退回手动模式。
5.2 抓帧回调与 BMP 保存
抓帧启用回调,要设置 Buffer 模式并实现 OnBuffer 事件。DSPack 的 TSampleGrabber 有一个属性叫 OnBuffer,事件里可以拿到 pSample 指针。保存 BMP 的完整例子:
procedure TForm1.SampleGrabber1Buffer(Sender: TObject; SampleTime: Double; var pSample: TSample); var Bitmap: TBitmap; Width, Height: Integer; begin Width := Grabber.Width; Height := Grabber.Height; Bitmap := TBitmap.Create; try Bitmap.Width := Width; Bitmap.Height := Height; Bitmap.PixelFormat := pf24bit; // 拷贝像素数据:SampleGrabber 默认返回的类型需要手动对齐行宽 SetDIBitsToDevice(Bitmap.Canvas.Handle, 0, 0, Width, Height, 0, 0, 0, Height, pSample.GetPointer, nil, DIB_RGB_COLORS); Bitmap.SaveToFile('frame.bmp'); finally Bitmap.Free; end; end;这个写法有几个点要注意:一是 SampleGrabber 输出的位深不一定和你设置的一致,必须先通过 Grabber.SetMediaTypeByString('RGB24') 锁定格式,否则 pSample 可能返回 YUY2 变换的缓冲,直接画到 Bitmap 上会花色;二是 SetDIBitsToDevice 的坐标和行宽对齐问题,如果图像宽度不是 4 的倍数,需要复制时调整 pitch;三是 OnBuffer 回调里不要做重活,保存 BMP 这种 IO 操作最好通过 Synchronize 丢到主线程,或者只存原始数据再统一处理。
我在实际工程里会把抓帧功能做成一个按钮触发,点击后设置一个 FNeedCapture 标志,在 OnBuffer 里判断并保存一次,之后清掉标志,否则每个回调都存图会把磁盘塞满。从那以后我每写一个播放器工程,都会把抓帧做进去,哪怕客户没提需求——因为一旦后面要“导出当前帧”或者“生成预览图”,就省得重构滤镜链了。希望帮到你。
本文还有配套的精品资源,点击获取