
简介面向Java开发者的视频监控SDK资源包聚焦大华SDK在Java平台下的二次开发适用于需要实现实时预览、录像回放、PTZ控制、报警事件处理等功能的Windows环境项目。压缩包共3663个文件大小17.12MB其中包含3548个class编译类、76个java源码、15个dll动态库以及jar、properties、bat脚本等class与java配合便于反查调用逻辑dll为底层设备通信支撑bat可辅助快速启动运行。已有1112人学习下载。资源内含NetSDKLib、AutoRegisterFrame、FaceRecognitionModule等模块示例覆盖设备注册、人脸识别、实时预览等关键流程并配有run_win64.bat等运行脚本可帮助开发者减少环境配置和接口调试时间快速搭建视频应用原型。1. 大华 Java SDK后端不想碰 C 也能把摄像头接进业务系统一个 Java 后端第一次被问到「能不能接大华摄像头」时通常都会去搜「大华 javasdk」搜到的结果却大多是 C/C 的接口文档和零散 Demo心里先凉一半。这类需求在智慧园区、门店监控、工厂视觉项目里非常常见设备已经装好了业务平台是 Java 写的你要做的不是去写插件而是把设备变成业务系统的一个数据源——登录、取流、抓图、接收报警全都走 SDK。大华官方 NetSDK 是 C 动态库Java 侧靠 JNA 封装就能调不需要 C 团队配合也不需要懂字节级协议。这篇按我实际接项目时的顺序把从加载库到实时预览、再到断线重连和抓图的完整路径拆给你顺带把最容易让人翻车的几个坑先指出来。2. 先搞懂 NetSDK 的「身世」JNA 封装和三条接入路线怎么选2.1 大华 NetSDK 的本质一个 C 动态库所有接入能力都在里面大华所有非 Web 端的接入能力最终都落在 NetSDK 这组动态库里。Windows 平台是 dhnetsdk.dllLinux 平台是 libdhnetsdk.so它对外暴露的是一整套以NET_DVR_开头、遵循 C 调用约定的函数。这套 SDK 覆盖的不只是实时视频预览还包括设备登录、通道管理、录像回放、报警订阅、云台控制、抓图、对讲等能力。你从设备配套光盘或大华开放平台拿到的 SDK 包里dll/so 以外通常还有头文件、说明 PDF 和示例代码其中头文件才是真正的地图Java 侧要复刻的就是这些头文件里的结构体和函数签名。Java 调这类 C 库只有两条路JNI 或 JNA。JNI 要自己写 C/C 桥接层和编译复杂度不在业务代码里而在每个平台的编译部署上JNA 则是在运行时通过反射解析结构体和函数指针Java 端直接写接口就行。项目侧我一般选 JNA理由很朴素接摄像头是业务需求不是性能竞赛JNA 能在半天内把链路跑通代价是首次加载时做一次类型映射这部分消耗对于秒级预览场景可以忽略。如果你发现自己需要应对几千路并发转码这种事那本身就应该考虑在 C 服务里做而不是用 Java 直接扛。NetSDK 对外提供两种能力形态一种是登录之后直接拉实时码流SDK 把 H.264/H.265 数据塞进回调另一种是走设备能力集查询通道信息、报警布防、云台操作。很多 Java 接入方拿到的官方示例只演示了登录和预览报警和回放要自己照着 PDF 啃。这意味着你首先要确认手里的 SDK 版本和设备的固件版本是否匹配——不同版本对结构体的定义不完全一致这也是后续一大堆「登录成功但字段是 0」问题的根源。2.2 三条路线对比NetSDK、RTSP 拉流、ONVIF 网关接大华设备常见的还有两条路线一是直接从设备的 RTSP 端口拉流二是走 ONVIF 标准协议做设备发现和能力接入。先说 RTSP它只能拿到码流本身最多再配合 RTCP 做一下延迟控制但你拿不到设备序列号、通道能力、报警事件、云台控制这些私有信息。如果业务只需要「在 Web 页面上看实时画面」RTSP 加播放器确实够用成本还低一旦要管理设备状态、做抓图、接报警RTSP 就完全不够了。ONVIF 是标准协议路线它能做设备发现、拉取媒体流地址、获取基本设备信息也支持 PTZ 控制。问题是很多厂商在标准之外扩展了私有能力比如大华的门禁事件、智能分析报警、行为分析参数这些私有接口各自为政。用 ONVIF 对接多个品牌时你会发现每个品牌的实现都有些偏移最后反而比直接用 SDK 更费劲。常见的做法是设备接入层直接用厂商 SDK把设备能力抽象成统一接口给上层业务封装一个设备网关。SDK 承载了设备的私有能力业务层不依赖具体品牌这样换设备厂商时只动接入层。还有一条更轻的路是 Web 插件。很多老项目还在用浏览器插件看实时视频大华摄像头插件也确实能直接预览。但它只能解决「看」解决不了回放管理、抓图存档、报警联动这些二次开发诉求。SDK 路线的优势是它跑在服务端和浏览器完全解耦前端只需要拿到后端转好的 HLS/FLV 流就行。这种「前端 sdk 解决不了的事交给后端 SDK」的架构是我处理这类项目时优先推荐的形态。2.3 拿到 SDK 包后的第一件事体检SDK 包解压之后先别急着写代码按下面三步检查一遍。第一步确认动态库和依赖库都齐全Windows 上 dhnetsdk.dll 可能还依赖 dhconfigsdk.dll、dhplay.dll 等Linux 上看 .so 文件用ldd检查依赖的 so 是否都在系统里缺失一个库会导致加载失败。第二步找到头文件确认 SDK 版本重点看NET_DVR_SDK_VERSION相关定义和NET_DVR_Login_V40是否存在老 SDK 包里的 Java Demo 经常还停留在NET_DVR_Login旧接口很多信息拿不全。第三步跑一下官方 Demo——如果官方有编译好的可执行文件先在开发机上登录一台真实设备确认设备固件和 SDK 能握手。像 TMA4.0 这类较新的设备型号固件如果太新而 SDK 太旧登录时会直接报版本不匹配。这一步能帮你把后续排查范围从「代码问题」缩到「SDK 与设备兼容问题」。体检的另一个重点是对齐位数。JVM 是 64 位就必须加载 64 位的 dll/so32 位的动态库在 64 位 JVM 里加载会直接报错。很多项目在 Windows 开发机上用 32 位 JDK 跑通了部署到 64 位 Linux 服务器上又挂十有八九是库位数不匹配。这类问题排查起来非常耗时间一开始就确认 JDK 位数和 SDK 库位数一致能省掉后面大把调试时间。3. 跑通第一条链路从加载库到实时预览的完整代码3.1 环境准备JNA 依赖和动态库路径别和 JDK 的 PATH 搞混Java 侧依赖只需要一个 JNA 包Maven 坐标就是net.java.dev.jna:jna版本选 5.x 即可。把大华 SDK 的 dll/so 放好之后JNA 并不会从CLASSPATH里找它而是从jna.library.path、java.library.path以及系统的LD_LIBRARY_PATH/PATH里找。很多人在这里和「java 环境变量配置」搞混——配 JDK 是设置 PATH 指向 bin 目录而 JNA 加载的是动态库存放的目录。# Windows 开发机启动参数 java -Djna.library.pathD:\dahua\sdk\libs -Djava.library.pathD:\dahua\sdk\libs -jar app.jar # Linux 部署时建议放在 /opt/dahua/sdk 目录 java -Djna.library.path/opt/dahua/sdk -jar app.jar逻辑说明jna.library.path是 JNA 优先查找的路径java.library.path是 JVM 加载 native 库的默认路径两个都设一遍可避免某些 JNA 版本解析不一致的问题。实际项目里我一般还会在 Java 代码里再兜底一次用System.setProperty(jna.library.path, sdkPath)抢在第一次Native.load之前执行这样部署时不用改启动脚本。参数说明sdk 路径建议用绝对路径不要依赖相对路径因为后端服务通常以 daemon 方式启动工作目录不固定。3.2 映射登录结构体和设备信息结构体字段顺序就是内存布局JNA 的Structure子类必须和 C 头文件里的结构体逐个字段对应字段顺序就是内存布局。大华登录用的是NET_DVR_USER_LOGIN_INFO设备信息用NET_DVR_DEVICEINFO_V40。以下代码只列出最常用的字段其余保留字段必须按头文件用byte[]占位补齐。import com.sun.jna.Structure; import com.sun.jna.ptr.ByteByReference; import java.util.Arrays; import java.util.List; public class DahuaStruct { // 登录信息结构体对应 NET_DVR_USER_LOGIN_INFO public static class NET_DVR_USER_LOGIN_INFO extends Structure { public byte[] sDeviceAddress new byte[129]; // 设备 IP字符串 public byte byLoginMode; // 登录方式0网络 public byte[] byRes1 new byte[3]; // 保留字段必须占位 public short wPort; // 设备端口默认 8000 public byte[] sUserName new byte[64]; // 用户名 public byte[] sPassword new byte[64]; // 密码 public byte bUseTransport; // 是否使用传输层加密 public byte bLoginBySSO; // 是否单点登录 public byte bProxyDirect; // 是否走代理 public byte bUseCert; // 是否使用证书 // 后续保留字段按头文件继续补齐 // 每个字段的偏移都必须和 C 结构体一致 public NET_DVR_USER_LOGIN_INFO() { super(); setAutoSynch(true); } Override protected ListString getFieldOrder() { return Arrays.asList(sDeviceAddress, byLoginMode, byRes1, wPort, sUserName, sPassword, bUseTransport, bLoginBySSO, bProxyDirect, bUseCert); } } // 设备信息结构体对应 NET_DVR_DEVICEINFO_V40 public static class NET_DVR_DEVICEINFO_V40 extends Structure { public byte[] sSerialNumber new byte[48]; // 设备序列号 public byte byAlarmInPortNum; // 报警输入端口数 public byte byAlarmOutPortNum; // 报警输出端口数 public byte byDiskNum; // 硬盘数量 public byte byDVRType; // 设备类型 public byte byChanNum; // 模拟通道数 public byte byStartChan; // 起始通道号 public byte byAudioChanNum; // 音频通道数 // 后续字段按头文件继续补齐 Override protected ListString getFieldOrder() { return Arrays.asList(sSerialNumber, byAlarmInPortNum, byAlarmOutPortNum, byDiskNum, byDVRType, byChanNum, byStartChan, byAudioChanNum); } } }逻辑说明getFieldOrder()必须返回字段声明顺序JNA 依赖它来计算每个字段在内存中的偏移。这里最容易出的问题是把wPort声明成int——C 里的WORD是 2 字节无符号整数Java 里对应short用int会让后面所有字段的偏移全部错位。参数说明sDeviceAddress、sUserName、sPassword这三个字节数组不能声明成StringSDK 会直接往缓冲区里写值必须用定长byte[]。现场填登录信息时要把 Java 字符串转成 GBK 字节数组再拷贝进去大华中文设备名和用户名默认走 GBK 编码。3.3 初始化、登录、登出最小可用骨架设备登录是整个 SDK 调用的入口。先初始化 SDK 运行环境再设置连接超时然后调用NET_DVR_Login_V40。登录成功后拿到一个lUserID整型句柄后续预览、抓图、报警订阅都依赖它用完后必须登出否则设备侧会保留无效会话。import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.ptr.IntByReference; public class DahuaSDK { // 定义需要调用的 SDK 接口方法签名对应动态库导出函数 public interface IDahuaSDK extends com.sun.jna.Library { IDahuaSDK INSTANCE Native.load(dhnetsdk, IDahuaSDK.class); boolean NET_DVR_Init(); void NET_DVR_Cleanup(); boolean NET_DVR_SetConnectTime(int dwWaitTime, int dwTryTimes); int NET_DVR_Login_V40(DahuaStruct.NET_DVR_USER_LOGIN_INFO.ByReference loginInfo, DahuaStruct.NET_DVR_DEVICEINFO_V40 deviceInfo); boolean NET_DVR_Logout(int lUserID); } public static void main(String[] args) { IDahuaSDK sdk IDahuaSDK.INSTANCE; boolean initOk sdk.NET_DVR_Init(); if (!initOk) { System.err.println(SDK 初始化失败); return; } sdk.NET_DVR_SetConnectTime(3000, 1); DahuaStruct.NET_DVR_USER_LOGIN_INFO loginInfo new DahuaStruct.NET_DVR_USER_LOGIN_INFO(); loginInfo.sDeviceAddress 192.168.1.64.getBytes(GBK); loginInfo.wPort 8000; loginInfo.sUserName admin.getBytes(GBK); loginInfo.sPassword password.getBytes(GBK); DahuaStruct.NET_DVR_DEVICEINFO_V40 deviceInfo new DahuaStruct.NET_DVR_DEVICEINFO_V40(); int userId sdk.NET_DVR_Login_V40(loginInfo, deviceInfo); if (userId 0) { System.err.println(登录失败错误码 sdk.NET_DVR_GetLastError()); sdk.NET_DVR_Cleanup(); return; } System.out.println(登录成功用户句柄 userId 通道总数 deviceInfo.byChanNum 起始通道 deviceInfo.byStartChan); // 业务逻辑... sdk.NET_DVR_Logout(userId); sdk.NET_DVR_Cleanup(); } }逻辑说明NET_DVR_Init必须在登录前调用它负责初始化 SDK 内部的线程和资源池。NET_DVR_SetConnectTime的dwWaitTime单位是毫秒表示单次连接超时dwTryTimes是重试次数我一般设 3000/1也就是 3 秒超时且不重试避免设备不在线时登录请求长时间卡住。参数说明登录成功返回的byChanNum是通道总数byStartChan是起始通道号很多设备的起始通道不是 0 而是 1预览时通道号不能从 0 开始遍历必须用这两个字段算出来。3.4 实时预览注册回调拿 PS 码流登录之后最核心的操作就是预览。调用NET_DVR_RealPlay_V40时传入预览参数结构体和回调函数SDK 解码出码流后会把数据推送给回调。Java 侧回调是StdCallCallback子接口回调方法在 SDK 专线程里执行不能做耗时操作。import com.sun.jna.Callback; import com.sun.jna.Pointer; import com.sun.jna.Structure; import java.util.Arrays; import java.util.List; public class PreviewDemo { // 预览参数结构体对应 NET_DVR_PREVIEWINFO public static class NET_DVR_PREVIEWINFO extends Structure { public int lChannel; // 通道号 public int dwStreamType; // 码流类型0 主码流1 子码流 public int dwLinkMode; // 链路模式0 TCP1 多播2 UDP public int bBlocked; // 是否阻塞式取流 public int dwDisplayBufNum; // 显示缓冲帧数 // 其余字段按头文件补齐 Override protected ListString getFieldOrder() { return Arrays.asList(lChannel, dwStreamType, dwLinkMode, bBlocked, dwDisplayBufNum); } } // 实时数据回调对应 SDK 中的 fRealDataCallBack public interface RealDataCallback extends Callback { void invoke(int lRealHandle, int dwDataType, Pointer pBuffer, int dwBufSize, Pointer pUser); } public interface IDahuaSDK extends com.sun.jna.Library { IDahuaSDK INSTANCE Native.load(dhnetsdk, IDahuaSDK.class); int NET_DVR_RealPlay_V40(int lUserID, NET_DVR_PREVIEWINFO.ByReference previewInfo, RealDataCallback callback, Pointer pUser); boolean NET_DVR_StopRealPlay(int lRealHandle); } public static void main(String[] args) { int userId loginDevice(); // 先完成登录 NET_DVR_PREVIEWINFO previewInfo new NET_DVR_PREVIEWINFO(); previewInfo.lChannel 1; // 具体值来自 deviceInfo.byStartChan previewInfo.dwStreamType 0; // 主码流画质高、码率大 previewInfo.dwLinkMode 0; // TCP 方式最稳定 previewInfo.bBlocked 1; // 阻塞式返回回调触发更及时 RealDataCallback callback (handle, dataType, buffer, bufSize, user) - { // 只处理码流数据其它类型按业务需要再扩展 if (dataType 0) { byte[] frame buffer.getByteArray(0, bufSize); // 这里只做拷贝丢给线程池做写盘或转封装 pipeline.offer(frame); } }; int playHandle IDahuaSDK.INSTANCE.NET_DVR_RealPlay_V40( userId, previewInfo, callback, null); if (playHandle -1) { System.err.println(预览失败错误码 IDahuaSDK.INSTANCE.NET_DVR_GetLastError()); } // 业务运行中... IDahuaSDK.INSTANCE.NET_DVR_StopRealPlay(playHandle); IDahuaSDK.INSTANCE.NET_DVR_Logout(userId); IDahuaSDK.INSTANCE.NET_DVR_Cleanup(); } }逻辑说明dwDataType为 0 表示码流数据也就是我们最常关心的内容SDK 在不同版本里对音频、智能数据的类型号定义不完全一致按官方头文件里的宏来写判断条件最稳妥。回调里buffer.getByteArray(0, bufSize)会把 native 内存拷贝成 Java 字节数组这一步本身有开销但它是 Java 安全访问 native 内存的必经路径不能省略。参数说明bBlocked设为 1 时回调是阻塞模式SDK 内部会排队设为 0 时是非阻塞模式数据可能被丢弃。做录像保存建议用阻塞模式做实时显示可以用非阻塞模式降低延迟。4. 参数记忆清单和大华 SDK 的字段习惯4.1 登录参数端口为什么是 8000超时和错误码怎么定位大华设备的 SDK 接入端口默认是 8000和 HTTP 端口 80、RTSP 端口 554 完全不是一回事。设备管理页面里能改这个端口改了之后 SDK 登录也必须跟着改否则登录一定会失败。很多人在项目现场遇到「设备能 Ping 通但登录失败」第一反应怀疑账号密码实际上去设备网络参数里看一眼端口有没有被改过能省下大量排查时间。超时设置我用NET_DVR_SetConnectTime(3000, 1)。这个接口的dwWaitTime是单次握手超时单位毫秒dwTryTimes是重试次数。如果设备经常不在线重试次数可以设成 3但每次失败都会等待一个超时周期批量轮询设备状态时会让整个巡检线程变慢。登录失败后调用NET_DVR_GetLastError()拿错误码对照 SDK 文档里的NET_DVR_*_ERROR宏去定位常见的有网络不可达、账号密码错误、权限不足、版本不匹配这几类。我习惯把错误码和含义做成一个映射表放进日志这样现场排查时不用再翻 PDF。登录结构体里的bUseTransport字段控制是否启用传输层加密。开启加密后抓包看不到明文密码但也可能影响某些老设备的兼容性。如果登录时反复失败且设备型号较老可以先把这个字段设成 0 再试一次。4.2 预览参数三兄弟通道号、码流类型、链路模式预览参数里最容易设错的是通道号。设备返回的byStartChan是起始通道号很多型号从 1 开始而 Java 开发者习惯从 0 开始遍历结果通道 0 永远黑屏。正确做法是用登录时返回的byStartChan作为第一个通道遍历byChanNum个通道。码流类型dwStreamType的常用值0 是主码流分辨率高、码率大适合录像存储和画面分析1 是子码流分辨率低、带宽占用小适合预览墙和大批量轮询2 是三码流通常在需要手机端和 PC 端同时看图时用。调试阶段建议先用子码流画幅小、传输快问题更容易定位确认链路通了再切主码流。链路模式dwLinkMode0 是 TCP最稳定适合跨网段取流2 是 UDP延迟低但可能丢包适合局域网内实时预览1 是多播适合多客户端同时看同一路画面的场景但需要交换机开启多播。我一般默认 TCP只有延迟敏感项目才试 UDP。参数常见值适用场景备注lChannelbyStartChan 起设备通道号不要把 0 当起始dwStreamType0/1/2主/子/三码流调试先用子码流dwLinkMode0/2/1TCP/UDP/多播跨网段用 TCPbBlocked1录像/抓帧非阻塞会丢帧4.3 回调里的 dwDataType哪些数据值得落盘回调里的dwDataType是 SDK 给每段数据打的类型标签。实时码流数据通常用 0 表示音频数据在某些 SDK 版本里是 2智能分析数据可能是 3 或者更高具体数值以你手里头文件定义为准。因为不同版本定义有差异代码里最稳妥的写法是先用头文件里的宏常量确认一遍再写判断分支不要凭经验硬编码。落盘策略上只对需要的类型做处理。做纯视频存储时判断dwDataType 0才把数据写入管道做音视频同步存储时再把音频类型的字节数组按时间戳写入同一个容器。回调线程里不要做任何 I/O 操作拿到字节数组后立即交给阻塞队列由专线程负责写盘。每次回调的bufSize不固定SDK 是按帧切块的一帧视频数据可能被拆成多次回调写文件时不要每次回调都新建文件用同一个输出流连续写。4.4 把 PS 流交给 FFmpeg 转封装Java 侧只做搬运工大华预览回调给的是 PS 流Program Stream直接把字节写进文件会得到一个.ps或.dat文件用普通播放器不一定能播更不适合直接给 Web 前端播放。常见做法是把回调数据落成临时文件再交给 FFmpeg 转封装成 MP4。转封装只改容器不改编码速度快、画质无损。# 把 PS 流转成 MP4 容器编码保持原始 H.264/H.265 ffmpeg -i record.ps -c copy -movflags faststart record.mp4逻辑说明-c copy告诉 FFmpeg 不做转码只重新封装-movflags faststart把 MP4 的索引信息移到文件头方便 Web 播放器边下边播。参数说明如果设备输出的是 H.265 编码生成的 MP4 播放器必须支持 H.265 解码浏览器里播放的话要注意兼容性某些老浏览器不支持。如果业务端对延迟敏感FiFo 管道的方式可以直接把回调数据灌给 FFmpeg 的标准输入而不落临时文件减少磁盘 I/O。5. 避坑现场Java 接大华 SDK 的 5 个典型翻车记录5.1 现象设备 Ping 得通登录始终报错 9 或 17现场最常遇到的情况是设备 IP 能 Ping 通甚至管理页面都能打开但 SDK 登录就是失败错误码在 9 和 17 这一类里。原因通常有两个第一设备 SDK 端口不是默认的 8000被改过但管理页面里不显眼第二账号密码正确但权限不足登录用户名不是管理员账号而 SDK 登录需要管理员权限。解决方法是先用设备配套的配置工具验证一遍端口和账号再对照 SDK 文档里的错误码宏确认到底卡在哪一步。我习惯在登录失败时把NET_DVR_GetLastError()的错误码连同设备 IP、端口、用户名一起打进日志避免现场反复试。5.2 现象回调里写文件预览卡顿、内存飙升把FileOutputStream直接写在回调里是新手最容易踩的坑。SDK 回调线程是 native 层的工作线程Java 侧的磁盘写入动作会阻塞这个线程SDK 内部缓冲满了就开始丢帧同时 Java 堆里堆积大量未及时处理的字节数组GC 频繁内存曲线直线上升。解决方法是回调里只做一次System.arraycopy或buffer.getByteArray把数据放进容量固定的阻塞队列由独立线程消费并写盘。队列容量要设上限比如 5000 帧满了就丢弃最旧的数据保证回调永远不被阻塞。5.3 现象登录成功了通道数却全是 0登录成功后设备信息里通道数读出来是 0看起来像设备没配置。这通常不是设备问题而是 JNA 结构体字段顺序和 C 头文件不一致。NET_DVR_DEVICEINFO_V40里字段很多你只声明了前几个字段中间漏掉一个保留数组后面所有字段的偏移就全部错位读出来的byChanNum实际是别的字段的值。解决方法是严格按头文件顺序补全所有字段哪怕用不到也要用byte[]占位然后对照头文件逐个核对类型BYTE对应byte、WORD对应short、DWORD对应int一个都不能混。5.4 现象JVM 直接崩溃不是抛异常JNA 调用 native 库时如果发生内存越界通常是进程直接崩溃Java 层连异常都看不到。原因大多出在字节数组长度不匹配登录结构体里sDeviceAddress在 C 里是 129 字节你声明成 128 甚至用String传进去SDK 写入时就会越界。解决方法是所有结构体数组长度按头文件原样定义不要自己裁减。如果崩溃发生在回调返回后还要检查getByteArray的bufSize是否大于实际缓冲区长度回调给出的dwBufSize就是可信长度不要额外加偏移。5.5 现象Windows 好好的Linux 加载库就失败开发环境 Windows 上跑得正常部署到 Linux 服务器后一启动就报找不到动态库或符号错误。原因分两类一是dhnetsdk.so依赖的其他 .so 库没同步部署或者系统里缺少某些基础库用ldd libdhnetsdk.so看缺失依赖二是库位数不匹配服务器 JVM 是 64 位但 SDK 只放了 32 位版本。解决方法是部署前先把 SDK 目录完整拷贝到服务器用ldd检查依赖确认 JVM 位数和 .so 位数一致启动参数里用绝对路径指定jna.library.path别依赖系统环境变量。6. 进阶断线重连、自动抓图和一个值得养成的验证习惯6.1 断线重连用 SDK 异常回调替代定时器轮询设备网络不稳定时登录句柄会失效预览句柄也可能断开。不要自己起定时器反复调用NET_DVR_Login_V40SDK 提供了异常回调机制NET_DVR_SetDVRMessageCallBack_V31注册一个全局回调网络断开和重连成功都会触发。回调里根据异常类型决定是等待重连还是重新登录异常类型里 0 表示断开1 表示重连成功具体宏名以头文件为准。重连成功之后必须重新调用NET_DVR_RealPlay_V40建立预览老句柄已经不可用。6.2 自动抓图JPEG 参数别用默认值抓图用NET_DVR_CaptureJPEGPicture接口它需要传入NET_DVR_JPEGPARA结构体里面有两个关键参数wPicSize和wPicQuality。wPicQuality不设的话默认值可能导致图片体积过大建议按实际需求设到中间档位画质损失不明显但存储成本下降不少。抓图保存路径不要用中文和空格大华 SDK 内部处理文件名时对某些字符集支持不好现场容易出诡异问题。6.3 验证习惯没有真实设备时能做什么我养成的习惯是每次对接新项目都把设备型号、SDK 版本号、端口、码流类型、登录账号权限写成一个 JSON 配置文件和代码一块入库。SDK 版本一更新先跑一次登录和预览冒烟测试确认设备固件兼容性再上生产。没有真实设备时可以用官方 Demo 加模拟器验证 JNA 结构体映射是否正确但最终还是要用真机过一遍全链路。希望帮到你。本文还有配套的精品资源点击获取