尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
海康网络摄像机OSD字符叠加:BCB6.0调用SDK把文字写进编码前帧缓冲
简介这套海康网络高清摄像机字符叠加OSD例程面向视频监控系统开发工程师演示如何在BCB6.0集成环境下调用海康SDK在视频流上叠加时间、文字等OSD信息。包内共56个文件包含22个DLL动态库、7个LIB导入库、6个H头文件以及可执行exe和测试工程覆盖SDK调用所需的完整环境压缩包整体仅11.61MB轻量易部署。已有7562人学习下载。通过分析测试工程和示例代码开发者可以快速掌握设备连接、字符叠加参数设置、实时刷新等关键流程缩短二次开发周期适合有一定C基础并从事安防接入开发的读者。1. 海康网络高清摄像机OSD字符叠加一个BCB6.0例程把画面文字写进编码前的帧缓冲很多做安防对接的开发者第一次碰OSD时都默认这活儿可以在本地干——拿到视频流用GDI画上字符再推出去。真这么写过一版就会发现编码器、播放器和网络传输层会联手把你的“手写字幕”折腾得面目全非延迟还高得没法用。海康网络高清摄像机做的是设备端叠加文字在编码前就写进帧缓冲随主力码流一起进录像和预览改完配置立刻生效不需要动一帧图像数据。这份例程用BCB6.0把“登录设备→下发OSD→校验结果→断开”整条链路串起来了适合要往项目里补字符叠加功能、或者手头维护老BCB工程的开发者抄作业。2. 选型复盘ISAPI和NET_DVR两条路为什么例程最终落在SDK上2.1 网络高清摄像机的OSD到底是什么OSD是On-Screen Display的缩写在安防领域特指叠加在视频画面上的字符信息。海康网络高清摄像机内部有一套独立的显示叠加层位于图像传感器输出、视频编码器输入之间。也就是说字符不是在电脑端画上去的而是设备在把YUV数据送进编码器之前就把时间、通道名、自定义文本写到帧缓冲上了。这样做的好处很直接录像文件里带字符预览画面里带字符第三方通过RTSP拉流看到的也是带字符的画面全链路一致不会出现“回放有字、实时没字”的割裂感。我经手过的停车场设备对接里经常需要把“入口编号、车牌号、抓拍时间”叠到画面上。用本地渲染方案的时候视频流经过RTSP转发再进播放器渲染延迟一叠加字符就错位换了设备端OSD之后延迟完全不受影响。这个差异在实时性要求高的场景里是致命的。2.2 ISAPI与SDK的对比海康网络高清摄像机给开发者开了两条路。一条是ISAPI基于HTTP协议的restful接口把OSD配置看作设备资源用PUT请求提交一段XML就能改字符内容另一条是NET_DVR SDK走私有协议封装先用NET_DVR_Login_V30建立会话再用NET_DVR_SetDVRConfig或NET_DVR_PUT_OSD下发配置。对比项ISAPINET_DVR SDK调用方式HTTP请求直接拼URL和XML动态库链接函数调用登录状态无状态每次请求携带认证信息需要维护UserID会话批量配置循环发请求需自行处理并发循环调用SDK内部维护连接池字符编码XML里声明UTF-8或GBK结构体里填ANSI字节流依赖库只需要网络库需要HCNetSDK.dll及配套DLL工程集成适合跨平台、弱耦合场景适合C/C客户端、做多设备管理单台设备调试的时候ISAPI确实方便用浏览器就能改完看效果很多工程商现场就这么干。但要做到系统里需求往往不止“改一行字符”还要做设备发现、通道管理、异常重连、批量下发这时候SDK的封装价值就体现出来了。例程最终落在SDK上一个原因是BCB6.0工程里调用DLL接口比拼HTTP字符串顺手得多另一个原因是SDK里带的重连回调机制能省掉自己写心跳探测的功夫。2.3 为什么ONVIF不常用来做OSD叠加ONVIF协议在设备发现、云台控制、视频流参数上做得比较完整但字符叠加恰恰是各家实现差异最大的地方。有些型号在ONVIF规范里根本不暴露OSD配置节点有些型号即使暴露了字段意义也和私有SDK对不上。我在某图像处理Demo里试过用ONVIF的方式叠字符结果同一批设备里有两台完全不响应配置命令最后还是要回到私有接口。做资源选型时不要把ONVIF当成万能钥匙私有SDK在设备配套能力上始终是最全的。2.4 SDK版本与BCB6的库兼容问题海康SDK本身是用Visual Studio工具链构建的官方发布的hybrid文件夹里通常包含h头文件、lib导入库和DLL。BCB6.0用的是Borland的编译器链接器的目标文件格式和微软的不一样直接把VC版lib拿到BCB6工程里链接最常见的报错是“无法解析的外部符号”或者“TLS段错位”。血泪经验是先确认SDK包里有没有BCB子目录如果没有就去SDK的bin目录下拿DLL用borland提供的implib工具从DLL生成lib。这个过程看着玄学其实只要把DLL导出表解析出来生成的lib一样能用。例程源码包里已经做了这层处理下载后替换成你手里对应版本的DLL即可。3. 核心接口拆解NET_DVR_PUT_OSD与NET_DVR_SetDVRConfig的兼容调用3.1 登录是前提NET_DVR_Login_V30的正确打开方式所有SDK配置操作都建立在登录成功的基础上。海康网络高清摄像机SDK里登录入口是NET_DVR_Login_V30调用成功后返回一个非负数的UserID后续所有配置接口都靠这个UserID来定位设备会话。登录失败时返回-1具体原因用NET_DVR_GetLastError读取。#include vcl.h #include HCNetSDK.h #pragma hdrstop NET_DVR_DEVICEINFO_V30 struDeviceInfo; LONG lUserID -1; bool LoginDevice(const char* ip, int port, const char* user, const char* pwd) { memset(struDeviceInfo, 0, sizeof(struDeviceInfo)); lUserID NET_DVR_Login_V30( (char*)ip, port, (char*)user, (char*)pwd, struDeviceInfo); if (lUserID -1) { DWORD dwErr NET_DVR_GetLastError(); // 记录日志错误码23代表连接失败可能是IP不可达或防火墙拦截 return false; } return true; }这段代码做的事很直白先清空设备信息结构体然后调用登录接口把IP、端口、用户名、密码传进去同时带一个用来接收设备能力信息的结构体。参数说明里最容易被忽略的是port海康网络高清摄像机默认端口是8000不是80也不是554。554是RTSP流媒体端口用来拉视频流和SDK登录端口完全是两回事。struDeviceInfo里返回的是设备的通道数、设备类型等信息在后续批量操作时可以用来确认当前设备最大支持到哪个通道。3.2 用NET_DVR_PUT_OSD设置OSD信息较新版本的网络高清摄像机SDK提供了NET_DVR_PUT_OSD接口专门用于字符叠加使用起来比老的NET_DVR_SetDVRConfig更直接。核心数据结构是NET_DVR_OSD_INFO里面定义了字符内容、字符串长度、字体大小、颜色和显示位置。bool SetChannelOSD(DWORD dwChannel, AnsiString szText) { NET_DVR_OSD_INFO struOSDInfo; memset(struOSDInfo, 0, sizeof(struOSDInfo)); // 注意SDK要求ANSI编码BCB6默认AnsiString就是GBK直接传即可 strncpy((char*)struOSDInfo.byString, szText.c_str(), sizeof(struOSDInfo.byString) - 1); struOSDInfo.wStringSize strlen((char*)struOSDInfo.byString); struOSDInfo.byFontSize 1; // 字号档位不同型号支持范围不同 struOSDInfo.byFontColor 0x00FFFFFF; // 白色0x00RRGGBB排列 struOSDInfo.wStartX 100; // 横向坐标单位像素 struOSDInfo.wStartY 50; // 纵向坐标单位像素 struOSDInfo.byAlign 0; // 0左对齐1居中2右对齐 BOOL bRet NET_DVR_PUT_OSD(lUserID, dwChannel, struOSDInfo); if (!bRet) { DWORD dwErr NET_DVR_GetLastError(); // 错误码280表示命令不支持可能设备固件较老需要回退到SetDVRConfig return false; } return true; }这里有几个字段特别容易踩坑。wStringSize是字节数不是字符数一个中文在GBK编码下占两个字节如果只填字符串的显示长度设备端读到的字符串末尾是残缺的。byFontSize不是像素值是设备定义的档位同一台设备上档位0是最大字档位大了反而变小要先用设备能力集确认当前型号支持的档位范围和默认尺寸。位置坐标wStartX和wStartY的单位在大部分型号上是像素但也有部分型号按百分比计算判定的办法是设一个比较大的值看字符是否跑到画面外。我把这种不确定性叫黑匣子参数改一次拉流看一眼比反复读文档管用。3.3 老设备回退到NET_DVR_SetDVRConfig海康的产品线很长网络高清摄像机从早期型号到最新型号固件支持的OSD接口并不完全一致。有的设备只支持老的NET_DVR_SET_OSD命令对NET_DVR_PUT_OSD返回错误码280。兼容做法是优先尝试新接口失败后回退到老接口。NET_DVR_OSD_WORKPARAM struOsdParam; memset(struOsdParam, 0, sizeof(struOsdParam)); // 把字符内容填到第一个OSD槽位 strncpy((char*)struOsdParam.byDispOSD[0].byString, szText.c_str(), sizeof(struOsdParam.byDispOSD[0].byString) - 1); struOsdParam.byDispOSD[0].wStringSize strlen((char*)struOsdParam.byDispOSD[0].byString); BOOL bRet NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_OSD, dwChannel, struOsdParam, sizeof(struOsdParam));NET_DVR_OSD_WORKPARAM结构体里带有一个堆栈式数组byDispOSD里的多个槽位对应画面上的多行文字老接口里一个通道最多能配置的OSD条数由结构体数组大小决定。填参数时只需要把想用的槽位赋值其他槽位保持零填充。这个接口还处在海康SDK的长期兼容名单里我的经验是凡是报“指定设备不支持该配置”的情况换成这个老接口反而能通。3.4 错误码能告诉你什么SDK调试时最省时间的动作就是看错误码。NET_DVR_GetLastError返回的DWORD值对应HCNetSDK.h里定义好的宏。整理一份常用对照表放在手边。错误码含义典型处理23网络连接失败检查IP、端口、防火墙24内存分配失败检查结构体大小是否匹配SDK版本27设备不在线确认设备网络状态280设备不支持当前命令换用老接口或升级固件291参数填写错误检查字符串长度和字段取值范围错误码是SDK版本相关的不同版本的SDK可能对同一码值有细微差异以你手里的头文件定义为准。我一般在日志里同时打印错误码和调用接口名现场排查时能直接对应到哪一步翻车。4. BCB6.0工程落地把登录、叠加、登出做成按钮里能跑的完整流程4.1 工程准备目录、文件与链接配置BCB6.0是老牌RAD工具VCL组件拖拽起来很快但工程配置有一些必须提前处理干净的细节。工程文件本身不要放在中文路径下BCB6的IDE对中文路径支持极差编译到一半报找不到文件是常态。所有SDK相关文件按固定目录摆放文件放置位置作用HCNetSDK.h工程Include目录编译期必须的头文件HCNetSDK.lib工程Lib目录链接期使用的导入库HCNetSDK.dll可执行文件同目录运行期动态加载配套加密库和播放库可执行文件同目录登录和取流依赖的运行库链接配置上在Project→Options→Linker里把“Use dynamic RTL”关掉避免运行时依赖一堆Borland动态库发布到现场机器上。因为BCB6编译出来的程序是32位现场机器上的DLL也要确保是32位版本混用64位DLL会在加载时直接报“应用程序无法正常启动”。4.2 初始化SDK与退出清理SDK使用前要先调用NET_DVR_Init做全局初始化退出时调用NET_DVR_Cleanup释放资源。这两个调用放在主窗体的OnCreate和OnDestroy里最省心。void __fastcall TFormMain::FormCreate(TObject* Sender) { NET_DVR_Init(); NET_DVR_SetReconnectCallback(ReconnectProc, g_bNeedReconnect); lUserID -1; } void __fastcall TFormMain::FormDestroy(TObject* Sender) { if (lUserID ! -1) { NET_DVR_Logout(lUserID); lUserID -1; } NET_DVR_Cleanup(); }NET_DVR_SetReconnectCallback是SDK内部断线重连的通知入口设备掉线再恢复时回调会被触发。回调函数运行在SDK的内部线程上不能在里面直接调用界面刷新或者同步的配置下发接口否则会死锁。稳妥的做法是里面只置一个标志位主线程定时器检测到标志位后重新登录并补发OSD配置。4.3 完整按钮流程代码界面上放几个Edit框用来输入设备IP、端口、用户名、密码、通道号和叠加字符再放一个按钮触发整个流程。按钮事件里按顺序执行登录、叠加、失败提示。void __fastcall TFormMain::btnApplyOSDClick(TObject* Sender) { // 登录尚未建立或已经失效时先执行登录 if (lUserID -1) { if (!LoginDevice(edtIP-Text.c_str(), StrToInt(edtPort-Text), edtUser-Text.c_str(), edtPwd-Text.c_str())) { ShowMessage(登录失败错误码 IntToStr(NET_DVR_GetLastError())); return; } } DWORD dwChannel (DWORD)StrToInt(edtChannel-Text); AnsiString szOSD edtOSDText-Text; if (SetChannelOSD(dwChannel, szOSD)) { ShowMessage(OSD设置成功); } else { // 串行回退新接口失败后尝试老接口 NET_DVR_OSD_WORKPARAM struParam; memset(struParam, 0, sizeof(struParam)); strncpy((char*)struParam.byDispOSD[0].byString, szOSD.c_str(), sizeof(struParam.byDispOSD[0].byString) - 1); struParam.byDispOSD[0].wStringSize strlen((char*)struParam.byDispOSD[0].byString); BOOL bRet NET_DVR_SetDVRConfig(lUserID, NET_DVR_SET_OSD, dwChannel, struParam, sizeof(struParam)); if (!bRet) ShowMessage(OSD设置失败错误码 IntToStr(NET_DVR_GetLastError())); else ShowMessage(老接口OSD设置成功); } }这段流程的好处是把登录和叠加分离按钮多次点击时不会重复登出登录设备。我习惯把“新接口失败就自动走老接口”的逻辑做在一个公共函数里所有界面按钮复同一个入口避免散落各处不一致。4.4 多通道循环下发与断线重连网络高清摄像机有单通道也有多通道型号。批量叠加时不要一个for循环里猛发几十次请求设备端的配置接口处理能力有限连续高频调用容易触发设备忙错误。我一般每个通道之间加几百毫秒间隔同时优先按设备信息结构体里的通道数范围来控制循环次数。断线重连要处理得保守一点。回调只是通知真正恢复配置还得靠主线程主动做。定时器每隔5秒检查一次断线标志如果设备掉线就重新登录成功后把原先配置的字符内容重新下一遍。重连逻辑必须幂等重复登录时先判断lUserID是否为-1避免同一会话被多次创建。5. OSD叠加避坑手册五个常见问题与排查方法5.1 中文叠加上去变成“”现象代码里写的是“一号入口”设备画面上显示出来是两个问号。原因海康SDK的OSD结构体要求字符串按设备的本地代码页编码国内出货的设备基本是GBK。BCB6.0的AnsiString在中文系统下刚好就是GBK直接传没有问题。但如果你从配置文件或者网络接口拿到的字符串是UTF-8或者工程里不小心用了WideString转换成Unicode字节序列就和设备端预期对不上。解决统一字符入口。配置文件读进来先确认编码如果是UTF-8就转成GBK再填结构体。例程里所有输入框直接用AnsiString不做多余转换这样最不容易翻车。5.2 字符尾部被裁掉现象设了一行很长的自定义文字画面里显示到一半就断了。原因网络高清摄像机的OSD有字节长度上限。不同型号范围不太一样保守做法是单行不超过32字节中文不超过16个字符。超过上限后设备端默默截断SDK还不返回错误属于典型“看着成功、其实没成功”的坑。原因定位后解决方式是下发前先按字节做截断判断字符串超过上限就拆成两行分别放到不同的OSD槽位用wStartY拉开纵向间距。多行叠加时还要注意byDispOSD数组槽位和画面位置的关系第一行放在槽位0第二行放在槽位1设备按槽位顺序渲染。5.3 错误码23和错误码280现象登录时报错误码23或者调用新接口时报错误码280。错误码23的原因通常不在SDK本身而是设备IP不可达、端口没开放、或密码错误。有一种现场偶发情况是设备端同时被多个客户端登录达到最大会话数后老连接没释放也会报这个错误。解决方式是先用海康官方的客户端软件手动登录一次验证账号状态再检查防火墙和网段。错误码280则是设备固件不支持NET_DVR_PUT_OSD换老接口或者升级固件。5.4 配置成功后重启设备OSD消失现象OSD配置在设备上设置成功后设备断电重启画面上的字符没了用客户端看配置项却是空的。原因部分型号的OSD配置下发后停留在运行配置里没有被保存到持久化存储。这可能是SDK版本和固件版本不匹配导致的配置持久化失败也可能是因为设备在重启过程中配置文件被重置。解决下发OSD后主动调用配置保存接口或者重新下发一次配置并等待几秒再断开。从我这边遇到的型号看重下一遍之后大部分设备都能把OSD写进持久化存储。如果还不行检查设备管理页面里“开机恢复默认配置”选项是否被误开。5.5 编译链接和运行期的DLL问题现象BCB6工程编译时链接报错或者程序运行时提示找不到HCNetSDK.dll。链接报错的原因基本是把VC版LIB直接塞进了BCB工程。解决方式是确认LIB是否由Borland工具链生成如果不是用implib从DLL重新生成。运行时找不到DLL的原因则是放置路径不对BCB6编译出来的程序默认从当前目录加载DLL把DLL放在可执行文件同目录是最稳的。有些现场会把程序注册成Windows服务服务的工作目录不一定等于EXE所在目录这时要避免依赖相对路径启动时主动拼接DLL所在目录。6. 验证OSD没翻车用RTSP拉流确认字符真的写进了画面配置接口调用成功不等于字符真的出现在码流里。设备有些参数要等编码器生效有些要重新协商会话所以验证这一步不能省。最简单的方式是用VLC拉RTSP流。vlc.exe rtsp://admin:password192.168.1.64:554/h264/ch1/main/av_streamRTSP路径在不同型号上略有差异有的用Streaming/Channels/101有的用media/video1。登录设备后用SDK里的取流接口查一下当前主码流的相对路径或者直接看设备网页端的预览地址。拉流以后先看三件事字符内容是否完整、位置是否会遮挡关键区域、颜色在亮暗背景下是否清晰。字符只要写进了帧缓冲任何播放器拉流都能看到这一步没问题才算真正生效。动态刷新的OSD也值得做一层进阶验证。比如停车场系统要每分钟更新一次当前剩余车位我习惯在定时器里更新AnsiString后重新调用OSD设置接口。这里有一个经验刷新频率不要太快设备做配置持久化有写入次数限制每秒刷新会让设备提前老化。按分钟级别更新足够满足业务还能避免不必要的设备错误。验证通过后再交付给现场是我一直坚持的流程。从那以后我每次改完OSD例程都会强制自己拉一次RTSP流把画面里的字看清楚了再收工这个动作帮我拦截了不止一次“配置成功但画面没字”的现场返工。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Forms-Dynamics:用结构化记忆根治RAG碎片化,打造稳定Agent长时记忆

