Delphi 2010老项目集成摄像头:DSPack 2.34实战指南
简介:DSPack 2.34 是面向 Delphi 2010 开发者的第三方组件包,专为需要网络通信、多线程、数据库访问与串口操作的 Windows 应用开发场景准备,适合已具备一定 Delphi 基础、希望借助现成组件缩短开发周期的中高级开发者。压缩包共 319 个文件,约 1.75MB,以 95 个 pas 单元、40 个 dpr 工程、39 个 dfm 窗体及 dpk、dproj、groupproj 等包与工程文件为主体,另含 res、dcu、bmp 等资源与编译产物,并附多个 bat 注册与清理脚本,便于按示例工程逐个导入 IDE 验证。该版本已在 Delphi 2010 环境下实测,可减少 Unicode 兼容性方面的踩坑成本。目前已有 329 人学习下载。读者可从中获得可直接编译运行的组件源码与示例工程,覆盖 TCP/UDP、HTTP、FTP 通信、线程管理与串口收发等典型用法,配合注册脚本快速完成组件安装,并借助示例窗体理解属性设置与事件处理方式,为服务器程序、实时监控、网络聊天等项目的搭建提供可复用的起点。
1. 老 Delphi 项目要接摄像头,为什么我最后翻出了 DSPack 2.34
手上有个维护了十来年的桌面系统,Delphi 2010 编译,界面全是 VCL,业务逻辑一动就牵一发动全身。上个月需求方要求加一路 USB 摄像头预览和本地录像,我第一反应是找现成的视频采集组件,结果试了几个新库,要么只认新版编译器,要么依赖一堆运行时,装到客户机上就翻车。折腾两天后,我把目光转回了 DSPack——这个在 Delphi 圈子里流传很久的 DirectShow 封装包。项目标题写得很直白:「delphi 2010 可用的 dspack 2.34(已测试)」,关键词就是 delphi 2010 dspack。它解决的不是什么高深问题,就是让老版本 Delphi 能稳定调用系统自带的 DirectShow 做音视频采集、预览和文件写入,适合还在维护 Delphi 7 到 2010 这段老工程、又不想重写整个多媒体层的从业者。下面我按自己实际拆包、编译、跑通的顺序讲,参数和坑都摆出来。
2. DSPack 2.34 的组成与 Delphi 2010 适配逻辑
2.1 这个包到底封装了什么
DSPack 的本质是一层 Pascal 接口,把 DirectShow 的 COM 接口、Filter Graph 管理器、常见 Filter(采集、压缩、渲染、文件写入)翻译成 Delphi 里能直接拖放和调用的类。核心单元大致分几块:DirectShow9负责基础接口和类型定义,DSPack提供FilterGraph、Filter、Pin这些高层封装,DSUtil放一些设备枚举和工具函数。它不自己实现编解码,采集和压缩全靠系统里注册的 DirectShow Filter,所以机器上装了什么编码器,它就能用什么,这一点既是灵活也是坑的来源。
2.34 这个版本号对应的是较早期的代码基线,能在 Delphi 2010 上编译通过,关键在于它处理了 Unicode 字符串迁移。Delphi 2009 之后string默认变成 UnicodeString,老代码里大量PChar、AnsiString和 DirectShow 的WideString接口对不上,直接编译会报一堆类型不匹配。这个版本里能看到不少PWideChar转换和WideString适配的痕迹,这也是标题强调「2010 可用」的实际含义,不是随便哪个老版本丢进 2010 都能过。
2.2 为什么选它而不是重写多媒体层
从选型上说,老工程的现实约束是编译器和部署环境都锁死了。重写多媒体层意味着引入新的依赖、重新做安装包、重新验证客户机兼容性,成本远高于接一个纯 Pascal 的封装包。DSPack 编译出来是普通单元,不额外带 DLL,运行时只依赖 Windows 自带的 DirectShow 组件,部署时少一层不确定性。另一个理由是它的 API 形态和 VCL 事件模型贴合,FilterGraph可以挂事件、可以同步等待状态切换,写预览和录像的流程比较直观。
代价也要说清楚:DirectShow 本身是较老的框架,在新系统上对某些高分辨率、高帧率设备的支持取决于驱动,DSPack 只是转发,不背这个锅。所以我的判断是——维护型老项目、采集需求以预览和常规录像为主,用它合适;要做低延迟推流、复杂滤镜链,还是得另找方案。
2.3 把包编译进 IDE 的步骤
拿到源码包后不要急着打开示例工程,先把运行库和设计期包分开编译。常见做法是先装运行库,再装设计期包,顺序反了会因为找不到已编译单元报错。下面是我实际走的命令和 IDE 操作混合流程,路径按自己解压位置替换。
# 假设源码解压到 D:\libs\dspack234 # 先确认目录结构,运行库通常在 src 下,设计期包在 packages 下 cd /d D:\libs\dspack234 dir /b # 期望看到 src、packages、demos 这类目录目录确认后,在 Delphi 2010 里打开packages下的运行库工程(通常是DSPack_D2010.dpk这类命名),先 Build 再 Install。Build 阶段如果报找不到单元,检查Tools > Options > Library里的搜索路径是否包含src目录。设计期包安装成功后,组件面板会出现 DSPack 页签,能看到FilterGraph、VideoWindow这些控件。
// 安装完成后,新建一个 VCL Forms 应用做最小验证 // 在窗体上放 FilterGraph1 和 VideoWindow1,然后写这段 procedure TForm1.FormCreate(Sender: TObject); begin // 把视频渲染窗口绑定到 VideoWindow 控件 FilterGraph1.VideoWindow := VideoWindow1; // 指定采集设备,这里用默认视频输入设备 FilterGraph1.Active := True; end;这段代码的逻辑是:VideoWindow属性把 FilterGraph 的视频渲染器输出重定向到指定控件的句柄,避免弹出独立窗口;Active := True触发整个 Graph 从停止态切到运行态,内部会依次完成设备打开、Pin 连接和渲染器初始化。参数上要注意,如果机器上有多个摄像头,默认设备未必是想要的那个,正式代码里应该先枚举设备再按名称或索引选择,而不是依赖默认值。
3. 用 FilterGraph 搭一条采集到录像的链路
3.1 设备枚举与采集 Filter 的创建
直接依赖默认设备在单摄像头机器上没问题,一到多设备环境就翻车。稳妥做法是先枚举系统里的视频输入设备,拿到设备名再创建对应的采集 Filter。DSPack 里通常用TSysDevEnum配合CLSID_VideoInputDeviceCategory来做这件事,枚举结果是一个列表,每项带显示名和 Moniker。
var DevEnum: TSysDevEnum; i: Integer; begin DevEnum := TSysDevEnum.Create(CLSID_VideoInputDeviceCategory); try // 列出所有视频输入设备,实际项目里可以填到 ComboBox for i := 0 to DevEnum.CountFilters - 1 do Memo1.Lines.Add(DevEnum.Filters[i].FriendlyName); // 按索引创建采集 Filter,索引要和界面上选中的一致 if DevEnum.CountFilters > 0 then CaptureFilter := DevEnum.GetFilter(0, False); finally DevEnum.Free; end; end;CountFilters返回当前注册的视频输入设备数量,GetFilter的第二个参数决定是否以独占方式打开设备,一般传False,否则别的程序占用时这里会直接失败。枚举出来的顺序和系统设备管理器里的顺序不一定一致,所以界面上最好把 FriendlyName 展示给用户选,而不是写死索引。这一步做完,采集端就有了一个可用的源 Filter,后面所有连接都从它的输出 Pin 出发。
3.2 预览链路:采集到视频渲染器
预览是最小可用链路,采集 Filter 的输出 Pin 直接连视频渲染器。DSPack 的FilterGraph提供了Render之类的便捷方法,但更可控的方式是手动找 Pin 再连接,这样出问题时能定位到具体是哪一步失败。
// 把采集 Filter 加入 Graph FilterGraph1.GraphBuilder.AddFilter(CaptureFilter, 'Capture'); // 找采集 Filter 的第一个输出 Pin OutPin := FilterGraph1.FindPin(CaptureFilter, 'Capture', PINDIR_OUTPUT, 0); // 创建视频渲染器并加入 RendererFilter := FilterGraph1.AddFilterByName('Video Renderer'); // 连接两个 Pin FilterGraph1.Connect(OutPin, RendererFilter.InputPin[0]);AddFilter的第二个参数是给 Filter 起的别名,后面查找 Pin 时用得上;FindPin的最后一个参数是同类 Pin 的序号,采集设备通常有预览和采集两个输出 Pin,序号选错会连到不合适的输出上。连接失败时先看Connect的返回值,再检查两个 Pin 的媒体类型是否兼容,常见原因是采集设备输出的是 YUY2 而渲染器只接受 RGB,这种时候需要中间插一个颜色转换 Filter。
3.3 录像链路:接 AVI Mux 与文件写入
录像要在预览之外再分一路,采集输出同时连渲染器和压缩/写入链路。DirectShow 里写 AVI 的标准组合是 AVI Mux 加 File Writer,压缩环节可选,不压缩就是原始帧直接落盘,文件会很大。
// 创建 AVI Mux 和 File Writer MuxFilter := FilterGraph1.AddFilterByName('AVI Mux'); WriterFilter := FilterGraph1.AddFilterByName('File Writer'); // 设置输出文件名 (WriterFilter as IFileSinkFilter).SetFileName(PWideChar(OutputFile), nil); // 采集输出连 Mux,Mux 输出连 Writer FilterGraph1.Connect(OutPin, MuxFilter.InputPin[0]); FilterGraph1.Connect(MuxFilter.OutputPin[0], WriterFilter.InputPin[0]);SetFileName这里必须传PWideChar,这是 Delphi 2010 Unicode 环境下最容易漏的细节,传PChar编译能过但运行时文件名可能乱码或写入失败。Mux 的输入 Pin 数量有限,多路视频要确认没超过上限。不接压缩 Filter 时,一小时 640x480 的原始 AVI 可能好几个 GB,实际项目里一般会在 Mux 前插一个编码 Filter,具体用哪个取决于客户机注册了哪些编码器,这一点没法在代码里写死,得在部署时确认。
3.4 状态切换与事件处理
Graph 搭好后不是直接Active := True就完事,采集设备打开需要时间,状态切换是异步的。DSPack 的FilterGraph支持等待状态切换完成,常见做法是切到暂停态等设备就绪,再切运行态,避免预览窗口黑屏几秒。
// 先切暂停,等待设备初始化完成 FilterGraph1.Pause; // 再切运行,开始出图 FilterGraph1.Run;如果直接Run,某些设备会在初始化期间丢帧,表现为预览前一两秒卡顿。Pause会阻塞到 Graph 进入暂停态,此时设备已打开、Pin 已连接,只是不推送数据,再Run就平滑很多。停止时顺序反过来,先Stop再释放 Filter,释放顺序错了会偶发访问冲突,这个在后面避坑章节细说。
4. 参数调优与常见翻车现场排查
4.1 分辨率、帧率与媒体类型协商
采集设备支持多种媒体类型,默认协商出来的未必是想要的分辨率。要指定参数,得在连接前找到采集输出 Pin 的IAMStreamConfig接口,枚举能力列表再设置。DSPack 对这层有封装,但具体调用还是绕不开媒体类型结构。
var StreamConfig: IAMStreamConfig; Capabilities: PVIDEO_STREAM_CONFIG_CAPS; MediaType: PAMMediaType; Count, Size, i: Integer; begin // 从采集输出 Pin 取流配置接口 OutPin.QueryInterface(IAMStreamConfig, StreamConfig); StreamConfig.GetNumberOfCapabilities(Count, Size); for i := 0 to Count - 1 do begin StreamConfig.GetStreamCaps(i, MediaType, Capabilities); // 按宽高筛选目标格式,这里以 1280x720 为例 if (MediaType.pbFormat.videoHeader.biWidth = 1280) and (MediaType.pbFormat.videoHeader.biHeight = 720) then begin StreamConfig.SetFormat(MediaType); Break; end; end; end;GetStreamCaps返回的媒体类型里带帧率和像素格式信息,筛选时除了宽高还要看帧率是否匹配,否则可能选到一个 720p 但只有 5 帧的格式。SetFormat必须在 Pin 连接之前调用,连上之后再改会失败。这一步是很多「预览正常但录像卡顿」问题的根源——预览链路协商的是低分辨率,录像链路却按高分辨率写盘,两边不一致。
4.2 常见问题:编译期与运行期的典型翻车
现象一:编译报Undeclared identifier或类型不匹配。原因多半是搜索路径没加全,或者混用了不同版本的单元。解决是把src及其子目录都加进 Library 路径,并确认没有同时引用旧版 DSPack 的单元。Delphi 2010 下还要注意DirectShow9里字符串相关声明是否已适配 Unicode,如果拿到的是更老的源码,需要手动改PChar为PWideChar。
现象二:组件装上了,但拖到窗体上就报类未注册。这是设计期包和运行库包版本不一致导致的,常见于先装了设计期包、后编译运行库。解决是先卸载设计期包,重新 Build 运行库,再 Install 设计期包,顺序不能乱。
现象三:预览黑屏,但 Graph 状态显示运行中。先查VideoWindow是否绑定到了可见控件,再查渲染器是否真的连上了。用 GraphEdit 这类工具把同样的链路手动搭一遍,能快速区分是代码问题还是设备/驱动问题。如果 GraphEdit 里也黑屏,基本可以判定是设备被占用或驱动异常,跟 DSPack 无关。
现象四:录像文件能生成但播放器打不开。多半是 File Writer 的文件名编码问题,或者 Mux 没收到完整的流就停止。解决是确认SetFileName传的是PWideChar,并且停止时先StopGraph 再释放 Writer,让 Mux 有机会写完文件尾。
现象五:程序退出时偶发访问冲突。释放顺序错误是主因。正确顺序是先Active := False停 Graph,再逐个释放自己创建的 Filter,最后释放FilterGraph。如果先 Free 了 Filter 再停 Graph,Graph 内部还持有接口引用,析构时就会踩到已释放内存。
提示:每次改完链路,先用 GraphEdit 手动验证一遍再回到代码,能省掉大量「到底是代码错还是环境错」的排查时间。
5. 把采集封装成可复用单元的几个技巧
链路跑通只是第一步,真正落地时要把设备枚举、链路搭建、状态切换、异常释放收进一个单元,界面层只调几个方法。我一般会定义一个采集控制类,内部持有FilterGraph、采集 Filter、Mux 和 Writer 的引用,对外暴露OpenDevice、StartPreview、StartRecord、StopRecord、Close这几个方法。这样换设备、改分辨率都只动一个地方,界面代码不用跟着改。
验证封装是否可靠,我习惯做三组回归:第一组是反复开关预览二十次,看有没有句柄泄漏和访问冲突;第二组是预览中途开始录像、录像中途停止预览,确认两路链路互不干扰;第三组是拔掉摄像头再插回,看程序是否能重新枚举到设备而不是卡死。第三组最容易暴露资源释放不干净的问题,因为设备消失时 Graph 会收到事件,如果事件处理里没做保护,就会在已失效的接口上继续调用。
还有一个具体技巧是给FilterGraph挂事件时,把事件处理和界面更新解耦。采集回调可能在非主线程触发,直接在回调里操作 VCL 控件会出问题,常见做法是回调里只置标志位或投递消息,由主线程去刷新界面。这个细节不写进代码里,跑起来大概率是偶发崩溃,而且很难复现,属于典型的血泪经验。
从那以后我每次接这类老框架的多媒体需求,都强制先用最小链路在目标机器上跑一遍枚举加预览,确认设备和驱动没问题,再往上叠录像和封装,绝不先写业务再回头调底层。希望帮到你。
本文还有配套的精品资源,点击获取