海康网络摄像机字符叠加OSD例程:BCB6.0老项目集成指南 简介面向视频监控开发者的海康网络高清摄像机字符叠加OSD例程演示在BCB6.0集成环境中调用海康SDK实现OSD文字信息显示。压缩包共56个文件约11.61MB主要包含动态库、静态链接库、头文件、C源码及工程配置另附可直接运行的测试程序方便对照观察实际叠加效果。细致阅读源码可以理清从SDK初始化、设备连接、字体颜色位置设置到实时刷新、错误处理的完整调用流程工程预留了界面扩展思路便于动态调整叠加文字与坐标贴近真实项目需求。目前已有7562人学习下载适合具备C基础、从事安防或视频监控开发的工程师作为入门参考也能为需要定制OSD功能的项目提供可直接借鉴的代码框架。1. 字符叠加OSD例程BCB6.0老项目的救火方案做安防上位机开发的人迟早会遇到“字符叠加OSD”这个需求把点位编号、时间戳、车牌号这些文字实时压在摄像头画面里。某开发者接手过一套用BCB6.0写的老安防客户端用户要求在十几台网络高清摄像机的预览画面上叠加大门编号和当前时间最初以为是视频编码层的活实际上设备SDK早就把OSD能力封装好了。这份海康网络高清摄像机字符叠加OSD例程核心就是把设备登录、OSD配置下发、中文编码转换串起来代码量不大但每一步都有讲究。适合两类人一类是要在BCB6.0老工程里补OSD功能的上位机开发者另一类是刚接触设备SDK、想快速跑通字符叠加的集成商。看完可以照着把OSD跑起来再往上改业务。2. 工程结构与SDK选型BCB6.0如何接住海康SDK先说为什么这个例程选择BCB6.0而不是C#或Java。很多安防客户端是十多年前用C Builder写起来的窗体、串口、报表模块都在里面。接一个OSD功能最大的约束不是语言本身而是要让新代码能和老工程一起编译、用同一套VCL窗体。2.1 设备SDK的组成头文件、导入库和动态库的分工海康设备网络SDK对外提供的是纯C接口动态库。在BCB6.0工程里SDK被拆成三个角色头文件负责声明函数和结构体导入库负责在链接期生成符号地址动态库在运行时真正完成网络通讯。写代码时include的是HCNetSDK.h编译时链接HCNetSDK.lib运行时把HCNetSDK.dll等一系列动态库放到exe同目录。这里给一张文件分工表文件作用使用时机HCNetSDK.h声明所有接口函数与结构体编译期includeHCNetSDK.lib导入库链接期定位函数入口链接期HCNetSDK.dll设备网络通讯主动态库运行时PlayCtrl.dll预览/回放解码库预览时hlog.dll / hconfig.dll日志与配置辅助模块运行时有一点容易误解字符叠加OSD功能并不依赖PlayCtrl.dll它走的是设备配置通道不是视频通道。所以例程里如果没做预览只需要HCNetSDK.dll加辅助模块就能跑。做了预览才需要PlayCtrl.dll。工程里常看到这两个动态库一起出现是因为老工程本身就要显示画面。2.2 例程的工程文件长什么样BCB6.0的工程和现代IDE不太一样。打开工程要看.bpr文件里面有编译器参数、源文件列表、链接库配置。例程目录里通常还带.dfm文件存放窗体布局.cpp和.h是逻辑代码。第一次编译前要把SDK路径配进Project Options的include目录和lib目录否则编译器会先报“找不到HCNetSDK.h”。新手最容易犯的错是直接双击.cpp打开源码然后按F9编译BCB会提示“Project has no .bpr file”。正确做法是打开整个工程文件让IDE按.bpr里的配置组织编译。老工程里如果已经有自定义的AppDir宏或公共头文件目录把SDK头文件目录追加进去即可不要新建工程否则VCL窗体框架要重搭一遍。2.3 搭建环境与首次编译编译和运行有两条线要同时准备好。第一条是编译期确认HCNetSDK.lib能被BCB6.0链接器接受。第二条是运行期把HCNetSDK.dll、hlog.dll、hconfig.dll复制到exe输出目录。我一般会把SDK目录固定放在工程下的三层结构里SDK/ include/HCNetSDK.h lib/HCNetSDK.lib dll/HCNetSDK.dll这样.bpr里的include路径写相对路径工程拷到别的机器不用改绝对路径。首次编译如果报“Unable to open file HCNetSDK.lib”先检查lib目录路径如果报“Illegal characters in path”多半是路径里带了中文或空格把SDK目录改到纯英文路径。运行阶段最常见的错误是启动时弹窗提示“找不到HCNetSDK.dll”这不是代码问题而是dll复制路径错了。BCB6默认输出在Debug目录或工程目录确认dll和exe在同一目录即可。调试时建议先把NET_DVR_SetConnectTime调短比如500毫秒这样设备不在线时登录失败能立刻返回不用等两秒超时。这里还要提醒一个和调用约定有关的知识点海康SDK导出的接口统一是__stdcall约定BCB6里封装函数指针时要记得加这个修饰。漏掉的话函数参数压栈顺序不对轻则返回错误重则直接崩溃而且这种问题非常难查。老工程里如果全景有宏定义确保没有把__stdcall重定义成别的。3. 核心代码拆解从设备登录到OSD字符下发完整链路OSD叠加的完整链路可以拆成四步初始化SDK、登录设备、构造叠加结构体、下发配置。下面按例程的实际调用来走一遍。3.1 设备初始化与登录拿到操作会话登录成功后返回的lUserID是全局会话句柄后续所有操作都挂在它下面。代码// 初始化SDK返回TRUE代表成功 NET_DVR_Init(); // 连接超时设为2秒失败后每10秒尝试重连 NET_DVR_SetConnectTime(2000, 1); NET_DVR_SetReconnect(10000, TRUE); NET_DVR_USER_LOGIN_INFO struLogin {0}; NET_DVR_DEVICEINFO_V40 struDevice {0}; struLogin.wPort 8000; strcpy(struLogin.sDeviceAddress, 192.168.1.64); strcpy(struLogin.sUserName, admin); strcpy(struLogin.sPassword, 12345678); LONG lUserID NET_DVR_Login_V40(struLogin, struDevice); if (lUserID -1) { ShowMessage(登录失败错误码 AnsiString(NET_DVR_GetLastError())); return; } // lUserID 是后续所有配置操作含OSD下发的会话句柄参数说明8000是海康设备的默认SDK端口和Web访问端口不一定相同改过端口的设备要在设备端确认实际值。sDeviceAddress只填IP不带协议头。NET_DVR_SetReconnect处理的是断线后的TCP会话重建它能自动恢复网络连接但不会自动重发OSD配置这一点到后面避坑章节会展开。登录成功之后还要留个心眼如果老工程里同时登录了多台设备每个lUserID要分开保存不能共用一个全局变量。OSD下发是按会话走的覆盖了别的设备的配置会让人抓狂。3.2 OSD叠加核心NET_DVR_SHOWSTRING_V30与配置下发叠加内容放在NET_DVR_SHOWSTRING_V30结构体里。一次最多可以放8条字符串每条字符串最长16字节含结尾0。下发指令用的是NET_DVR_SetDVRConfigNET_DVR_SHOWSTRING_V30 struShow {0}; NET_DVR_SHOWSTRING_INFO struInfo {0}; // 第0行叠加Gate-01 strcpy((char*)struInfo.sShowString, Gate-01); struInfo.wShowStringSize 7; struInfo.wShowStringTopLeftX 0; struInfo.wShowStringTopLeftY 0; struShow.dwSize sizeof(NET_DVR_SHOWSTRING_V30); struShow.wShowStringNum 1; struShow.wShowStringCharMode 0; struShow.struShowStringInfo[0] struInfo; BOOL bRet NET_DVR_SetDVRConfig( lUserID, NET_DVR_SET_SHOWSTRING, 0, struShow, sizeof(NET_DVR_SHOWSTRING_V30));几个容易忽略的点dwSize必须正确填充很多配置下发失败都是这里漏了第三个参数是通道号网络摄像机填0wShowStringSize单位是字节不是字符数中文字符串要按字节算。坐标系以画面的左上角为原点X向右Y向下具体取值范围和主码流分辨率有关建议先用(0,0)跑通再微调位置。下发是否成功不能只看返回值严格的做法是用NET_DVR_GET_SHOWSTRING把设备端配置回读出来逐字段比一次。有些固件版本对超长字符串会静默截断回读时能看到sShowString被切掉一半。这个回读动作在例程里通常注释掉了实际项目里建议打开。3.3 中文编码与坐标计算避免乱码和错位设备端OSD字符默认按GBK处理这一点在BCB6.0下反而有优势AnsiString的c_str()就是GBK字符集直接赋值给sShowString数组没有问题。如果工程是Unicode模式比如后来换到BCB2009以上就不能直接拷要先把UnicodeString转成GBK// UnicodeString - GBK再写入结构体 UnicodeString wsText L出口道闸; char szBuf[32]; WideCharToMultiByte( CP_ACP, 0, wsText.c_str(), -1, szBuf, sizeof(szBuf), NULL, NULL ); strncpy((char*)struInfo.sShowString, szBuf, 16); struInfo.wShowStringSize (WORD)strlen(szBuf);WideCharToMultiByte的CP_ACP表示当前系统ANSI代码页中文系统下就是GBK。如果不做这步转换直接把Unicode的缓冲区指针强转后写入设备端显示的就是乱码或空字符。代码里szBuf长度32sShowString上限16所以拷入前要确认字符串不超过15个字节否则会截断。坐标计算按主码流分辨率来。海康网络高清摄像机主码流常见1920x1080X、Y字段最大不要超过画面宽高超过后部分固件会自动裁剪部分会下发失败。多行叠加时我一般让每行Y递增30到40像素实际行高受设备字体大小影响先设一个固定值再在预览里观察struInfo.wShowStringTopLeftX 10; struInfo.wShowStringTopLeftY 10 nLine * 30; // nLine从0开始wShowStringCharMode这个字段例程里通常填0对应设备的默认字符集兼容性最好。部分设备支持1或2代表UTF-8或Unicode具体以设备能力集为准。真遇到了就一个个试试到不乱码为止。4. 避坑指南OSD叠加最常见的5个翻车现场写这一章是因为OSD功能本身不复杂但跑不通时定位问题非常费时间而且不少坑和BCB6.0的编译器特性搅在一起。4.1 登录与配置下发三个最容易翻车的点第一条exe启动就报找不到HCNetSDK.dll。现象是程序一跑起来直接弹错误框还没走到界面。原因几乎都是dll没复制全或者复制了32位dll到64位进程。解决确认exe和dll同目录确认SDK位数和exe编译位数一致。BCB6.0默认生成32位程序就去SDK的32位目录拿dll不要拿64位的。第二条登录返回-1NET_DVR_GetLastError()返回的常见错误码集中在7网络不通、23账号或密码错误这类。原因IP不对、SDK端口被改、账号密码错误。解决先在命令行ping设备IP再用设备厂商工具确认端口和账号最后把错误码对照SDK包里的错误码表定位。这种情况大概率是环境问题不是代码问题别一上来改代码。第三条NET_DVR_SetDVRConfig返回TRUE画面里却没有字符。这是OSD叠加最暧昧的失败接口认为成功了设备端没生效。原因通常是设备编码通道的字符叠加总开关没打开。解决登录设备Web管理页在“视频参数-字符叠加”或类似位置开启字符叠加开关再回到程序下发一次。部分老固件还需要设备重启后才能生效。4.2 编码与链接两个隐蔽问题第四条中文变成乱码。现象英文正常中文显示为“???”或方块。原因调用方把UTF-8或Unicode数据直接写入sShowString。解决统一转成GBK再填结构体。如果还是乱码检查代码里是否有中间层把AnsiString转成了char后丢失代码页信息。BCB6里AnsiString::c_str()返回的是char本身就是按当前代码页解释的字符串通常不需要额外转码怕的就是有人先转成了wchar_t*再拷回来。第五条BCB6.0链接HCNetSDK.lib报格式错误。现象编译到链接阶段报类似“Bad object file format”的提示。原因SDK自带导入库是VC的COFF格式BCB6.0的ilink32默认期望OMF格式两者不兼容。解决有两个一是用转换工具把lib转成OMF格式后再链接二是绕开lib运行时用LoadLibrary和GetProcAddress动态加载函数。第二种在BCB6.0下更省事typedef BOOL (__stdcall *pfnNetDVR_Init)(void); typedef LONG (__stdcall *pfnNetDVR_Login_V40)( NET_DVR_USER_LOGIN_INFO*, NET_DVR_DEVICEINFO_V40*); HMODULE hSdk LoadLibraryA(HCNetSDK.dll); pfnNetDVR_Init pNetDVR_Init (pfnNetDVR_Init)GetProcAddress(hSdk, NET_DVR_Init); pfnNetDVR_Login_V40 pLogin (pfnNetDVR_Login_V40)GetProcAddress(hSdk, NET_DVR_Login_V40);注意这段代码里NET_DVR_USER_LOGIN_INFO仍然来自头文件所以头文件要能正常编译通过。如果连头文件都在BCB6.0上报错把#include HCNetSDK.h用extern C包一层并统一加__stdcall调用约定。这样能绕开绝大多数兼容性问题代价是每个接口都要手写一个函数指针类型略繁琐但可控。5. 二次开发实战把静态OSD升级成业务信息看板例程跑通后最直接的改造是把字符叠加封装成类把结构体细节藏起来。我一般会在例程代码外再包一层TOSDOverlay暴露SetText和Apply两个方法class TOSDOverlay { public: TOSDOverlay(LONG lUserID) : m_lUserID(lUserID) { memset(m_struShow, 0, sizeof(m_struShow)); m_struShow.dwSize sizeof(NET_DVR_SHOWSTRING_V30); m_struShow.wShowStringNum 8; m_struShow.wShowStringCharMode 0; } bool SetText(int nLine, const AnsiString asText, int nX, int nY) { if (nLine 0 || nLine 7) return false; strncpy((char*)m_struShow.struShowStringInfo[nLine].sShowString, asText.c_str(), 16); m_struShow.struShowStringInfo[nLine].wShowStringSize asText.Length(); m_struShow.struShowStringInfo[nLine].wShowStringTopLeftX nX; m_struShow.struShowStringInfo[nLine].wShowStringTopLeftY nY; return true; } bool Apply() { return NET_DVR_SetDVRConfig(m_lUserID, NET_DVR_SET_SHOWSTRING, 0, m_struShow, sizeof(m_struShow)); } private: LONG m_lUserID; NET_DVR_SHOWSTRING_V30 m_struShow; };封装之后业务代码只需要管“第几行显示什么内容”。比如做一个1秒刷新一次的时钟叠加用BCB6.0的Timer组件在OnTimer里更新字符串并调用Applyvoid __fastcall TFormMain::Timer1Timer(TObject *Sender) { AnsiString asTime Now().FormatString(yyyy-mm-dd hh:nn:ss); m_osd.SetText(0, asTime, 10, 10); m_osd.Apply(); }下发的频率不能太快1秒一次已经算高频了。OSD配置每次都要走一遍网络请求到设备端生产环境建议改成内容变化时才下发或者用单独的线程限流否则设备CPU占用会明显升高。再往前走一步可以把OSD和业务联动刷卡开门时把卡号、姓名叠到对应画面上再抓图报警触发时叠加报警类型和时间再保存图片。这样事后翻录像时画面里的文字本身就是证据不用再对着时间轴对表。抓图用NET_DVR_CaptureJPEGPicture叠加逻辑复用上面的类就行抓图的路径和时间戳也可以一并拼进OSD字符串。最后说一个习惯。从前做完OSD集成我只会对着实时画面肉眼看字有没有出来。后来有一次画面正常但回放录像里没有字符才发现是没做回读校验。从那以后每次改完配置我都会强制用NET_DVR_GET_SHOWSTRING回读一次设备端配置确认下发内容和设备存储一致。这个习惯帮我少踩了很多阴晴不定的坑希望帮到你。本文还有配套的精品资源点击获取