最近项目中用到海康威视的摄像头实现人员移动检测以及前端页面手机端等查看实时摄像头。由于第一次基于海康SDK开发,留一笔记用于记录,同时希望帮助到后来人。废话不多说,直接开始:
注:本文中所有的代码和工具包的下载地址如下(可导入idea直接运行)
PS:本文实现的功能如下(后期如用到会增加功能)
- 设备的布防、撤防(布防成功后可实现对客流量统计、移动侦测、设备遮挡、人脸侦测报警等。更多报警类型查看官方文档,本文只实现移动侦测作为例子。)
- 摄像头实时视频(用于web端、手机端观看)
传送门:官方SDK下载
本文所用sdk版本:
CH-HCNetSDKV6.1.6.45_build20210302_linux64
CH-HCNetSDKV6.1.6.45_build20210302_win64
一、流程分析
-
集成海康sdk到项目
-
实现登录布防、撤防、注销、监听等功能
-
获取摄像头的rtsp视频流转成rtmp直播流网页显示
-
搭建流媒体服务器
二、具体实现过程
1. 集成海康sdk到项目
由于服务器用的是CentOS,所以本文用的是Linux版SDK。但是本地开发调试一般我们用的是windows系统,所以兼容的windowsSDK。
ps:为了方便调试先以windowsSDK为例
下载sdk后解压
把jna.jar examples.jar加入到本地maven库中
Maven 安装 JAR 包的命令:
<dependency>
<groupId>net.java.jna</groupId>-----------------(参数二)
<artifactId>jna </artifactId>-----------(参数三)
<version>1.0.0</version>------------(参数四)
</dependency>
#语法
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
在项目根目录创建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用于布防和注销登录
/**
* 注册登录
*
* @param m_sDeviceIP 设备ip
* @param m_sUsername 设备用户名
* @param m_sPassword 设备密码
* @param m_sPort 设备端口
* @return
*/
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用于撤防
/**
* 布防
*
* @param lUserID
* @return
*/
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
/**
* 撤防
*
* @param lAlarmHandle
*/
public void closeAlarmChan(Integer lAlarmHandle) {
if (lAlarmHandle.intValue() > -1) {
if (!hCNetSDK.NET_DVR_CloseAlarmChan_V30(new NativeLong(lAlarmHandle))) {
log.error("撤防失败");
}
}
}
传入登录返回的用户id注销登录
/**
* 注销
*
* @param lUserID
*/
public void logout(Integer lUserID) {
hCNetSDK.NET_DVR_Logout(new NativeLong(lUserID));
hCNetSDK.NET_DVR_Cleanup();
}
布防之后会有一个回调类用于监听,可以监听多项事件,具体事件类型查看官方文档。
@Slf4j
public class FMSGCallBack implements HCNetSDK.FMSGCallBack {
/**
* 报警信息回调函数
*
* @param lCommand 上传消息类型
* @param pAlarmer 报警设备信息
* @param pAlarmInfo 报警信息
* @param dwBufLen 报警信息缓存大小
* @param pUser 用户数据
*/
@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;
}
}
/**
* 处理返回的信息 字节转字符串
*
* @param bytes
* @return
*/
private static String byteToString(byte[] bytes) {
String[] strings = new String(bytes).split("\0", 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();
}
}
实现效果:(可在监听回调中,做一些自己的业务逻辑)
(新增)摄像头控制
新增摄像头控制功能,如需要相关功能请自行加入到代码中。userId是摄像头登录成功后换回的id标识!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/**
* 控制上-开始
*
* @param userId
*/
public void startUp(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 21, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制上-结束
*
* @param userId
*/
public void endUp(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 21, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制下-开始
*
* @param userId
*/
public void startDown(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 22, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制下-结束
*
* @param userId
*/
public void endDown(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 22, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制左-开始
*
* @param userId
*/
public void startLeft(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 23, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制左-结束
*
* @param userId
*/
public void endLeft(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 23, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制右-开始
*
* @param userId
*/
public void startRight(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 24, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制右-结束
*
* @param userId
*/
public void endRight(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 24, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制上左-开始
*
* @param userId
*/
public void startUpLeft(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 25, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制上左-结束
*
* @param userId
*/
public void endUpLeft(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 25, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制上右-开始
*
* @param userId
*/
public void startUpRight(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 26, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制上右-结束
*
* @param userId
*/
public void endUpRight(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 26, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制下左-开始
*
* @param userId
*/
public void startDownLeft(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 27, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制下左-结束
*
* @param userId
*/
public void endDownLeft(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 27, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制下右-开始
*
* @param userId
*/
public void startDownRight(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 28, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 控制下右-结束
*
* @param userId
*/
public void endDownRight(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 28, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦距变大-开始
*
* @param userId
*/
public void startZoomIn(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 11, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦距变大-结束
*
* @param userId
*/
public void endZoomIn(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 11, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦距变小-开始
*
* @param userId
*/
public void startZoomOut(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 12, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦距变小-结束
*
* @param userId
*/
public void endZoomOut(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 12, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦点前调-开始
*
* @param userId
*/
public void startFocusNear(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 13, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦点前调-结束
*
* @param userId
*/
public void endFocusNear(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 13, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦点后调-开始
*
* @param userId
*/
public void startFocusFar(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 14, 0);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 焦点后调-结束
*
* @param userId
*/
public void endFocusFar(Integer userId) {
boolean bool = hCNetSDK.NET_DVR_PTZControl_Other(new NativeLong(userId), new NativeLong(1), 14, 1);
if (!bool) {
throw new ServiceException("控制失败,请稍后重试");
}
}
/**
* 是否在线
*
* @param userId
*/
public Boolean isOnLine(Integer userId) {
boolean isOnLine = hCNetSDK.NET_DVR_RemoteControl(new NativeLong(userId), 20005, null, 0);
return isOnLine;
}
/**
* 截图
*
* @param userId
*/
public void captureJPEGPicture(Integer userId, HttpServletResponse response) {
HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(new NativeLong(userId), devwork)) {
// 返回Boolean值,判断是否获取设备能力
throw new ServiceException("抓图失败,请稍后重试");
}
//图片质量
HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA();
//设置图片分辨率
jpeg.wPicSize = 0;
//设置图片质量
jpeg.wPicQuality = 0;
IntByReference a = new IntByReference();
//设置图片大小
ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024);
// 抓图到内存,单帧数据捕获并保存成JPEG存放在指定的内存空间中
boolean is = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(new NativeLong(userId), new NativeLong(1), jpeg, jpegBuffer, 1024 * 1024, a);
log.info("-----------这里开始图片存入内存----------" + is);
ByteArrayInputStream in = new ByteArrayInputStream(jpegBuffer.array(), 0, a.getValue());
OutputStream outputStream = null;
try {
//1、设置response 响应头 //设置页面不缓存,清空buffer
response.reset();
//字符编码
response.setCharacterEncoding("UTF-8");
//二进制传输数据
response.setContentType("multipart/form-data");
//设置响应头
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+".jpeg", "UTF-8"));
outputStream = response.getOutputStream();
LoginUser loginUser = LoginContext.me().getLoginUser();
WaterMarkUtil.markImageByIO(loginUser.getAccount(),in,outputStream,null,"jpeg");
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException("抓图失败,请稍后重试");
} finally {
try {
outputStream.close();
} catch (IOException e) {
throw new ServiceException("抓图失败,请稍后重试");
}
}
log.info("-----------处理完成截图数据----------");
}
private void picCutCate(NativeLong lUserID, NativeLong chanLong, String imgPath) {
//图片质量
HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA();
//设置图片分辨率
jpeg.wPicSize = 0;
//设置图片质量
jpeg.wPicQuality = 0;
IntByReference a = new IntByReference();
//设置图片大小
ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024);
File file = new File(imgPath);
// 抓图到内存,单帧数据捕获并保存成JPEG存放在指定的内存空间中
log.info("-----------这里开始封装 NET_DVR_CaptureJPEGPicture_NEW---------");
boolean is = hCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(lUserID, chanLong, jpeg, jpegBuffer, 1024 * 1024, a);
log.info("-----------这里开始图片存入内存----------" + is);
if (is) {
/**
* 该方式使用内存获取 但是读取有问题无法预览
* linux下 可能有问题
* */
log.info("hksdk(抓图)-结果状态值(0表示成功):" + hCNetSDK.NET_DVR_GetLastError());
byte[] array = jpegBuffer.array();
//存储到本地
BufferedOutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(file));
outputStream.write(jpegBuffer.array(), 0, a.getValue());
outputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
log.info("hksdk(抓图)-抓取失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
}
}
3.获取摄像头的rtsp视频流转成rtmp直播流网页显示
组装海康rtsp地址:
- 手动组装 传送门:此文章有非常详细的海康视频流介绍
onvif插件获取 传送门:此文章有详细的onvif介绍
本文主要用onvif插件获取rtsp视频流,把onvif安装到本地meven库,引入到pom文件。(上方有jar包安装教程)
传送门:onvif包下载
由于权限问题,需要在视频流中拼接上账号密码,此方法获取的视频流为主码流,如需要子码流请查看上方视频流介绍修改视频流。获取rtsp视频流的代码如下:
/**
* 获取实时视频流
*
* @param m_sDeviceIP 设备ip
* @param m_sUsername 设备用户名
* @param m_sPassword 设备密码
* @return
*/
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下载
/**
* 开始推流
* @param appName 进程名称
* @param m_sDeviceIP 设备ip
* @param m_sUsername 设备用户名
* @param m_sPassword 设备密码
* @return
*/
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<String,String> 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);
}
/**
* 关闭进程
* @param appName
* @return
*/
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可以最上方链接下载。