springboot整合海康监控完整步骤及踩坑

一、背景及注意点

项目需要根据时间段从海康监控中获取对应的视频,我只参考文档做了这一个功能,其他功能可自行参考官方代码进行修改,重点讲下如何整合及踩坑

二、步骤

2.1、官网

首先要去官网下载对应的开发包,一般都是window和linux的,都直接下载下来就行。https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10

一般来说都是64位的,注意:这里是没有mac版本的

2.2、整合

2.2.1、jar包

首先将其中的jar包放到自己对应的resources目录下,有两个

然后pom中加上

xml 复制代码
       <dependency>
            <groupId>examples</groupId>
            <artifactId>examples</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/src/main/resources/lib/examples.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>jna</groupId>
            <artifactId>jna</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/src/main/resources/lib/jna.jar</systemPath>
        </dependency>

很重要的一点,打包插件一定要加上这个,否则jar包只会在resources中

xml 复制代码
<!--设置将本地jar包导出到项目最终的依赖库中-->
  <includeSystemScope>true</includeSystemScope>

2.2.2、文件

我把window的文件都放在这里了,可自行随意放置

2.2.3、yaml配置文件

我自己加了几个配置参数

yaml 复制代码
#监控一些资源的配置
video:
  monitor:
    # window中ffmpeg路径,linux不用管
    ffmpegpath:
      window: D:/workplaces/device-supervision/monitorvideo/ffmpeg.exe
    #监控需要加载的动态库,最后不用加斜杠
    load:
      window: D:/workplaces/jianshe/device-supervision/monitorvideo
      linux: /home/monitorvideo

##监控连接相关配置
monitor:
  ip: 
  # 这个默认就是8000,一般不用改
  port: 8000
  user: 
  password: 

2.2.4、对应的Configuration文件啥的

MonitorProperties 读取监控相关配置

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 读取监控相关配置
 */
@Component
@ConfigurationProperties(prefix = "monitor")
@Data
public class MonitorProperties {

    /**
     * 监控ip地址
     */
    private String ip;

    /**
     * 监控端口
     */
    private short port;

    /**
     * 用户名
     */
    private String user;

    /**
     * 用户密码
     */
    private String password;
}

HikVisionConfiguration 用来初始化加载海康的一些类,这些东西给的实例代码中都有的,像OsSelectUtil,FExceptionCallBackException啥的

java 复制代码
@Configuration
@Slf4j
public class HikVisionConfiguration {

    private HCNetSDK hCNetSDK = null;
    private FExceptionCallBackException fExceptionCallBack;

    @Value("${video.monitor.load.window}")
    private String windowPath;
    @Value("${video.monitor.load.linux}")
    private String linuxPath;


    /**
     * 播放库加载
     */
    @Bean
    public PlayCtrl createPlayInstance() {
        PlayCtrl playControl;
        synchronized (PlayCtrl.class) {
            String strPlayPath = "";
            try {
                if (OsSelectUtil.isWindows()) {
                    //win系统加载库路径
                    strPlayPath = windowPath + "\\lib\\HCNetSDK.dll";
                } else if (OsSelectUtil.isLinux()) {
                    //Linux系统加载库路径
                    strPlayPath = linuxPath + "/lib/libPlayCtrl.so";
                }
                playControl = (PlayCtrl) Native.loadLibrary(strPlayPath, PlayCtrl.class);
            } catch (Exception ex) {
                log.error("加载失败,loadLibrary: {}", strPlayPath);
                log.error(ex.getMessage(), ex);
                return null;
            }
        }
        log.info("加载PlayCtrl成功");
        return playControl;
    }

