☰
Delphi 12.3视频采集指南:TVideoGrabber SDK安装、预览与帧率调优
2026/10/2 12:33:06 网站建设 项目流程

简介:Delphi 12.3环境下可用的TVideoGrabber视频处理控件SDK,版本为15.2.5.3,支持全平台,面向需要在Windows、macOS、Android、iOS等操作系统中集成视频采集、预览、录制与网络流播放功能的客户端开发者。压缩包内共942个文件,整体体积约101MB;除了组件核心的运行时包、编译单元、动态链接库与静态库之外,还配置了界面资源文件、窗体定义文件、工程源码示例、头文件以及帮助文档,可供使用Delphi或C++Builder进行原生开发时直接引用,也保留了用于快速验证的二进制模块。目前已有85人学习下载。该SDK特别适合需要接入USB摄像头、IP摄像机、HDMI采集卡或本地视频文件的桌面应用项目;借助完整的示例工程和跨平台库文件,开发者不必从底层视频驱动开始编写,只需设置控件属性、响应事件,即可实现视频画面显示、抓帧、录制保存和推流等操作,从而降低多平台适配的工作量。资源内置的文档与模块分层清晰,对中高级客户端工程师和需要二次封装视频能力的团队都具有参考价值。

1. 从一次“摄像头预览不到 30 帧”的选型说起:Delphi 12.3 下的 TVideoGrabber SDK 到底解决了什么

如果你在 Delphi 12.3 里接过摄像头,大概率经历过这种场面:原本用 DirectShow 自绘预览,把画面画到 TCanvas 上,分辨率一提到 1080p,帧率直接掉到 15 帧上下;想做截图还要维护一整套帧同步逻辑,代码越写越像黑匣子。标题里这个 Datastead 出的 TVideoGrabber SDK 控件,v15.2.5.3 是 2023 年 4 月发布的版本,后面跟着 All Platforms,意味着它面向 Windows、macOS、Android 等多平台,把采集、预览、录制、截图、RTSP 拉流这一整套能力收敛到单个控件里。它解决的问题很具体:让做视频采集的 Delphi 开发者不再自己折腾底层 DirectShow 封装,而是靠属性赋值和事件回调完成 80% 的日常需求。适合谁?正在做安防客户端、医疗影像、视频会议、直播推流工具的 Delphi 程序员,以及刚接触视频采集但不想从零写驱动的桌面端开发者。

2. 装进 Delphi 12.3:TVideoGrabber SDK 控件包的安装与库路径配置

很多人在这一步就翻车了。压缩包解压以后不先认文件,直接双击安装脚本,然后 IDE 报错找不到 bpl,或者干脆控件拖不上窗体。这一章把安装路径上的坑先排掉。

2.1 先认全压缩包:解压之后你会看到什么

一个视频采集 SDK 控件包和解普通 Delphi 库不一样,不能只关心那个最大的可执行文件。用压缩包自带的目录结构来说,常见会包含几类东西:运行时包(bpl)、编译接口文件(dcp)、源码单元(pas)、示例工程(samples)和帮助文档(docs)。其中 bpl 是 IDE 设计期要加载的,dcp 是编译期要引用的,pas 则是你后续想跟读源码或修改控件行为时才需要的东西。samples 目录往往价值最高——很多参数在帮助文件里写得不清楚,但示例工程一跑就明白用法。

我一般会先把整个包解压到一个没有空格的路径下,比如D:\Components\TVideoGrabber,而不是放在C:\Program Files这种带空格的位置。空格问题在 Delphi 的 Library path 和老旧安装脚本里非常常见,后面编译报错时排查起来最浪费时间。解压后在命令行里快速确认文件是否完整,这一步可以省掉后面大量无头绪的排查。

cd /d D:\Components\TVideoGrabber dir /b *.bpl *.dcp *.pas

