最近项目中用到海康威视的摄像头实现人员移动检测以及前端页面手机端等查看实时摄像头。由于第一次基于海康SDK开发,留一笔记用于记录,同时希望帮助到后来人。废话不多说,直接开始:
注:本文中所有的代码和工具包的下载地址如下(可导入idea直接运行)传送门:海康威视SpringBoot运行demo
PS:本文实现的功能如下(后期如用到会增加功能)
- 设备的布防、撤防(布防成功后可实现对客流量统计、移动侦测、设备遮挡、人脸侦测报警等。更多报警类型查看官方文档,本文只实现移动侦测作为例子。)
- 摄像头实时视频(用于web端、手机端观看)
传送门:官方SDK下载
本文所用sdk版本:
CH-HCNetSDKV6.1.6.45_build20210302_linux64
CH-HCNetSDKV6.1.6.45_build20210302_win64
一、流程分析-
集成海康sdk到项目
-
实现登录布防、撤防、注销、监听等功能
-
获取摄像头的rtsp视频流转成rtmp直播流网页显示
-
搭建流媒体服务器
由于服务器用的是CentOS,所以本文用的是Linux版SDK。但是本地开发调试一般我们用的是windows系统,所以兼容的windowsSDK。
ps:为了方便调试先以windowsSDK为例
下载sdk后解压
把jna.jar examples.jar加入到本地maven库中
Maven 安装 JAR 包的命令:#语法 mvn install:install-file -Dfile=jar包的位置(参数一) -DgroupId=groupId(参数二) -DartifactId=artifactId(参数三) -Dversion=version(参数四) -Dpackaging=jar mvn install:install-file -Dfile="D:jna.jar" -DgroupId=net.java.jna -DartifactId=jna -Dversion=1.0.0 -Dpackaging=jar net.java.jna -----------------(参数二) jna -----------(参数三)1.0.0 ------------(参数四)
在项目根目录创建lib文件,把库文件里的所有文件复制到项目lib文件里(如需精简请查看官方sdk里的txt文件)。如下图:
由于项目需要部署到Linux服务器所以打开LinuxSDK文件夹下》LinuxJavaDemo》src》test》把HCNetSDK.java复制到项目中,如果部署到windows服务器,复制windowSDK的Demo里的此文件。(HCNetSDK.java在不同的sdk中的方法不一样)
项目部署到Linux系统时,需要使用Linux版的sdk包,把LinuxSDK中的lib包上传到服务器,可以使用绝对路径、也可以使用官方文档中的方法。
2.实现登录布防、撤防、注销、监听等功能初始化sdk实例
注册登录,登录成功会返回用户id,用户id用于布防和注销登录
public Integer login(String m_sDeviceIP, String m_sUsername, String m_sPassword, short m_sPort) {
// 初始化
if (!hCNetSDK.NET_DVR_Init()) {
log.error("SDK初始化失败");
}
//设置连接时间与重连时间
hCNetSDK.NET_DVR_SetConnectTime(2000, 1);
hCNetSDK.NET_DVR_SetReconnect(100000, true);
//设备信息, 输出参数
HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();
HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
// 注册设备-登录参数,包括设备地址、登录用户、密码等
m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());
m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());
m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());
m_strLoginInfo.wPort = m_sPort;
//是否异步登录:0- 否,1- 是 windowsSDK里是true和false
m_strLoginInfo.bUseAsynLogin = 0;
m_strLoginInfo.write();
NativeLong lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo.getPointer(), m_strDeviceInfo.getPointer());
if (lUserID.intValue() < 0) {
//释放SDK资源
hCNetSDK.NET_DVR_Cleanup();
log.error("登录失败");
}
return lUserID.intValue();
}
传入登录返回的用户id,布防成功会返回一个id,此id用于撤防
public Integer setupAlarmChan(Integer lUserID) {
//设置监听回调函数
if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V30(new FMSGCallBack(), null)) {
log.error("设置回调函数失败");
}
//启用布防
NativeLong lAlarmHandle = hCNetSDK.NET_DVR_SetupAlarmChan_V30(new NativeLong(lUserID));
if (lAlarmHandle.intValue() < 0) {
hCNetSDK.NET_DVR_Logout(new NativeLong(lUserID));
hCNetSDK.NET_DVR_Cleanup();
log.error("布防失败");
}
return lAlarmHandle.intValue();
}
传入布防成功返回的id
public void closeAlarmChan(Integer lAlarmHandle) {
if (lAlarmHandle.intValue() > -1) {
if (!hCNetSDK.NET_DVR_CloseAlarmChan_V30(new NativeLong(lAlarmHandle))) {
log.error("撤防失败");
}
}
}
传入登录返回的用户id注销登录
public void logout(Integer lUserID) {
hCNetSDK.NET_DVR_Logout(new NativeLong(lUserID));
hCNetSDK.NET_DVR_Cleanup();
}
布防之后会有一个回调类用于监听,可以监听多项事件,具体事件类型查看官方文档。
@Slf4j
public class FMSGCallBack implements HCNetSDK.FMSGCallBack {
@Override
public void invoke(NativeLong lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, HCNetSDK.RECV_ALARM pAlarmInfo, int dwBufLen, Pointer pUser) {
//报警类型
String sAlarmType = "lCommand=0x" + Integer.toHexString(lCommand.intValue());
//报警时间
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
//序列号
String sSerialNumber = byteToString(pAlarmer.sSerialNumber);
//用户id
String lUserID = new String(String.valueOf(pAlarmer.lUserID));
//ip地址
String sDeviceIP = byteToString(pAlarmer.sDeviceIP);
//lCommand是传的报警类型
switch (lCommand.intValue()) {
//移动侦测
case HCNetSDK.COMM_ALARM_V30:
//报警类型
log.info("sAlarmType:======{}", sAlarmType);
//设备序列号
log.info("sSerialNumber:======{}", sSerialNumber);
//用户id
log.info("lUserID:======{}", lUserID);
//设备ip
log.info("sDeviceIP:======{}", sDeviceIP);
//当前时间
log.info("date:======{}", date);
break;
default:
log.info("其他报警信息=========={}");
break;
}
}
private static String byteToString(byte[] bytes) {
String[] strings = new String(bytes).split(" ", 2);
StringBuilder sb = new StringBuilder();
if (strings != null && strings.length > 0) {
for (int i = 0; i < strings.length - 1; i++) {
sb.append(strings[i]);
}
}
return sb.toString();
}
}
实现效果:(可在监听回调中,做一些自己的业务逻辑)
3.获取摄像头的rtsp视频流转成rtmp直播流网页显示组装海康rtsp地址:
- 手动组装 传送门:此文章有非常详细的海康视频流介绍
- onvif插件获取 传送门:此文章有详细的onvif介绍
本文主要用onvif插件获取rtsp视频流,把onvif安装到本地meven库,引入到pom文件。(上方有jar包安装教程)
传送门:onvif包下载
由于权限问题,需要在视频流中拼接上账号密码,此方法获取的视频流为主码流,如需要子码流请查看上方视频流介绍修改视频流。获取rtsp视频流的代码如下:
public String getStreamUri(String m_sDeviceIP, String m_sUsername, String m_sPassword) {
onvifDevice nvt = null;
try {
nvt = new onvifDevice(m_sDeviceIP, m_sUsername, m_sPassword);
} catch (ConnectException e) {
log.error("获取实时视频流失败");
} catch (SOAPException e) {
log.error("获取实时视频流失败");
} catch (MalformedURLException e) {
log.error("获取实时视频流失败");
}
String streamUri = nvt.getStreamUri().replace("rtsp://", "");
streamUri = "rtsp://" + m_sUsername + ":" + m_sPassword + "@" + streamUri;
return streamUri;
}
# 返回的rtsp视频流如下
# rtsp://admin:123456@172.19.33.88:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1
把rtsp转成rtmp视频流
本文使用ffmpeg把rtsp视频流转成rtmp视频流,并推流到流媒体服务器。
安装ffmpeg、把ffch4j.jar安装到本地meven库,引入到pom文件。
传送门:windows版ffmpeg
需要把ffmpeg的路径配置到环境变量path中。
Linux版请看下方流媒体服务器安装。
传送门:ffch4j.jar下载
public String startTranscoding(String appName,String m_sDeviceIP,String m_sUsername,String m_sPassword) {
if(manager == null){
manager = new CommandManagerImpl(10);
}
if(taskerIsRun(appName)){
//如果进程存在,则直接返回进程名
return appName;
}
String streamUri = hikVisionService.getStreamUri(m_sDeviceIP,m_sUsername,m_sPassword);
Map map = new HashMap<>();
//进程名
map.put("appName", appName);
//组装rtsp流
map.put("input", streamUri);
//rtmp流.live为nginx-rtmp的配置
map.put("output", "rtmp://localhost:1935/live/");
map.put("codec", "h264");
map.put("fmt", "flv");
map.put("fps", "25");
map.put("rs", "1280x720");
map.put("twoPart", "1");
// 执行任务,id就是appName,如果执行失败返回为null
return manager.start(map);
}
public boolean stopTranscoding(String appName){
if(!taskerIsRun(appName)) {
return true;
}
return manager.stop(appName);
}
在loadFFmpeg.properties配置文件中配置ffmpeg路径
#ffmpeg执行路径,一般为ffmpeg的安装目录,该路径只能是目录,不能为具体文件路径,否则会报错 path=C:/Users/Administrator/Desktop/ffmpeg-20200315-c467328-win64-static/bin/ #存放任务的默认Map的初始化大小 size=10 #事件回调通知接口地址 callback=http://127.0.0.1/callback #网络超时设置(毫秒) timeout=300 #开启保活线程 keepalive=true #是否输出debug消息 debug=true
推流成功后可以通过播放 rtmp://流媒体服务器ip:1935/live/进程名,进行测试。
4.搭建流媒体服务器由于篇幅过长,请移步一下文章查看详细搭建流程。
传送门:nginx流媒体服务器搭建
5.结束本文内容纯手打!转载请注明出处,谢谢!
本文所有代码、jar包、运行demo可以最上方链接下载。