    /**
     * 动态库加载
     */
    @Bean
    public HCNetSDK init() {
        if (hCNetSDK == null) {
            synchronized (HCNetSDK.class) {
                String strDllPath = "";
                try {
                    //win系统加载库路径
                    if (OsSelectUtil.isWindows()) {
                        strDllPath = windowPath + "\\lib\\HCNetSDK.dll";
                    }
                    //linux系统建议调用以下接口加载组件库
                    else if (OsSelectUtil.isLinux()) {
                        strDllPath = linuxPath + "/lib/libhcnetsdk.so";
                    }
                    hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
                    //linux系统建议调用以下接口加载组件库
                    if (OsSelectUtil.isLinux()) {
                        HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
                        HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
                        //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
                        String strPath1 = linuxPath + "/lib/libcrypto.so.1.1";
                        String strPath2 = linuxPath + "/lib/libssl.so.1.1";
                        log.info("strpaht1:{}",strPath1);
                        log.info("strpaht2:{}",strPath2);
                        System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
                        ptrByteArray1.write();
                        hCNetSDK.NET_DVR_SetSDKInitCfg(HCNetSDK.NET_SDK_INIT_CFG_LIBEAY_PATH, ptrByteArray1.getPointer());
                        System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
                        ptrByteArray2.write();
                        hCNetSDK.NET_DVR_SetSDKInitCfg(HCNetSDK.NET_SDK_INIT_CFG_SSLEAY_PATH, ptrByteArray2.getPointer());
                        String strPathCom = linuxPath + "/lib/";
                        HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
                        System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
                        struComPath.write();
                        hCNetSDK.NET_DVR_SetSDKInitCfg(HCNetSDK.NET_SDK_INIT_CFG_SDK_PATH, struComPath.getPointer());
                    }


                } catch (Exception ex) {
                    log.error("加载失败,loadLibrary: {}" ,strDllPath);
                    log.error(ex.getMessage(), ex);
                    return null;
                }
            }
        }
        //SDK初始化,一个程序只需要调用一次
        hCNetSDK.NET_DVR_Init();

        //异常消息回调
        if (fExceptionCallBack == null) {
            fExceptionCallBack = new FExceptionCallBackException();
        }
        Pointer pUser = null;
        if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
            log.error("设置异常消息回调失败");
            return null;
        }
        //启动SDK写日志
        hCNetSDK.NET_DVR_SetLogToFile(3, "./sdkLog", false);
        log.info("加载hksdk成功");
        return hCNetSDK;
    }


    @PreDestroy
    public void destroy() {
        if (null != hCNetSDK) {
            log.info("释放hcNetSDK资源");
            hCNetSDK.NET_DVR_Cleanup();
        }
    }
}

这个类我也不太清楚干啥的,不过源码中有就拿出来了

java 复制代码
@Slf4j
public class FExceptionCallBackException implements HCNetSDK.FExceptionCallBack {
    @Override
    public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
        log.error("异常事件类型:"+dwType);
    }
}

用来登录的工具类

java 复制代码
/**
 * 监控工具类
 */
@Slf4j
public class MonitorUtil {

    private static int userId = -1;

    /**
     * 登录设备,支持 V40 和 V30 版本,功能一致。
     *
     * @return 登录成功返回用户ID,失败返回-1
     */
    public static int loginDevice() {
        if (userId != -1) {
            return userId;
        }
        MonitorProperties bean = SpringUtils.getBean(MonitorProperties.class);
        HCNetSDK hCNetSDK = SpringUtils.getBean(HCNetSDK.class);
        String ip = bean.getIp();
        short port = bean.getPort();
        String user = bean.getUser();
        String psw = bean.getPassword();
        // 创建设备登录信息和设备信息对象
        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();

        // 设置设备IP地址
        byte[] deviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
        byte[] ipBytes = ip.getBytes();
        System.arraycopy(ipBytes, 0, deviceAddress, 0, Math.min(ipBytes.length, deviceAddress.length));
        loginInfo.sDeviceAddress = deviceAddress;

        // 设置用户名和密码
        byte[] userName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
        byte[] password = psw.getBytes();
        System.arraycopy(user.getBytes(), 0, userName, 0, Math.min(user.length(), userName.length));
        System.arraycopy(password, 0, loginInfo.sPassword, 0, Math.min(password.length, loginInfo.sPassword.length));
        loginInfo.sUserName = userName;

        // 设置端口和登录模式
        loginInfo.wPort = port;
        loginInfo.bUseAsynLogin = false; // 同步登录
        loginInfo.byLoginMode = 0; // 使用SDK私有协议

        if (userId == -1) {
            synchronized (MonitorUtil.class) {
                // 执行登录操作
                userId = hCNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo);
                if (userId == -1) {
                    log.error("登录失败,错误码为: {}", hCNetSDK.NET_DVR_GetLastError());
                } else {
                    log.info("{}设备登录成功! ", ip);
                    // 处理通道号逻辑
                    int startDChan = deviceInfo.struDeviceV30.byStartDChan;
                    log.info("预览起始通道号: {}", startDChan);
                }
            }
        }
        return userId; // 返回登录结果
    }

}

