简介DSPACK 2.3.4 是面向 Delphi 5/6/7 开发者的多媒体组件库基于 DirectShow 与 DirectX适用于视频播放、录制、摄像头控制、音频处理等场景可帮助中高级 Delphi 程序员快速构建音视频应用。资源包共 277 个文件压缩包 1.78MB主要包含 89 个 pas 源文件、40 个 dpr 工程文件、39 个 dfm 窗体文件、9 个 dpk 包文件以及 bat 脚本、lib 库、h 头文件等便于源码学习与二次编译。包内附示例项目与帮助文档覆盖摄像头采集、文件格式读写、DirectSound 混音等常见功能的完整实现适合希望深入理解 DirectShow 架构或需要直接复用组件的开发者。已有 502 人学习下载可作为 Delphi 多媒体开发的实用参考。1. DSPACK 2.3.4 还在被翻出来老 Delphi 项目接摄像头为什么要选它很多还在维护的老业务系统尤其是进销存、工控上位机这类界面停在 Delphi 7却要新增摄像头拍照、视频预览之类的需求。DSPACK 2.3.4 for Delphi 5/6/7 就是为这批老 IDE 准备的 DirectShow 封装库视频采集、预览、播放、抓帧都在一套组件里完成不用手写大量 COM 模板代码。我接手过几个类似项目头一两天基本都在跟陌生的 COM 接口搏斗后来换成 DSPACK工作量直接降了一个量级。下文按概念、安装、最小代码、坑、进阶的顺序讲适合想把摄像头功能快速补进老项目的开发者也适合还在犹豫要不要把一个老多媒体模块迁移到新方案的人。2. 先看懂组件模型DSPACK 把 DirectShow 的 Filter/Graph 变成了哪些 Delphi 类2.1 DirectShow 为什么难写DSPACK 解决了什么DirectShow 是 Windows 上的一套多媒体框架核心思想是“管道”把采集、解码、渲染拆成一个个 Filter再用 Filter Graph 把它们的 Pin 连接起来。这本身不复杂但直接调用时痛苦在接口风格上。Native 接口满眼是 IBaseFilter、IPin、IMediaControl全部继承自 IUnknown每个对象都要管引用计数一不小心就泄漏调试信息还少黑匣子一样。DSPACK 的做法是给这套 COM 世界套了一层 Delphi 外皮。常见做法是把 DirectShow 头文件转换成 Delphi 单元再提供一批 VCL 组件来管理 Filter Graph让老 Delphi 开发者可以用自己熟悉的方式干活代码里也写不了几条 COM 调用出问题时有组件属性和事件可查。它的价值不在“新”而在“全”老系统常见的摄像头捕获、视频文件播放、音视频同步、抓帧、录像基本上都有对应封装。这里要特别说明一个容易混淆的点DSPACK 是组件包但它的地基是 DirectShow9.pas 这类接口声明单元。你在工程里 uses 这个单元才能拿到 Windows SDK 里那一堆 COM 接口和 GUID 常量。组件负责把接口的创建、连接、状态切换收起来接口单元负责类型定义两者缺一不可。2.2 核心封装类与它们的职责边界下面几个是日常用到的核心类理解它们的职责后面读代码会顺很多DSPACK 类封装的重点接口主要职责常用属性/方法TFilterGraphIFilterGraph / IMediaControl / IMediaEvent管理整个 Filter 图的创建、运行、暂停、停止Active、RenderFile、ClearGraphTVideoWindowIVideoWindow / IBasicVideo控制视频画面输出到哪个窗体、怎么拉伸VideoWindow、Mode、AspectRatioTSysDevEnumICreateDevEnum / IMoniker枚举系统里的 DirectShow 设备摄像头、声卡Count、FiltersTSampleGrabberISampleGrabber从流里截取数据帧用于拍照或图像处理SampleGrabber、SetMediaTypeTFilterGraph 是整个组件的交通枢纽它对应的是 DirectShow 里那个 Graph Manager。你在设计器里拖一个 TFilterGraph、一个 TVideoWindow把 VideoWindow 的 FilterGraph 属性指过去后面激活图、播放文件、停止都是以 TFilterGraph 为主角。组件之间用属性关联而不是写代码去 Connect这是 DSPACK 一个很讨喜的设计。比如 TVideoWindow 只管“画面给我显示在哪个句柄上”它内部连接哪个渲染器不需要你操心TFilterGraph 只管“我这套图现在处于什么状态”它自己维护内部状态机和事件回调。2.3 老版本 Delphi 的边界为什么要单独挑 5/6/7 来说不是所有版本的 Delphi 都适合跑这套东西。DSPACK 2.3.4 那个年代的代码大量依赖旧的字符串处理、没有泛型的容器、老的运行时包机制放进新版 IDE 往往要改编译条件或补类型定义。真正长期维护这类项目的环境多半是 Delphi 7这也是标题里明确写 Delphi 5/6/7 的原因之一。在新版 Delphi 里硬编 DSPACK 不是不行但你会碰上两个问题一是部分 String 到 WideString 的隐式转换规则变了二是某些接口方法的参数定义与新版本 Windows SDK 不一致。如果你手上项目就是老代码老老实实用老 IDE 编译能省很多时间。拿到源码后我一般先做一个小自检确认 DirectShow 接口单元能正常编译和创建 COM 对象再往后走uses ComObj, DirectShow9; procedure CheckDirectShow; var Fg: IGraphBuilder; begin // 创建 Filter Graph Manager 的 COM 实例 OleCheck(CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, Fg)); // 能走到这里说明 COM 注册正常、接口单元完整 if Assigned(Fg) then ShowMessage(FilterGraph 创建成功); end;这段代码不依赖 DSPACK 组件只依赖 DirectShow9.pas用来排除最基础的“接口单元坏了”问题。CoCreateInstance 如果返回失败先查系统的 DirectShow 基础组件是否注册再查工程里是否混入了旧版接口文件。3. 装进 Delphi 5/6/7路径配置、组件注册与最小编译3.1 解压后的目录结构与 Library Path 配置下载到的 DSPACK 2.3.4 源码包里通常是几个大目录src 放的是核心单元examples 是示例工程docs 是说明文档。先把整个目录解压到一个稳定位置建议用不含空格的路径比如 D:\DSPACK_234省得后面 IDE 的搜索路径出幺蛾子。打开 Delphi 7 后在 IDE 主菜单走 Tools - Environment Options - Library把 src 目录加到 Library Path 里。这一步的意思是告诉编译器工程里 uses 了 DSPACK 相关单元时你到这个目录去找 .pas 文件。不需要拷贝任何文件到 Delphi 的 Lib 目录也不建议拷贝否则以后解压新版源码时会混版本。一个常见的做法是同时把 examples 里常用的几个示例工程目录也加进搜索路径方便打开示例直接编译但生产项目里我一般不加保持工程依赖干净。加完路径后打开任何一个示例工程编译一下先确认这套源码在你的 IDE 版本下能过编译。3.2 两种用法装组件面板还是纯代码创建用 DSPACK 有两种姿势不要混着用。第一种是把它安装成 IDE 组件打开 Component - Install Component选择 DSPACK 包源文件让 IDE 把这些类注册到组件面板上之后可以像拖 Button 一样把 FilterGraph、VideoWindow 拖到窗体上。适合快速做界面原型鼠标拖几下属性一设置预览窗口就出来了。第二种是不注册组件只在 uses 里引入单元运行时用代码创建对象。适合团队协作或需要控制组件生命周期的时候而且不会被 IDE 的已安装包污染。我倾向于第二种原因很简单老项目里加组件包要重新打开 IDE 的包列表遇到不同机器环境容易漏装而纯代码创建只要编译环境搭好工程拷贝到哪都能编。两种方式的取舍可以参考这个对比方式上手速度部署风险适用场景安装到组件面板快拖拽即可每台开发机都要装包容易漏个人快速原型纯代码创建稍慢需要写构造代码低只依赖搜索路径团队协作、长期维护3.3 最小编译通过一个空窗口也要验证单元可用装完路径别急着写业务先建立一个最小工程只留一个空 Form在 interface 的 uses 里加上 DirectShow9 和 DSPACK 两个单元跑一次编译。这个动作能提前暴露绝大多数环境问题避免写了一大段逻辑后才发现编译不过。unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DirectShow9, DSPACK; // DirectShow9 管接口DSPACK 管组件 type TForm1 class(TForm) private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} end.DirectShow9.pas 如果报错找不到说明搜索路径没生效DSPACK.pas 报错则说明源码目录里少了主单元。如果报的是“重复定义”或类型冲突多半是系统里有别的 DirectX 接口单元和 DSPACK 冲突把工程 Search Path 里其他无关路径清掉再试。4. 跑通三件事摄像头预览、截帧拍照、播放本地视频的最小代码4.1 摄像头预览与拍照枚举设备、加入 Filter、开始预览摄像头预览是 DSPACK 最常用的场景。目标是把第一个可用摄像头接到 Filter Graph 里并把画面输出到 Panel 上。下面的代码用 TSysDevEnum 枚举视频输入设备取第一个可用设备再让它和渲染器自动连接procedure TForm1.StartCapture; var DevEnum: TSysDevEnum; CamFilter: IBaseFilter; Graph: IFilterGraph; begin CoInitialize(nil); // DirectShow 是 COM 组件线程使用前必须先初始化 DevEnum : TSysDevEnum.Create(CLSID_VideoInputDeviceCategory); try if DevEnum.Count 0 then raise Exception.Create(没有找到摄像头); // 取第一个设备的 Filter 接口这里符号名按常见封装写以本地版本声明为准 CamFilter : DevEnum.Filters[0].Filter; finally DevEnum.Free; // 枚举器用完后释放Filter 接口仍然有效 end; Graph : FGraph.FilterGraph; // 拿到底层 IFilterGraph 接口 Graph.AddFilter(CamFilter, Cam); // 把设备源加进图 FVideoWindow.FilterGraph : FGraph; FVideoWindow.VideoWindow : Panel1.Handle; // 画面画到 Panel1 上 FVideoWindow.Mode : vmVMR; // 用 VMR 渲染比旧模式兼容性好 FVideoWindow.AspectRatio : True; // 保持原始宽高比不拉伸 FGraph.Active : True; // 激活整张图开始预览 end;代码里的 TSysDevEnum 用来枚举系统里的 DirectShow 设备Count 为 0 表示没有摄像头或驱动没装好AddFilter 把选中的设备源挂进 Filter GraphActive 设为 True 后DSPACK 会自动尝试把源的输出 Pin 和默认渲染器连接。如果你手头版本的 TSysDevEnum 没有暴露 Filter 属性可以用枚举器自带的 GetDevice 之类方法拿到 IMoniker再 BindToObject 出 IBaseFilter本质上是一回事。拍照这块一个简单可靠的办法是从视频窗口 DC 上抓图。因为画面已经渲染到 Panel 上了直接用 BitBlt 拷贝像素到 TBitmap再保存成 BMP 文件procedure TForm1.CaptureBitmap; var Bmp: TBitmap; DC: HDC; begin Bmp : TBitmap.Create; try Bmp.Width : Panel1.Width; Bmp.Height : Panel1.Height; DC : GetDC(Panel1.Handle); try // 把 Panel 上的画面拷到内存位图 BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, DC, 0, 0, SRCCOPY); finally ReleaseDC(Panel1.Handle, DC); end; Bmp.SaveToFile(D:\capture.bmp); finally Bmp.Free; end; end;这里有一个边界要知道BitBlt 抓的是屏幕上已经渲染出来的画面如果 Panel 被别的窗口盖住抓到的东西可能不完整。这不是 DSPACK 的问题是窗口抓图的通病。想要拿原始帧、不受遮挡影响就要用第 6 章的 SampleGrabber 方案那里拿到的才是真正的视频数据。4.2 播放本地视频RenderFile 一行代码接起文件源摄像头是采集场景播放文件是另一种常见需求。DSPACK 用 TFilterGraph.RenderFile 就能让系统自动匹配解码器和渲染器生成整条链路procedure TForm1.PlayFile(const AFileName: string); begin FGraph.Active : False; // 先停掉当前图 FGraph.ClearGraph; // 清空上一轮留下来的 Filter FGraph.RenderFile(AFileName); // 按文件后缀自动重建解码链路 FGraph.Active : True; end;RenderFile 的参数是文件路径常见的 AVI、MPEG、WMV 都能识别。每次播放前先 Active : False 再 ClearGraph这是很多人会漏掉的一步否则上一次播放留下的 Filter 和 Pin 连接会干扰新图。参数说明上要注意路径里的字符编码Delphi 7 的 string 是 ANSI而 RenderFile 内部要走 WideChar遇到中文目录名很容易损坏要么保证路径是纯英文要么传参前把字符串转成 WideString 再调底层接口。播放后如果想控制暂停、停止、拖动进度基本操作都在 TFilterGraph 上Active 控制运行/停止IMediaControl 提供暂停和继续IMediaPosition 或 IMediaSeeking 提供跳转。DSPACK 把这些接口暴露在 FilterGraph 属性里可以直接查接口后调用。4.3 影响画面效果的关键参数预览画面是否正常、会不会变形主要看几个参数参数取值作用注意VideoWindow窗口句柄指定视频画面输出到哪个控件必须在窗口创建完成后设置ModevmOldStyle / vmVMR选择渲染器实现老显卡或远程桌面兼容性问题时改用 vmOldStyleAspectRatioTrue / False是否保持视频原始宽高比设为 False 时会全屏拉伸人脸容易变形StretchTrue / False是否让视频填满窗口需要裁边显示时配合 AspectRatio 调整我一般推荐优先用 VMR 模式它在 XP 之后的系统上表现稳定支持硬件加速但如果你遇到了“画面出不来但是系统没报错”的玄学问题改回 vmOldStyle 往往立刻就好。AspectRatio 控制的是比例Stretch 控制的是填充两者要一起调只开一个容易出现黑边或者变形。5. 避坑编译失败、黑屏、解码器缺失和回调卡顿的排查记录5.1 编译报错找不到 DSPACK 单元现象新建工程编译时提示 File not found: DSPACK.dcu 或 DirectShow9.dcu。原因最常见的是 Library Path 没配置或者配置的路径里含空格、中文Delphi 7 的老搜索逻辑解析不了另一种是解压目录不对以为加的是 src实际加到了 examples 目录。解决把 DSPACK 源码里的所有 .pas 所在主目录检查一遍确认 DSPACK.pas 和 DirectShow9.pas 在同一层然后在 Library Path 里用绝对路径重新指向这一层。改完路径后一定要完全关闭并重开 Delphi让 IDE 重新加载环境配置否则不生效。5.2 窗口句柄上有黑屏不显示画面现象程序能跑起来不报错但 Panel 上一片黑或者画面闪烁后消失。原因最常见的是 TVideoWindow.FilterGraph 没有赋给 TFilterGraph另一个高发原因是 Form 还没显示完成就把 Panel1.Handle 交给 VideoWindow这时候窗口句柄还没创建视频没地方画还有个别老显卡对 VMR 支持差视频渲染不出来。解决先按顺序检查两点一是 FVideoWindow.FilterGraph : FGraph 有没有执行到二是 VideoWindow 的赋值放在 FormShow 之后。如果这两点都对了把 Mode 改成 vmOldStyle 再试一次。改模式不会影响数据流向只是换渲染方式多半在黑屏时能救回来。5.3 激活 FilterGraph 报错找不到可连接的解码器现象RenderFile 或 Active : True 时抛异常提示 Pin 无法连接或者没有可用的过滤器。原因DirectShow 本身没有真正的解码器它只是把任务分派给系统里注册的第三方 Filter。如果文件格式是 H.265、某些高清封装格式而系统里没有对应的 DirectShow 解码器连接就会失败。这种情况和 DSPACK 无关是系统解码环境缺组件。解决换用编码稳定的格式测试比如 AVI、MPEG2先确认链路本身能通。业务上一定要播高压缩格式的话装一套完整的解码包或者在自己的安装程序里带上必要的第三方 Decoder并注意商业授权问题不要默认系统一定自带这些能力。5.4 SampleGrabber 回调里做耗时处理视频卡成幻灯片现象加了抓帧逻辑后预览画面明显掉帧程序整体变卡。原因SampleGrabber 的回调跑在流线程上这个线程里不能做界面刷新、文件保存、耗时的图像算法。很多人在回调里直接放 Bmp.SaveToFile 和主线程 UI 操作一个回调几百毫秒流线程被占住后面帧排不上队。解决回调只做一件事把缓冲区数据复制到自己管理的内存里然后通过 PostMessage 或一个有优先级的线程通知主线程去处理。复制要快用 Move 或 CopyMemory一次拷贝一个完整帧。锁粒度尽量小不要在主线程处理期间还占着回调的临界区否则一样会卡。5.5 中文路径导致的播放和保存失败现象代码在 C 盘英文路径下跑得好好的文件移到带中文的目录下就报错或者保存 BMP 文件名乱码。原因Delphi 7 默认 AnsiString而 DirectShow 的接口方法用的是 WideStringAnsiString 转 WideString 时如果当前代码页对不上转出来的字符已经错了Windows 找不到真实文件。解决播放或保存前把所有路径统一处理成纯 ASCII是最省事的方案必须用中文路径的话用 WideString 显式声明接口参数不要依赖隐式转换。工程里也不要开“使用系统代码页动态转换”之类的旧选项那种行为在不同机器上结果不一致排查起来非常痛苦。6. 进阶技巧用 SampleGrabber 拿原始帧把 DSPACK 接进图像处理流程BitBlt 抓窗口适合“看一眼”的场合但如果你要做的是图像识别、条码读取、图像测量这类要认真算数据的活儿得走 SampleGrabber。它拦在数据链路上拿到的是一帧一帧的原始视频数据不受窗口遮挡影响。用 ISampleGrabber 操作标准做法是创建一个 SampleGrabber Filter设置想要的媒体类型然后把它加入到源 Filter 和渲染器之间。先写好回调对象type TFrameCallback class(TInterfacedObject, ISampleGrabberCB) private FBuffer: Pointer; FSize: Integer; public destructor Destroy; override; // 回调成功后拷贝到自己的缓冲 function SampleCB(SampleTime: Double; pSample: IMediaSample): HResult; stdcall; function BufferCB(SampleTime: Double; pBuffer: PByte; BufferLen: Integer): HResult; stdcall; end; destructor TFrameCallback.Destroy; begin if FBuffer nil then FreeMem(FBuffer); inherited Destroy; end; function TFrameCallback.BufferCB(SampleTime: Double; pBuffer: PByte; BufferLen: Integer): HResult; begin if FSize BufferLen then begin ReallocMem(FBuffer, BufferLen); FSize : BufferLen; end; // 只做内存拷贝不要在这里做算法和 UI Move(pBuffer^, FBuffer^, BufferLen); Result : S_OK; end;BufferCB 是最常用的回调pBuffer 指向一帧数据的起始地址BufferLen 是字节数。注意原样拷贝可能得到 YUY2、UYVY 这类 YUV 格式不是每个摄像头都直接输出 RGB24。做图像处理前先看设备输出的子类型必要时在 Grabber 上设置请求 RGB24让 DirectShow 自动加格式转换 Filter。回调里绝不碰 VCL这是硬规矩否则视频线程挂起主线程界面全卡死。把回调对象挂到 Grabber 上的代码骨架是这样var GrabberFilter: IBaseFilter; Grabber: ISampleGrabber; MT: TMediaType; CB: TFrameCallback; begin // 创建 SampleGrabber Filter 并查询接口 CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, GrabberFilter); Grabber : GrabberFilter as ISampleGrabber; // 请求视频流尽量让系统输出 RGB24 便于后续处理 FillChar(MT, SizeOf(TMediaType), 0); MT.majortype : MEDIATYPE_Video; MT.subtype : MEDIASUBTYPE_RGB24; MT.formattype : FORMAT_VideoInfo; Grabber.SetMediaType(MT); // 挂上回调 CB : TFrameCallback.Create; Grabber.SetCallback(CB, 0); // 1 表示用 SampleCB0 用 BufferCB // 再把 GrabberFilter 加入到 FilterGraph 的链路中 // 具体加法看你的 DSPACK 版本可能走 TFilterGraph 的方法 // 也可能要拿底层 IFilterGraph 接口后手动 AddFilter end;这里 CB 的引用计数要管住因为接口传给 COM 后TInterfacedObject 在没有 Delphi 变量持有期间可能被释放。我的习惯是让回调对象挂在主窗体或数据模块上随组件生命周期一起释放别让它在回调函数内部失去引用。老项目里把这条路打通等于给 Delphi 7 装了一个现代视觉处理的前置入口拿帧、转成位图、再丢给自写算法或第三方库整体结构都不会动。这也是我做这类改造时最推荐的落地路径先稳住预览和存储再逐步把帧接进业务逻辑改动面小回退也容易。希望这篇笔记能让你少走我当年绕过的弯路。本文还有配套的精品资源点击获取