简介:面向视频监控与安防集成开发者,本例程演示调用海康威视网络高清摄像机SDK,在BCB6.0中实现字符叠加OSD,可设置时间、文本位置与透明度,适合需二次开发或调试监控画面的初中级工程师。压缩包56个文件,约11.61MB,含HCNetSDK.dll等动态库、HCCore.lib等链接库、HCNetSDK.h等头文件,以及源码、可执行程序和运行配置;动态库与静态库负责设备接入,头文件提供API定义,源码工程可在C++ Builder中直接修改编译。已有7562人学习下载。通过编译运行“海康IP网络高清叠加测试”程序可直观看到OSD效果,结合工程代码还能快速掌握连接初始化、叠加属性设置、刷新更新与错误处理等完整调用流程,对监控平台及定制化视频显示的开发工作有直接参考价值。
1. 海康网络高清摄像机字符叠加OSD例程:BCB6.0 老代码里的新价值
搞安防集成的人,十有八九会遇到这样的需求:在摄像机画面上叠加一行文字,比如出入口的通道号、车间工位编号,或者门禁进出的时间戳。早年很多方案是在客户端解码后自己绘制文字,后来发现这样录像不保留、多路播放卡顿,最靠谱的办法还是在设备端做OSD叠加。最近我拆了一份海康网络高清摄像机字符叠加OSD例程,开发环境是 BCB6.0,代码不算新,但思路和踩坑点对现在做设备端字符叠加的工程依然有用,特别是老设备、工控机、遗留系统升级这类场景。这份例程覆盖了SDK初始化、设备登录、OSD参数配置和预览验证的全流程,适合刚接手海康网络高清摄像机二次开发、又想在老语言环境里快速落地的工程师。
2. 先从原理入手:OSD 叠加的两种路径与 SDK 环境准备
2.1 设备端 OSD 与客户端叠加的区别
字符叠加 OSD 在安防项目里通常有两条实现路径。第一条是把视频流拉到本地,在客户端程序里通过 DirectDraw 或者 GDI 把文字画到视频窗口上,这种方式灵活,实时性好,但录到 NVR 或者后端存储里的视频是没叠字的,前端如果挂了,叠加也就丢了。第二条是设备端 OSD,也就是把文字作为编码前的一条叠加信息,先写入视频流再编码输出,录像里自然带字。海康网络高清摄像机绝大部分型号都支持后者,通过 SDK 的配置接口下发叠加参数就行。
例程用的是设备端叠加,核心价值在于:不管接哪家的 NVR,也不管客户端软件是谁,只要视频流里有叠字,后续调看录像、公安取证、厂内追溯都拿得到证据链。代价是配置过程比客户端画字复杂一点,涉及结构体填充、设备能力集查询、坐标换算这些。特别是 BCB6.0 环境下,由于编译器对齐方式和 VC 不同,SDK 头文件里的结构体很容易踩坑,后面会专门讲。
2.2 BCB6.0 加载海康网络 SDK 的依赖与配置
BCB6.0 是 Borland C++ Builder 6.0,虽然年头长了,但国内很多工控上位机、遗留的安防管理软件还跑在它上面。加载海康 SDK 要做的第一件事是把头文件和导入库加进工程。SDK 包里通常有HCNetSDK.h、HCNetSDK.lib和一堆 DLL 文件。我一般会建一个公共目录,把 SDK 的include和lib单独拉出来,避免直接散落在系统目录里。工程设置里需要在 Project -> Options -> Directories/Conditionals 的 Include 路径和 Library 路径分别指过去,并在项目源文件里用
#include "HCNetSDK.h" #pragma comment(lib, "HCNetSDK.lib")引入。注意 BCB6.0 对#pragma comment(lib, ...)的解析不如 VC 顺畅,我是直接通过 ProcessLinker 配置把HCNetSDK.lib加进通配符里的,运行的时候把所有 DLL 烤到 exe 同目录。
2.3 例程的文件结构与主要模块
打开这份例程,工程里文件不多但结构清楚。主窗体单元Main.cpp负责按钮交互和登录逻辑,OSDThread.cpp是在我后来建议补上的线程模块,例程里默认只有单次写入。核心的SDKHandle.cpp封装了NET_DVR_Init、NET_DVR_Login_V40、NET_DVR_SetDVRConfig这几个调用。值得留意的是例程里有一个OSDCfg.h,里面定义了用于匹配设备能力的结构体版本,因为不同固件的设备对 OSD 支持的参数类型不一样。
我在复现的时候按自己的习惯给例程加了一层封装:把设备 IP、端口、用户名、密码,以及叠加文字、区域编号、坐标都抽成了配置文件。真实项目里,设备数量一多,直接在代码里改结构体参数会疯掉的。例程原版是把这些写死的,我看完整个逻辑后,更建议只把它当模板,不要直接用在多个设备的工业现场。
3. 把例程跑通:登录设备、写入 OSD 参数、预览验证
3.1 SDK 初始化和设备登录
海康网络 SDK 的初始化很简单,但顺序不能乱。所有操作之前必须先调NET_DVR_Init,结束再NET_DVR_Cleanup。登录用的是NET_DVR_Login_V40,注意这个函数在旧版 SDK 里是NET_DVR_Login_V30,两者结构体不同。例程里用的NET_DVR_Login_V40,接口参数里有一个NET_DVR_USER_LOGIN_INFO和一个返回的NET_DVR_DEVICEINFO_V40。切到 BCB6.0 下需要注意:NET_DVR_Init返回BOOL,但 BCB 的BOOL定义可能会和 SDK 里的冲突,我一般是强转成int或者直接比较。
// 初始化SDK if (!NET_DVR_Init()) { ShowMessage("SDK初始化失败"); return -1; } NET_DVR_USER_LOGIN_INFO loginInfo = {0}; NET_DVR_DEVICEINFO_V40 deviceInfo = {0}; loginInfo.wPort = 8000; strcpy(loginInfo.sDeviceAddress, "192.168.1.64"); strcpy(loginInfo.sUserName, "admin"); strcpy(loginInfo.sPassword, "your_password"); // 登录设备 LONG lUserID = NET_DVR_Login_V40(&loginInfo, &deviceInfo); if (lUserID < 0) { ShowMessage("登录失败,错误码: " + AnsiString(NET_DVR_GetLastError())); NET_DVR_Cleanup(); return -1; }这里说明一下:wPort建议固定成 8000,不要用设备 Web 页面的 80 端口,SDK 走的是私有协议端口。strcpy在 BCB6.0 里是没问题的,因为它默认的char是单字节,不涉及 Unicode 宏。登录成功后的lUserID是所有后续配置操作的凭证,后面每次调用NET_DVR_SetDVRConfig都要传它。
3.2 设置 OSD 叠加参数
OSD 参数的写入是整个例程的核心。设备端字符叠加在海康 SDK 里对应的是NET_DVR_SET_OSD这个配置命令,传入的结构体在不同设备系列里版本不同。老式标准定义是NET_DVR_OSD_CFG,新一点的高清网络摄像机用的往往是NET_DVR_OSD_CFG_V40,里面多了一些诸如文字颜色、背景、滚动等高级字段。例程里先查设备能力并用NET_DVR_GET_OSD读回默认值,再修改后写回,这个思路比直接填一个空结构体可靠得多。
NET_DVR_OSD_CFG_V40 osdCfg = {0}; osdCfg.dwSize = sizeof(osdCfg); // 先读取当前OSD配置 if (!NET_DVR_GetDVRConfig(lUserID, NET_DVR_GET_OSD, 0, &osdCfg, sizeof(osdCfg))) { ShowMessage("读取OSD失败,错误码: " + AnsiString(NET_DVR_GetLastError())); return; } // 修改叠加内容 memset(osdCfg.sOSDName, 0, sizeof(osdCfg.sOSDName)); strcpy((char*)osdCfg.sOSDName, "1号通道 2024-05-01 08:30:00"); // 启用OSD并设置显示位置 osdCfg.byEnable = 1; osdCfg.stOSDName.wShowString = 1; osdCfg.stOSDName.stSize.wSize = 24; osdCfg.stOSDName.stPosition.wX = 20; osdCfg.stOSDName.stPosition.wY = 20; // 写入配置 if (!NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_OSD, 0, &osdCfg, sizeof(osdCfg))) { ShowMessage("设置OSD失败,错误码: " + AnsiString(NET_DVR_GetLastError())); } else { ShowMessage("OSD配置成功"); }这段代码里NET_DVR_GET_OSD和NET_DVR_SET_OSD是成对出现的。先读取的意义在于:很多设备固件里保留着历史配置,直接覆盖空白结构体可能漏掉某些必须由设备填写的默认项,比如区域个数、字体路径。sOSDName是叠加的字符串,注意它不是宽字符,SDK 要求的是 ANSI 编码,也就是 GBK。stPosition.wX和wY是坐标,单位是像素,它们相对于整个画面的左上角。这里必须和设备当前图像分辨率匹配,否则叠出来的位置会和预期差很远。
3.3 开启预览并确认字符叠加生效
配置写入成功不代表就万事大吉,必须拉一路预览来看实际效果。例程里用NET_DVR_RealPlay直接在窗口上预览,这个接口会开启一个视频通道并返回句柄。如果只是验证叠加,也可以不建窗口,用NET_DVR_SetRealPlayCallBack拿回调数据,但那需要解码,麻烦。我通常的做法是直接在窗体上放一个 Panel,把窗口句柄传进去。
// 设置实时预览参数 NET_DVR_PREVIEWINFO previewInfo = {0}; previewInfo.lChannel = 1; previewInfo.dwStreamType = 0; // 主码流 previewInfo.dwLinkMode = 0; // TCP方式 previewInfo.hPlayWnd = pnlPreview->Handle; // BCB6.0面板句柄 previewInfo.byPreviewMode = 0; LONG lPlayHandle = NET_DVR_RealPlay_V40(lUserID, &previewInfo, NULL, NULL); if (lPlayHandle < 0) { ShowMessage("预览失败,错误码: " + AnsiString(NET_DVR_GetLastError())); }预览时注意码流类型。叠加参数是跟随编码通道的,如果你用主码流预览能看到叠字,切到子码流没显示,那是正常的,因为子码流分辨率低,很多设备默认只在这个码流上叠加主码流文字,需要分别配置。另外,设备端叠加是即时生效的,但如果你同时开着 IE 插件或者官方客户端,那边可能会有缓存,最好关掉其他控制端再验证。
4. OSD 叠加避坑记录:乱码、坐标越界、结构体对齐与 SDK 版本
4.1 中文乱码
现象:叠加上去的中文在预览画面里显示成“?”或者方块,英文和数字正常。原因:海康设备 OSD 字符串按 GBK/ANSI 编码解析,而 BCB6.0 里如果你用了String类型或者源文件保存成 UTF-8,strcpy后的字节流就不是 GBK,设备直接用 ASCII 错误解码中文,自然乱码。解决:把源文件编码改成 ANSI,叠加内容用AnsiString中转,避免出现 L"" 之类的宽字符常量。例程里代码用的是strcpy,但要确保右侧字符串不是 Unicode 转出来的。
4.2 叠加位置越界
现象:设置了坐标,但预览画面里文字跑到画面之外,或干脆消失。原因:结构体里的坐标wX/wY是像素值,而设备当前实际编码分辨率可能是 1280x720,你却按 1920x1080 写。尤其高清设备切换主码流分辨率后,坐标不会自动缩放。解决:先通过NET_DVR_GetDVRConfig读取设备当前分辨率,或者在登录后拿到deviceInfo.byResolution做换算。更稳妥的做法是用设备 Web 配置页确认当前分辨率,再按比例计算坐标。
4.3 结构体字节对齐问题
现象:设置 OSD 返回错误码 29(参数错误),读取配置也失败。原因:海康 SDK 头文件里的结构体是按微软编译器默认的对齐方式设计的,也就是 8 字节对齐。BCB6.0 在默认工程设置里,结构体对齐可能是 4 字节甚至 2 字节,导致结构体内部字段偏移量错位,SDK 读到的是错乱的数据。解决:在包含 SDK 头文件之前强制设置对齐字节。
#pragma pack(push, 1) #include "HCNetSDK.h" #pragma pack(pop)提示:这个技巧在 BCB6.0 下尤其重要,例程原版在头文件里做了#pragma pack,如果你自己新开工程引用 SDK,千万别漏。
4.4 老版本 SDK 不支持高清设备 OSD
现象:登录正常,但NET_DVR_SetDVRConfig返回“不支持该命令”或错误码 117。原因:设备是高像素网络摄像机,固件使用新的 OSD 结构体,而工程里链接的 SDK 还是老版本,只有NET_DVR_OSD_CFG,没有 V40 版本。解决:升级到对应设备型号的最新设备网络 SDK,并在代码里用NET_DVR_OSD_CFG_V40。例程里已经保留了 V40 分支,但如果你手里的 SDK 包太老,要先更新 SDK 再编译。
4.5 叠加内容带动态时间时刷新失败
现象:希望通过定时器每秒刷新一次屏幕上的时间,但更新几次后发现画面卡顿,甚至设备断流。原因:NET_DVR_SetDVRConfig每次调用都会触发设备重新编码流内叠加参数,频繁调用会加重设备负担。解决:把刷新周期放宽到 5 秒以上,或者直接使用设备自带的“时间叠加”功能,让设备走内置时钟,不要手动刷字符串。例程里没有内置时钟的演示,但你要做动态 OSD 前先看设备 Web 端是否支持“显示时间”,能省很多事。
5. 从例程升级:动态刷新 OSD、多行叠加与报警联动
5.1 定时刷新 OSD 文本
固定内容的 OSD 只覆盖了一半场景,工厂里经常要叠加实时产量、温度、车牌号这些变化数据。定时刷新最简单的做法是起一个TTimer,每隔 5 秒调用一次写入函数。注意不要在定时器里直接塞整段读取-修改-写入逻辑,最好把当前要叠加的文本放到一个公共变量里,定时器只负责将变量里的内容刷进设备。
void __fastcall TMainForm::Timer1Timer(TObject *Sender) { if (lUserID >= 0 && bOSDEnabled) { NET_DVR_OSD_CFG_V40 osdCfg; memset(&osdCfg, 0, sizeof(osdCfg)); // 用最新文本更新结构体 osdCfg.dwSize = sizeof(osdCfg); strcpy((char*)osdCfg.sOSDName, EditOSDText->Text.c_str()); osdCfg.byEnable = 1; // 写回设备 if (!NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_OSD, 0, &osdCfg, sizeof(osdCfg))) { // 错误计数,连续失败多次再提示 iFailCount++; if (iFailCount > 10) ShowMessage("OSD刷新连续失败: " + AnsiString(NET_DVR_GetLastError())); } else { iFailCount = 0; } } }这里的逻辑说明:读取旧配置已经被省略,因为例程里已经验证设备在写入时会保留未修改字段。但为了保险,我还是建议在前面保留一次NET_DVR_GetDVRConfig,尤其是修改坐标时。Timer1Timer里每次都会memset,这是为了清掉之前字符串残留。刷新频率别太激进,5 秒一次对大部分设备压力不大;如果你要 1 秒刷新一次,先在小规模设备上测试,同时观察设备 CPU 占用。
5.2 多行叠加与可配置参数
海康高端网络摄像机支持多个 OSD 区域,也就是屏幕上可以放多行文字,每行独立控制坐标、字体大小、颜色。NET_DVR_OSD_CFG_V40结构体里一般有多个NET_DVR_OSD_CFG子结构体,比如stOSDName、stOSDTime,以及新增的自定义区域。例程里只用了第一个区域。扩展的方法是把每个区域封装成一个独立配置项:
// 配置区域1 SetOSDArea(0, "左边通道", 20, 20, 24); // 配置区域2 SetOSDArea(1, "右边通道", 20, 60, 24);SetOSDArea内部需要根据区域索引填写不同的结构体字段。不同设备固件支持的区域内个数不一样,有的只有 4 个,有的支持 8 个,提前通过NET_DVR_GET_DEVICECFG看能力集。这里最容易翻车的是区域索引跟通道号搞混,代码里lChannel是登录后的通道,区域索引和它没关系,别把一个当另一个。
5.3 与报警事件联动的叠加触发
真实场景不光要定时刷,还要在报警时把报警类型叠加到画面里。海康 SDK 支持布放报警回调,在回调里获取报警信息,然后更新 OSD 文本。注意回调函数是在 SDK 自己的线程里执行的,不能在那里直接调用NET_DVR_SetDVRConfig,因为 SDK 内部可能有锁,容易死锁或延迟。我一般是在回调里把报警文本写到一个全局 buffer,然后触发定时器去消费。
void CALLBACK AlarmCallback(char *pAlarmInfo, DWORD dwLen, void *pUser) { // 只在回调中设置标志,不直接写设备 strcpy(g_cAlarmText, "Alarm: intruder detected"); g_bAlarmPending = true; }定时器里检查g_bAlarmPending,如果为真就更新 OSD,然后清标志。这样把设备写操作都放到主线程,避免回调上下文的环境问题。例程里没有报警模块,我是自己加了几十行代码完成联动的,效果是:报警触发后 5 秒内画面出现红色文字,持续直到报警复位。如果设备支持彩色 OSD,可以把文字颜色字段一起更新。
6. 一小时内复盘这套方案:验证清单与后续维护习惯
拿到例程之后,我建议你先别急着往现场部署,先在办公室搭一个带验证流程的环境。我的复盘顺序是这样:第一步确认 SDK 版本和设备的协议版本匹配,登录后调用NET_DVR_GetDVRConfig读取 OSD,能读回就说明结构体和设备固件对得上;第二步用官方客户端先手工在 Web 配置页里加上同一行文字,确认设备本身没问题,再跑例程代码;第三步把预览窗口打开,逐帧截图检查文字边缘是否清晰,尤其是中文笔画复杂的字符在小字号下有没有糊掉。
验证时有一个细节别忽略:字符叠加是否随录像文件保留。把预览录像录一分钟,再用播放器离线打开录像,看叠字是否仍然在,如果录像里没有而实时画面有,说明你配置到了预览叠加而没进编码通道,这跟码流类型配置有关。这个测试很重要,因为有些设备存在预览叠加和编码叠加独立开关。
维护习惯上的事我也多说一句:从那以后,我每次拿到类似海康网络高清摄像机字符叠加OSD例程,都会强制先把字符编码和结构体对齐两项检查掉,再动坐标和内容。BCB6.0 工程里把这些检查写成一个InitEnvironment()函数,在登录前调用,能省很多半夜现场调试的时间。这份例程里有一些参数是写死在代码里的,我建议你学完以后改成外部配置文件,把叠加文字、坐标、区域、刷新间隔全部分离出来,这样以后不同设备不同场景,不用重新编译。
希望帮到你。
本文还有配套的精品资源,点击获取