PlayCtrl 让我单独抽取出来了

java 复制代码
//播放库函数声明,PlayCtrl.dll
public interface PlayCtrl extends Library {
    public static final int STREAME_REALTIME = 0;
    public static final int STREAME_FILE = 1;

    boolean PlayM4_GetPort(IntByReference nPort);

    boolean PlayM4_OpenStream(int nPort, Pointer pFileHeadBuf, int nSize, int nBufPoolSize);

    boolean PlayM4_InputData(int nPort, Pointer pBuf, int nSize);

    boolean PlayM4_CloseStream(int nPort);

    boolean PlayM4_SetStreamOpenMode(int nPort, int nMode);

    boolean PlayM4_Play(int nPort, W32API.HWND hWnd);

    boolean PlayM4_Stop(int nPort);

    boolean PlayM4_SetSecretKey(int nPort, int lKeyType, String pSecretKey, int lKeyLen);

    boolean PlayM4_GetPictureSize(int nPort, IntByReference pWidth, IntByReference pHeight);

    boolean PlayM4_GetJPEG(int nPort, Pointer pBitmap, int nBufSize, IntByReference pBmpSize);

    int PlayM4_GetLastError(int nPort);

    boolean PlayM4_SetDecCallBackExMend(int nPort, DecCallBack decCBFun, Pointer pDest, int nDestSize, int nUser);

    public static interface DecCallBack extends Callback {
        void invoke(int nPort, Pointer pBuf, int nSize, FRAME_INFO pFrameInfo, int nReserved1, int nReserved2);
    }

    public class FRAME_INFO extends Structure {
        public int nWidth;                   /* 画面宽,单位像素。如果是音频数据,则为音频声道数 */
        public int nHeight;                     /* 画面高,单位像素。如果是音频数据,则为样位率 */
        public int nStamp;                           /* 时标信息,单位毫秒 */
        public int nType;                            /* 数据类型,T_AUDIO16, T_RGB32, T_YV12 */
        public int nFrameRate;                /* 编码时产生的图像帧率,如果是音频数据则为采样率 */
        public int dwFrameNum;                      /* 帧号 */

        //高版本方法覆盖
        @Override
        protected List getFieldOrder() {
            return Arrays.asList("nWidth","nHeight","nStamp","nType","nFrameRate","dwFrameNum");
        }
    }

}

2.25、对应的controller

HcVideoUtil 就是对应的源码中的VideoDemo,这个文件太大,就不粘贴了

java 复制代码
@RestController
@RequestMapping("monitorvideo")
public class MonitorVideoController {

    @Autowired
    private HcVideoUtil hcVideoUtil;

    @PostMapping("getvideo")
    public void getVideo(String channel, Date startTime, Date endTime, String videoFile) {
        int userId = MonitorUtil.loginDevice();
        hcVideoUtil.dowmloadRecordByTime(userId, Integer.parseInt(channel), startTime, endTime, videoFile);
    }
}

大体上就这些文件,额外需要自己在改改就能跑了,接下来就要说踩坑了,这才是重点(linux部署也放在踩坑里,这个是踩坑最多的)

三、踩坑

3.1、 Structure.getFieldOrder() 错误解决办法

可参考这篇文章,https://www.cnblogs.com/easyidea/p/16490708.html

使用他的工具类需要注意,就像博主说的,有些情况并没有处理,所以有的生成的getFieldOrder里面字段是多的,具体什么情况多的有些忘了,距离完成写这篇文章差不多过了半个月。。。

当然,要是没有用其他的jna版本,这个问题是不会出现的

3.2、linux环境部署

首先将对应的文件上传到响应的文件目录下,对应yaml中的文件路径

启动应该会报找不到一些配置文件,此时我们就参考官方文档

于是,我们就去找这个东西如何添加到变量中

