1. 环境准备与SDK集成
搞过多年的监控项目,我发现海康摄像头的SDK集成其实没那么复杂,关键是要把环境配对了。先说说我踩过的坑:第一次集成时因为没注意32位和64位的区别,折腾了一整天。
SDK获取与配置海康官方SDK需要从官网下载,注意区分Windows和Linux版本。我建议直接放在项目的resources/lib目录下,这样打包部署都方便。Windows平台需要这两个关键文件:
- HCNetSDK.dll
- PlayCtrl.dll
Linux平台则是:
- libhcnetsdk.so
- libPlayCtrl.so
Maven依赖配置在pom.xml中添加JNA依赖,这是调用海康SDK的关键:
<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.10.0</version> </dependency>SDK初始化代码这个初始化方法我用了不下20个项目,绝对靠谱:
public class HikvisionSDK { private static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE; public static boolean init() { // 设置SDK日志路径(排查问题时特别有用) hCNetSDK.NET_DVR_SetLogToFile(3, "./sdklog", true); // 设置连接超时(单位毫秒) hCNetSDK.NET_DVR_SetConnectTime(2000, 1); // 初始化SDK return hCNetSDK.NET_DVR_Init(); } }2. 多摄像头设备管理
管理多个摄像头时,最头疼的就是设备连接状态维护。我设计了个设备管理类,用Map保存设备句柄,实测可以稳定管理50+摄像头。
设备登录实现这段登录代码我优化过三次,现在的版本最稳定:
public class DeviceManager { private static Map<String, Integer> deviceMap = new ConcurrentHashMap<>(); public static int login(String ip, short port, String username, String password) { HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); HCNetSDK.NET_DVR_DEVICEINFO_V40 deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40(); // 设置登录参数 System.arraycopy(ip.getBytes(), 0, loginInfo.sDeviceAddress, 0, ip.length()); System.arraycopy(username.getBytes(), 0, loginInfo.sUserName, 0, username.length()); System.arraycopy(password.getBytes(), 0, loginInfo.sPassword, 0, password.length()); loginInfo.wPort = port; int userId = hCNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo); if(userId != -1) { deviceMap.put(ip, userId); log.info("摄像头{}登录成功,通道数:{}", ip, deviceInfo.struDeviceV30.byChanNum); } return userId; } }多设备管理技巧
- 使用连接池管理设备连接
- 定时心跳检测设备状态
- 异常自动重连机制
- 合理的连接超时设置(建议2-3秒)
3. 布防与报警回调
布防是监控系统的核心功能,这里有个关键点:JSON数据和图片数据分离配置。这个配置能让后续数据处理简单很多。
布防配置代码这段配置代码来自海康官方文档,但加了异常处理:
public class AlarmManager { public static int setupAlarm(int userId) { HCNetSDK.NET_DVR_SETUPALARM_PARAM alarmParam = new HCNetSDK.NET_DVR_SETUPALARM_PARAM(); alarmParam.dwSize = alarmParam.size(); alarmParam.byLevel = 1; // 布防等级 alarmParam.byAlarmInfoType = 1; // 使用新报警信息 // 关键配置:分离JSON和图片数据 HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG generalCfg = new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG(); generalCfg.byAlarmJsonPictureSeparate = 1; hCNetSDK.NET_DVR_SetSDKLocalCfg(17, generalCfg.getPointer()); int alarmHandle = hCNetSDK.NET_DVR_SetupAlarmChan_V41(userId, alarmParam); if(alarmHandle == -1) { log.error("布防失败,错误码:{}", hCNetSDK.NET_DVR_GetLastError()); } return alarmHandle; } }回调函数实现这个回调类处理各种报警事件,特别注意车牌识别和图片保存:
public class AlarmCallback implements HCNetSDK.FMSGCallBack_V31 { @Override public boolean invoke(int command, HCNetSDK.NET_DVR_ALARMER alarmer, Pointer alarmInfo, int bufLen, Pointer user) { // 车牌识别处理 if(command == HCNetSDK.COMM_ITS_PLATE_RESULT) { handlePlateResult(alarmInfo); } return true; } private void handlePlateResult(Pointer alarmInfo) { HCNetSDK.NET_ITS_PLATE_RESULT plateResult = new HCNetSDK.NET_ITS_PLATE_RESULT(); plateResult.write(); Pointer pPlateInfo = plateResult.getPointer(); pPlateInfo.write(0, alarmInfo.getByteArray(0, plateResult.size()), 0, plateResult.size()); plateResult.read(); // 保存车牌图片 savePlateImage(plateResult); } }4. 图片存储与数据管理
图片存储我推荐使用"本地磁盘+Redis缓存+MySQL持久化"的三层架构。实测这种架构能承受每秒100+的抓拍请求。
本地存储实现这段代码处理图片保存,加入了日期目录:
public class ImageStorage { public static String saveImage(byte[] imageData, String plateNumber) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); String dateDir = dateFormat.format(new Date()); File dir = new File("./images/" + dateDir); if(!dir.exists()) { dir.mkdirs(); } String filename = plateNumber + "_" + System.currentTimeMillis() + ".jpg"; try(FileOutputStream fos = new FileOutputStream( new File(dir, filename))) { fos.write(imageData); return filename; } catch(Exception e) { log.error("图片保存失败", e); return null; } } }Redis缓存设计使用Redis的Hash结构存储最新抓拍数据:
public class RedisCache { @Autowired private RedisTemplate<String, Object> redisTemplate; public void cachePlateInfo(String plateNumber, PlateInfo info) { redisTemplate.opsForHash().put( "plate:latest", plateNumber, info); // 设置30天过期 redisTemplate.expire("plate:latest", 30, TimeUnit.DAYS); } }MySQL表设计这是经过优化的车牌数据表结构:
CREATE TABLE `plate_records` ( `id` bigint NOT NULL AUTO_INCREMENT, `plate_number` varchar(20) NOT NULL, `capture_time` datetime NOT NULL, `image_path` varchar(255) NOT NULL, `vehicle_type` tinyint DEFAULT NULL, `camera_id` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_plate_time` (`plate_number`,`capture_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;5. 定时任务与数据同步
数据同步我推荐使用Spring Batch,比普通定时任务更可靠。下面是我的实战配置:
Spring Batch配置这个配置实现了断点续传和批量提交:
@Configuration @EnableBatchProcessing public class BatchConfig { @Autowired private JobBuilderFactory jobBuilderFactory; @Autowired private StepBuilderFactory stepBuilderFactory; @Bean public Job syncPlateJob() { return jobBuilderFactory.get("syncPlateJob") .start(syncStep()) .build(); } @Bean public Step syncStep() { return stepBuilderFactory.get("syncStep") .<PlateInfo, PlateInfo>chunk(100) .reader(plateReader()) .processor(plateProcessor()) .writer(plateWriter()) .build(); } }数据读取器实现从Redis读取待同步数据:
public class PlateReader implements ItemReader<PlateInfo> { @Autowired private RedisTemplate<String, Object> redisTemplate; private Iterator<Object> iterator; @Override public PlateInfo read() { if(iterator == null || !iterator.hasNext()) { List<Object> values = redisTemplate.opsForHash() .values("plate:sync"); iterator = values.iterator(); } return iterator.hasNext() ? (PlateInfo)iterator.next() : null; } }6. 异常处理与性能优化
在监控项目中,稳定性比功能更重要。这是我总结的几个关键点:
常见错误处理
- SDK初始化失败:检查dll/so文件路径
- 设备登录失败:检查网络和账号权限
- 布防失败:确认设备支持智能分析功能
- 回调不触发:检查JSON和图片分离配置
性能优化技巧
- 使用连接池管理设备连接
- 图片存储使用异步线程池
- Redis使用Pipeline批量操作
- MySQL批量插入使用rewriteBatchedStatements=true
监控指标设计建议监控这些关键指标:
- 设备在线率
- 抓拍成功率
- 图片处理延迟
- 存储空间使用率
7. 实战经验分享
最后分享几个只有踩过坑才知道的经验:
海康SDK的线程安全问题:SDK本身不是线程安全的,建议所有SDK调用都放在同一个线程处理。我专门写了个单线程的Executor来处理所有SDK操作。
内存泄漏排查:长期运行后如果发现内存增长,重点检查回调函数中的Pointer对象是否及时释放。我遇到过因为没释放Pointer导致OOM的情况。
跨平台部署:Linux下需要注意so文件的权限问题,建议部署时执行chmod +x *.so。还有glibc版本兼容性问题,最好在相同版本的系统上编译。
日志管理:海康SDK的日志非常详细,但如果不加控制会很快占满磁盘。建议配置日志轮转,只保留最近7天的日志。