Forms-Dynamics:用结构化记忆根治RAG碎片化,打造稳定Agent长时记忆

做Agent应用,最容易被低估的部分不是模型,而是记忆系统。我接手过不少项目,模型明明很强,回答却像金鱼记忆——客户刚说过的预算、付款周期、关键决策人,过了三轮对话就开始漂移。最初我也迷信RAG,以为把历…

📅 2026/10/19 3:10:32
IDEA升级后无法启动?从闪退到卡死的完整排查指南

IDEA升级后无法启动?从闪退到卡死的完整排查指南

升级后双击图标没反应、闪退、卡在启动页,再要么就是转圈转了半天自动消失——这三类启动失败问题,几乎每个长期用 IDEA 的人都至少撞上过一次。尤其是你手上插件装了一堆、JVM 参数自定义过、配置目录还手动整理过的场景,升级之后出问题的概…

📅 2026/10/19 3:10:32
Android自定义SeekBar:图片进度条的精准绘制与触控实现

Android自定义SeekBar:图片进度条的精准绘制与触控实现

简介:本资源是一份面向Android中高级开发者的自定义SeekBar控件实战项目,聚焦UI个性化定制需求,解决系统默认SeekBar样式单一、难以融入品牌设计的问题。项目通过继承原生SeekBar并注入图片资源,实现背景图与进度条图的分离控制&a…