这段命令的逻辑很简单:dir /b只列出文件名,不加多余信息,适合快速扫一眼关键文件是否都在。如果发现只有 exe 或者空目录,先别继续安装,八成是解压过程中安全软件把运行库拦截了,这种时候装出来的控件通常会在运行时报“类未注册”之类的错,而且很难定位到根因。

2.2 在 RAD Studio 12.3 里装控件的标准动作

安装路径正确后,接下来才是 IDE 层面的动作。我在 RAD Studio 12.3 下安装这类第三方控件的顺序基本固定,不会直接双击安装脚本,因为脚本经常写死了旧版 Delphi 的安装路径,在 12.3 上会装错位置。

第一件事是把包目录整体放到无空格路径,然后打开 IDE,进入Component > Install Packages,点Add,选择对应版本的 bpl 文件。如果包提供的是源码方式安装,那就用File > Open打开 .dpk 工程,在右侧 Project Manager 里先右键Compile,再右键Install。这里有个关键区别:Compile 只生成文件,Install 才把控件注册进 Tool Palette,只编译不安装是“装完拖不出来”的第一大原因。

uses TVideoGrabber; procedure TForm1.FormCreate(Sender: TObject); begin // 如果 IDE 能编译过这一行,说明控件单元已正确引入 // 此时设计期应该能在 Tool Palette 搜到 TVideoGrabber end;

这段代码看起来简单,实际是安装后的冒烟测试。uses TVideoGrabber能否编译通过,直接反映出源码路径和 dcp 引用是否正确。如果在这里报找不到单元,问题基本锁定在 Library path 没加或加错了路径,和代码本身无关。

安装完成后还要做一个动作:Tools > Options > Environment Options > Delphi Options > Library,把源码目录加进 Library path。这一步很多新手会漏,但它是打开示例工程时能不能编译的关键。示例工程引用的不只是 bpl,还有 pas 源码,IDE 需要靠 Library path 找到它们。路径配好后,我习惯把示例工程里最简单的那个先编译一遍,能跑起来才算安装真正结束。

2.3 装完却拖不出来控件:三个最常犯的安装疏漏

第一个疏漏就是前面说的只 Compile 没 Install。Delphi 的包机制里,Compile 只是在当前 IDE 会话里生成编译产物,不写进注册表;只有 Install 操作才会把控件注册到 Tool Palette。遇到拖不出来时,先回 Project Manager 看有没有执行 Install。

第二个疏漏是版本混装。机器上装了多个 Delphi 版本,比如老的 XE8 和新版 12.3 并存,bpl 文件如果用的是旧版编译器产物,安装在 12.3 里要么提示 “Package 版本不受支持”,要么安装了但拖到窗体时 IDE 直接崩溃。解决办法是把包源码在有问题的 IDE 版本里重新 Compile 一遍,生成对应版本的 bpl,再 Install。标题里的 v15.2.5.3 是 2023 年的版本,和 12.3 的 RAD Studio 基本处于同一代,主版本冲突的概率不大,但旧版升级上来的机器要格外注意。

第三个疏漏是路径里有中文或空格导致的隐性问题。Delphi 12.3 的 IDE 对 Unicode 路径支持已经很好了,但第三方控件的安装脚本未必跟上,常见表现是安装时一切正常,打开示例工程却报“Cannot open file”。这类问题最有迷惑性,因为错误信息跟路径完全对不上。我的习惯是从第一步就避开非 ASCII 路径,不在这上面赌运气。把这些点都排掉后,TVideoGrabber 控件出现在 Tool Palette 里、拖到窗体上能正常显示,安装阶段才算真的过关。

3. 跑通第一个采集合成:预览、视频源切换与画面抓取

安装只是热身,真正的开始是把摄像头画面在窗体上显示出来。这一章给出最小可运行代码,从设备枚举到帧回调,再到截图保存,最后落到视频源切换。全程用 Delphi 12.3 默认的 VCL 工程讲解,不涉及第三方显示控件。

3.1 最小初始化代码:先枚举设备再 Open,别用设备索引硬编码