参考这个
https://blog.csdn.net/x_y_csdn/article/details/110521330

完事后执行下这个命令 ldconfig

此时启动应该就不报错了

3.3、docker环境部署

首先说下,以jdk作为基础镜像是很难搞的 ,因为你不知道到底还缺少了多少动态库,所以建议直接以linux作为基础镜像 ,贴下我的dockerfile文件

LD_LIBRARY_PATH=/home/ypx/monitorvideo/lib:$LD_LIBRARY_PATH \ 就是上面提到的环境变量

dockerfile 复制代码
FROM docker.m.daocloud.io/library/ubuntu:20.04

# 设置工作目录和临时卷
WORKDIR /app
VOLUME /tmp

# 更新apk包索引并安装ffmpeg
RUN apt-get update && \
    apt-get install -y ffmpeg && \
    apt-get install -y openjdk-8-jdk 
# 复制本地编译好的 jar 文件(从target目录)
COPY device-supervision.jar app.jar

# 设置时区为中国时区
ENV TZ=Asia/Shanghai \
    LD_LIBRARY_PATH=/home/ypx/monitorvideo/lib:$LD_LIBRARY_PATH \
	#设置中文不乱码的	
	LANG=C.UTF-8 \
	LC_ALL=C.UTF-8
# 暴露端口
EXPOSE 18881

# 设置 Java 运行参数
ENTRYPOINT ["java", \
            "-Djava.security.egd=file:/dev/./urandom", \
            "-XX:+UseContainerSupport", \
            "-XX:MaxRAMPercentage=75.0", \
            "-jar", \
            "/app/app.jar"]

启动命令
docker run -d -p 18881:18881 --privileged=true -v /home/ypx/monitorvideo/:/home/ypx/monitorvideo/ --restart always --name device device:0.0.1

3.4、下载下来的视频可以本地播放,网页上放不了

https://blog.csdn.net/xcg340123/article/details/139825982

简单来说就是网页不支持这种海康格式的视频文件,我是用ffmpeg 转换的,贴下工具类

java 复制代码
@Slf4j
public class VideoUtil {

    /**
     * 海康下载来的视频无法直接在浏览器播放,需要进行转码
     *
     * @param oldPath 旧的地址
     * @param newPath 新的地址
     */
    public static void convert(String oldPath, String newPath, String ffmpegPath) {
        try {
            //需要区分是否为linux环境
            if (OsSelectUtil.isLinux()) {
                ffmpegPath = "ffmpeg";
            }
            List<String> command = new ArrayList<String>();
            command.add(ffmpegPath);
            command.add("-i");
            command.add(oldPath);
            command.add("-c");
            command.add("copy");
            command.add("-an");
            command.add(newPath);
            ProcessBuilder builder = new ProcessBuilder(command);
            Process process = builder.start();
            //等待转换完成
            process.waitFor();
            log.info("转换视频{}完成!", newPath);
            //需要删除源文件
            FileUtil.del(oldPath);
        } catch (Exception e) {
            log.error("转换视频出错!");
            log.error(e.getMessage(), e);
        }
    }


}
相关推荐
小殷要努力刷题!5 分钟前
每日一刷——12.10——学习二叉树解题模式(1)
java·学习·算法·leetcode·二叉树·二叉树的建立
委婉待续9 分钟前
java抽奖系统登录下(三)
java·开发语言·状态模式
先睡13 分钟前
动态sql
java·数据库·sql
程序员大金22 分钟前
基于SpringBoot+Vue的高校电动车租赁系统
前端·javascript·vue.js·spring boot·mysql·intellij-idea·旅游
XY.散人24 分钟前
初识Linux · 日志编写
linux·服务器
fragrans26 分钟前
设置docker镜像加速器
运维·docker·容器
喜欢AC~不爱WA26 分钟前
简单的Java小项目
java·开发语言
互联网动态分析31 分钟前
Apache Kafka:实时数据流处理的强大引擎
java·kafka
泰山小张只吃荷园35 分钟前
期末复习-计算机网络篇
java·网络·网络协议·计算机网络·面试
qq85722263137 分钟前
java+springboot+mysql科研成果管理系统
java·spring boot·mysql