📅 2026/10/19 3:10:32
MORE NEWS

更多资讯

📰

开放式代码评审:从理念到落地的高效工程实践

1. 别把 Code Review 只当成“挑毛病”打开任何一个技术团队的招聘 JD,“Code Review”几乎都是必备关键词;但真到了日常开发里,能把它做好的团队其实不多。“open-code-review”这个项目标题,我第一眼看到的时候想的不是某个具体…

📰

SAP物料编号范围配置原理与实战避坑指南

简介:本资源是一份面向SAP ABAP开发人员、后勤模块顾问及ERP实施工程师的实操型配置指南,聚焦物料主数据编号范围的后台维护方法,解决多物料类型下编码唯一性管理、内外部编号段协同分配等典型实施难题。文档以ERP 6.0 EHP7版本为基准&#x…

📰

diagram-design:从规范到组件,搭建统一图表设计体系

写这套名为 diagram-design 的图表示范设计体系,起因其实特别具体:团队里的图表长什么样,基本取决于最后写代码那个人当时的心情。有人用渐变柱状图,有人用直角坐标系,有人把饼图切成了十块还配了双色阴影。客户见了问…

📰

数码管识别为何不用OCR?OpenCV+轻量CNN实战方案

简介:这是一套面向计算机视觉初学者与嵌入式图像识别实践者的Python数码管实时检测与识别系统源码,解决工业仪表、智能硬件中数字显示区域的自动定位与高鲁棒性识别问题。资源共192个文件,包含21个核心Python脚本(如digit_rgn_lea…

📰

Docker入门实战:从镜像容器到Compose编排,解决环境一致性问题

我第一次真正意识到必须认真学Docker基础,是在一次上线前夜。当时本地跑得好好的服务,部署到测试服务器上就开始报错:缺Python依赖、系统库版本不对、中间件连不上,同一份代码在两台机器上的表现完全不一样。后来排查了一晚上&…

📰

DeskcommCRM实战:销售自动化配置、数据清洗与团队落地全记录

刚接手销售运营那阵子,我最头疼的不是业绩上不去,而是根本说不清楚业绩到底是怎么来的。客户资料散落在销售个人微信、Excel 表格、企业网盘和邮箱附件里,谁跟到什么进度全凭记忆,主管催单只能逐个问。后来我们花了几周时间部署 D…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