刚接触 TVideoGrabber 时最容易犯的错误是直接从网上抄一段代码,把VideoDevice写成0之类的设备索引。这样做在开发者自己机器上可能没问题,但换到用户机器上,摄像头设备的枚举顺序完全可能不一样,索引 0 可能指向一个虚拟摄像头,也可能指向一个采集卡。我习惯的做法是启动时把设备列表读出来,让用户选,或者至少做一个下拉框。

procedure TForm1.FormCreate(Sender: TObject); var I: Integer; begin // 把系统当前可用的视频设备名全部列出来 for I := 0 to TVideoGrabber1.VideoInputList.Count - 1 do ComboBox1.Items.Add(TVideoGrabber1.VideoInputList[I]); end;

这段代码的逻辑很直接:VideoInputList返回当前系统里所有可用的视频输入设备,逐个添加进下拉框。这里要注意的是,枚举动作必须在Open之前做,否则列表可能为空。设备名在不同系统下会变(比如集成摄像头叫 “Integrated Camera”,USB 摄像头叫 “USB Camera #2”),所以设计期不要写死设备名,运行期从列表里取才是稳定做法。

设备名拿到之后,打开设备就简单了:

procedure TForm1.btnOpenClick(Sender: TObject); begin // 视频源指向摄像头,而不是本地文件或网络流 TVideoGrabber1.VideoSource := vsVideoSource; // 设备名从下拉框取,避免硬编码索引 TVideoGrabber1.VideoDevice := ComboBox1.Text; // 打开设备,启动画面采集 TVideoGrabber1.Open; end;

VideoSource是 TVideoGrabber 最核心的属性之一,它决定控件从哪拿画面。vsVideoSource表示本地摄像头,vsFile表示读取本地视频文件,vsNetworkStream对应网络流,这在后面做视频源切换时会用到。VideoDevice赋值设备名后,调用Open才真正开始采集。需要说明的是,Open是一个同步调用,设备打开失败时会有异常抛出,正式项目里要包一层 try/except,并给出友好的错误提示。

3.2 OnFrame 事件:采集线程与 UI 线程的同步边界

TVideoGrabber SDK 的帧回调机制是它最好用也最容易踩坑的地方。控件内部会启动采集线程,每一帧到达时触发OnFrame事件,事件参数里的Bitmap就是当前帧画面。很多新手第一次写回调时,直接在事件里把 Bitmap 赋给 TImage,然后发现界面卡顿甚至程序崩溃——问题出在线程边界上。

OnFrame里的 Bitmap 是控件内部复用的内存对象,这一帧处理完,下一帧到达时这块内存可能已经被改写。所以回调里绝对不能保存 Bitmap 的引用,只能深拷贝。同时,直接在这里操作 VCL 控件(比如 TImage)是不安全的,因为在不同线程里访问 UI 对象会造成不可预料的后果。

procedure TForm1.TVideoGrabber1Frame(Sender: TObject; Bitmap: TBitmap); var Bmp: TBitmap; begin // 先深拷贝,把当前帧复制到独立的内存里 Bmp := TBitmap.Create; Bmp.Assign(Bitmap); // 通过 TThread.Queue 切回主线程再更新 UI TThread.Queue(nil, procedure begin try Image1.Picture.Bitmap.Assign(Bmp); finally Bmp.Free; end; end); end;

这段代码解决了两个问题:第一,Assign把 Bitmap 的内存完整复制了一份,后续帧到达不会影响这份数据;第二,TThread.Queue把 UI 更新操作投递到主线程队列,避免跨线程访问 TImage。这里为什么不直接Synchronize?因为Synchronize是阻塞的,采集线程会等主线程处理完才继续,帧率一高,回调就被 UI 卡住,画面出现明显延迟。Queue是非阻塞的,主线程空闲时处理,忙时自然丢弃积累的更新请求,语义上更适合视频预览。

这段代码里要特别注意Bmp.Free的位置。我在finally里释放,保证即使主线程里的Assign出错也不会泄漏。深拷贝带来的额外开销在一路视频时完全可接受,但四路视频时就要考虑优化——这个后面第 4 章再展开。

3.3 从本地文件到网络流:视频源切换的三个合法姿势

TVideoGrabber 做视频源切换比很多原生实现要省事,因为VideoSource已经帮你分好了类。从摄像头切到本地视频文件,只需要改两个属性再重新Open:

procedure TForm1.btnPlayFileClick(Sender: TObject); begin // 先停止当前采集,否则切换会失败 TVideoGrabber1.Close; // 视频源类型改成本地文件 TVideoGrabber1.VideoSource := vsFile; TVideoGrabber1.VideoFile := 'D:\videos\test.mp4'; TVideoGrabber1.Open; end;

这段代码看着简单,但Close这一步很容易被忽略。如果不在切换前关闭当前设备,有些版本的 SDK 会直接忽略新设置,停留在旧画面;有些版本则会抛异常。在 Delphi 12.3 下,我的做法是把切换动作封装成一个公共方法,内部统一执行Close -> 设置属性 -> Open,避免每次手工操作丢步骤。

切换到网络流(比如 RTSP 拉流)时原理一样,把VideoSource设为vsNetworkStream,再在对应属性里填 URL。这里有一个实际经验:RTSP 地址往往对延迟敏感,如果预览画面停顿,先检查流地址是不是走了 UDP 端口被防火墙拦截,SDK 层面能设置的只是连接超时时间。这类问题在开发环境里很难复现,部署到客户内网才冒出来,属于典型的“环境相关”问题。

从文件或网络流切回摄像头时,同样走Close -> VideoSource := vsVideoSource -> VideoDevice 赋值 -> Open的流程。我在实际项目里还会保留一个“设备打开失败自动重试”的逻辑,因为 USB 摄像头设备被其他进程占用的概率并不低,而电视会议软件(比如钉钉、腾讯会议)在后台挂着时经常独占摄像头——这里只是举个例子,实际项目里我遇到过好几次这种占用冲突,重试通常能解决问题。

4. 参数调优:分辨率、帧率、像素格式与多路视频的平衡

画面能出来了,接下来就是你跟真实产品之间的差距:分辨率设多少合适、为什么设了 60 帧实际只有 30 帧、四路视频是否扛得住。这一章不讲玄学,直接给参数语义和取舍逻辑。

4.1 分辨率与帧率:设备支持列表才是唯一合法的参数来源

TVideoGrabber 控件里有VideoWidth、VideoHeight、FrameRate三个属性,表面上是“赋值就生效”,但实际行为更接近“请求值”——驱动会根据自己的能力协商一个最接近的结果。比如你设了 1920x1080,但摄像头硬件本身只支持 1280x720 下的 30 帧,那最终拿到的就是 720p 的画面,而且控件的实时状态里会显示实际协商结果。这解释了为什么很多人发现“我设了 1080p 但画面明显不是 1080p”。

正确做法是,在Open之前先读取设备支持的分辨率列表,从里面选一个,而不是拍脑袋写死数值。SDK 在枚举设备后通常会提供对应格式列表,15.2 版本里这个列表的名字跟早期版本略有差异,但思路一致:先枚举,再选择,最后设置。

procedure TForm1.ApplyResolution(AWidth, AHeight, AFPS: Integer); begin // 设置前先保证设备已关闭,避免参数在运行中失效 TVideoGrabber1.Close; TVideoGrabber1.VideoWidth := AWidth; TVideoGrabber1.VideoHeight := AHeight; TVideoGrabber1.FrameRate := AFPS; TVideoGrabber1.Open; end;

这段代码暴露了一个容易被忽略的细节:参数修改要在设备关闭状态下进行。如果设备处于打开状态,某些版本的 SDK 会忽略参数变化,你的赋值操作没有报错,但也不生效,看起来就像“参数没调对”。我先Close再赋值再Open,从流程上堵住这个坑。

帧率方面,我会在运行时区分两个概念:你设置的FrameRate是请求值,实际帧率可能受环境光照、USB 带宽、CPU 负载影响。USB 2.0 摄像头在 720p 下做到 30 帧已经很紧张,想稳定 60 帧通常需要换 USB 3.0 设备或者降低分辨率。这个不是控件能解决的,是硬件链路的天花板。

4.2 像素格式:同样 1080p,RGB 和 YUV 的带宽差接近一半

像素格式是最容易被忽略的性能参数。摄像头传感器输出的原始画面通常是 YUV 格式,但很多应用为了显示方便会转换成 RGB。TVideoGrabber 控件允许你控制这个转换链路的末端格式,选错了,带宽和 CPU 开销都上去了。

像素格式1080p 单帧数据量典型用途代价
RGB24约 6.2 MB图像处理、直接显示带宽最大,帧率最容易掉
YUY2约 4.1 MB通用预览、录制部分处理场景需要转 RGB
I420约 3.1 MB视频编码、推流、识别显示前转换开销

数据量计算不复杂:1920 乘以 1080 再乘以每像素字节数,RGB24 每像素 3 字节,YUY2 每像素 2 字节,I420 每像素 1.5 字节。三者在 1080p 下相差接近一半的带宽。如果你的应用下一步是做 OpenCV 识别,通常需要 RGB;如果只是预览加录制,用 YUV 格式能省下不少带宽,把帧率余量留给其他环节。

在 TVideoGrabber 里设置像素格式的入口不叫 PixelFormat,不同版本有不同命名,有的叫VideoFormat,有的叫PixelsFormat。我建议你在 15.2.5.3 这个版本的帮助文档里查一下确切名称,不要凭旧版本记忆去硬找。这里提醒一个排查方法:当你发现相同分辨率下 CPU 占用率异常偏高,先把像素格式改成 YUV 类型,如果 CPU 明显下降,说明之前花在格式转换上的开销不是白来的。

4.3 四路摄像头是常见需求:多实例与线程模型

做安防或监控类产品的读者,早晚会遇到四路甚至更多路视频同屏的需求。TVideoGrabber 的模型是一路视频对应一个控件实例,所以四路视频就是窗体上放四个 TVideoGrabber,或者动态创建四个实例。这个模型本身没什么问题,但要注意它背后是四套独立的采集线程。

procedure TForm1.AddSecondCamera(AParent: TWinControl); var Grabber2: TVideoGrabber; begin // 运行时动态创建第二个采集实例,不依赖设计期控件 Grabber2 := TVideoGrabber.Create(Self); Grabber2.Parent := AParent; Grabber2.Align := alClient; Grabber2.VideoSource := vsVideoSource; Grabber2.VideoDevice := 'USB Camera #2'; Grabber2.Open; end;

这段代码演示了动态创建实例的基本写法。关键在于Parent和Align,动态创建的控件如果不设置这两个属性,画面不会显示在预期区域。设计期摆放四个控件虽然直观,但布局调整和参数管理都麻烦,我一般只放一个模板控件,运行时复制参数动态创建。

四路同时跑时,OnFrame 回调频率是单路的四倍,如果每路回调里都做Bitmap.Assign深拷贝,内存分配压力会非常明显。我的做法是回调里只做“移交指针所有权”式的操作:把 Bitmap 存入队列,由独立处理线程消费。这样每一帧只发生一次拷贝,且拷贝发生在生产者侧,消费侧不会再复制。这个模式放在后面第 6 章给完整示例。回到线程模型上,四路采集会带来四个工作线程加一个 UI 线程的并发结构,Delphi 的 TThread.Queue 仍然是安全的,但要克制在主线程里做的操作量,避免 UI 渲染成为瓶颈。

5. 血泪避坑:TVideoGrabber 安装与运行中的五个高频问题

把常见问题一条条列出来,每条都按“现象 -> 原因 -> 解决”来写。这里面的经验来自真实项目里踩过的坑,不是从帮助文件里抄来的。

5.1 编译报错找不到 TVideoGrabber 单元

现象:新建工程后,在代码里uses TVideoGrabber,编译直接报F2613 Unit 'TVideoGrabber' not found。很多人第一反应是控件没安装,但重新 Install 后问题依旧。

原因:控件安装了,但 IDE 没有把源码目录加进 Library path。Delphi 的编译单元搜索路径和包安装是两个独立体系,安装包只解决设计期放置问题,编译期查找 pas/dcu 需要靠 Library path。

解决:打开Tools > Options > Environment Options > Delphi Options > Library,把包源码目录加进去,点Save后重新编译。注意 64 位平台要在同一页面里切到 64-bit Windows 平台再检查一遍路径,32 位和 64 位的路径配置是分开记忆的,只配了 32 位,64 位编译依旧找不到单元。

5.2 Open 后黑屏,但摄像头在其他软件里正常

现象:控件不出错,画面区域黑屏,但打开系统相机或者钉钉视频能看到画面。此时控件的状态显示设备已打开。

原因:摄像头被其他进程占用,或者操作系统的相机隐私设置关闭了桌面应用访问权限。Windows 10/11 都有“相机隐私”开关,某些软件会自动关闭非商店应用的相机访问权限,这种现象在 Windows 11 上尤其常见。

解决:先去系统设置里的隐私与安全性 -> 相机,确认“桌面应用可以访问相机”是开启状态。再检查有没有后台进程占用摄像头,常见的占用来源包括会议软件、远程协助工具等。TVideoGrabber 本身没有抢占机制,设备被占时Open不一定报错,但画面一直黑着,这时候只能靠排查外部因素。

5.3 设定 60 帧实际只有 30 帧,始终到不了标称值

现象:FrameRate设为 60,运行后实际帧率稳定在 30 左右,怎么调都上不去。

原因:多半是设备输出端就锁在 30 帧,或者 USB 2.0 带宽不足以支撑当前分辨率和像素格式下的 60 帧传输。USB 2.0 的极限有效带宽在 280 Mbps 左右,720p 的 RGB24 在 30 帧时已经要 330 Mbps,早就超标了,驱动会主动降帧。

解决:先看设备在不同分辨率下的能力表,很多摄像头最大只支持 60 帧是在 VGA 分辨率下。把分辨率降低到 640x480 再试 60 帧,如果能到,说明瓶颈在带宽,不在代码。如果降分辨率还是只能 30 帧,那就是摄像头的固件锁死了帧率,换设备才能解决。

5.4 程序退出时偶发崩溃,出错地址每次都不一样

现象:主界面关闭后程序闪退,调用栈指向的位置不固定,有时在释放代码里,有时干脆停在系统库内部。Debug 模式比 Release 模式更频繁。

原因:窗体销毁时,TVideoGrabber 控件和它的采集线程发生了竞争。采集线程还在回调 OnFrame,主线程已经把窗体对象释放了,回调里的代码成了对已释放内存的访问。这是典型的释放时序问题,崩溃点随机是它的标志性特征。

解决:在窗体关闭流程里加显式停止动作,先停止采集再释放对象。

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin // 先停采集线程,避免 OnFrame 在释放期间被触发 TVideoGrabber1.StopPreview; TVideoGrabber1.Close; end;

这段代码的关键是StopPreview和Close的先后顺序。StopPreview拉停采集线程,Close释放设备资源,两步都做完后,采集线程不会再执行 OnFrame,窗体再销毁控件才是安全的。如果你的代码里还有TThread.Queue投递的匿名方法排队未执行,Release 模式下这些方法可能在新窗体实例上运行,造成更难查的花屏问题。这个属于 Delphi 匿名方法队列的经典坑,我通常会在 OnFrame 里用一个布尔标识FClosing做开关,FormClose先置位再关闭设备,双保险。

5.5 从非官方渠道拿到的带 CRACK 标记的包:装上后各种诡异问题

现象:安装完成后,第一次编译能过,但运行起来控件属性设置总是不生效;或者 IDE 运行时弹出奇怪窗口;更常见的是杀毒软件把主程序和 bpl 一起隔离。于是你开始怀疑自己代码写错了,折腾一整天无果。

原因:像标题里那种带 CRACK 标记的包,内部文件已经被人为修改过,用来绕过注册校验。这类修改会影响控件对 SDK 本身的初始化逻辑,表现为间歇性失灵;另一方面,杀毒软件对这类修改过的二进制文件高度敏感,隔离操作会进一步破坏文件完整性。

解决:我的建议很明确,不要在这种包上继续排错,浪费时间也没有结果。Datastead 官方提供评估版本,能覆盖到学习和原型验证阶段。如果是公司项目,直接购买正式授权,视频采集 SDK 是基础组件,跟着项目走好几年,后期遇到摄像头兼容性问题需要官方支持时,一个正规授权能省下的时间和沟通成本远超授权费本身。我见过太多团队因为省了这笔预算,最后在兼容性排查上付出更大代价。验证包是否被修改的方法也简单:对比解压文件的签名信息或文件大小,和官方发布说明核对,任何一个文件对不上都直接丢弃。

6. 进阶技巧:把 OnFrame 的帧喂给图像识别线程,一份值得抄的队列代码

前面第 3 章给的 OnFrame 代码里,深拷贝后直接交给主线程更新 UI,这在单路预览场景下够用。但如果你要做人脸识别、车牌识别或者任何图像分析类功能,把图像识别放到主线程是灾难性的——识别一帧可能花 100 毫秒,UI 直接卡死。正确思路是生产者-消费者队列,采集线程当生产者,识别线程当消费者。这里给一份 Delphi 12.3 下可直接用的实现。

TThreadedQueue是 Delphi 自带的泛型线程安全队列,不需要额外引入第三方库。初始化时指定队列长度、压入超时和弹出超时,然后采集回调往里面放帧,识别线程从里面取:

// 在 Form1 的私有成员里声明: // FQueue: TThreadedQueue<TBitmap>; procedure TForm1.TVideoGrabber1Frame(Sender: TObject; Bitmap: TBitmap); var Bmp: TBitmap; begin // 深拷贝是必须的,Bitmap 是控件内部的复用对象 Bmp := TBitmap.Create; Bmp.Assign(Bitmap); // 队列满时 PushItem 返回超时,此时直接丢弃当前帧 if FQueue.PushItem(Bmp, 0) <> wrSignaled then Bmp.Free; end;

PushItem(Bmp, 0)是一个带超时的压入操作,超时设为 0 表示“满了就不等了”。这个丢弃策略是我调出来的关键:识别线程一旦慢了,保活机制应该优先保证采集线程不被阻塞,画面预览不卡,识别结果慢一两秒问题不大。反过来如果采集线程被阻塞,整个链路都会连锁卡顿。

识别线程这边的消费循环写法相对固定:

while not Terminated do begin Bmp := FQueue.PopItem(200); if Bmp <> nil then begin // 在这里做识别、编码或存盘 // 处理完必须释放,所有权已经转移到消费者这边 Bmp.Free; end; end;

PopItem(200)表示最多等 200 毫秒,超时返回时不阻塞主流程,这样线程退出时不用等队列消费完,响应也快。识别代码如果需要在处理过程中显示进度到 UI,用TThread.Queue投递结果,不要在识别线程里直接改界面。这套结构的核心收益是:OnFrame 回调里只做一次分配和一次拷贝,消费端独占处理,帧率波动被缓存吸收,识别线程的偶发慢帧不会反噬采集线程。我做视频采集端有一个习惯:每一路视频交付前都测一遍快速开关窗和长时间挂机,出现任何崩溃先怀疑释放时序。这份队列代码能解决大部分帧回调安全问题,剩下的一小部分,靠日志定位总比靠猜可靠。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询