大华SDK+JAVA+4g网络摄像头进行二次开发

Posted Cljxy~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大华SDK+JAVA+4g网络摄像头进行二次开发相关的知识,希望对你有一定的参考价值。

前言

监控,相信大家都不陌生。现在的监控技术发展迅速,国内以海康威视为首的智能视频监控提供商也层出不穷。现在,这些提供商都已经提供了相应的SDK以及API接口,能够很好的支撑我们进行摄像机的二次开发工作。相信大家都有接触过这么一个需求:利用手机可以自己进行摄像机的录像控制,然后在规定的时间内停止录制,然后将这个时间段内的视频保存下来。方便客户端的客户进行查看相应时间段的检控视频。这样就避免了视频一直录制,存储空间不足以承受如此之大的压力,而且也可以降低对于实时拉流的带宽要求。

本文关键词

JAVA、大华SDK、视频拉流、自定义拉流时长、录像保存、4G网络摄像机

硬件设备

本次所用到的摄像机是大华的4G网络摄像机,此摄像机支持利用SIM卡进行4G上网

现在不借助大华自带的软件来控制摄像机,自行开发软件来实现手机端访问摄像头进行拉流的触发,摄像头执行相应的程序进行一段时间的拉流,到了预定的时间进行自动保存视频数据到服务器的相应位置。

技术分析

首先,我们要明确的知道要实现这么一个简单的需求要具备哪些知识。
①手机怎么通过网络触发摄像机?
答:摄像头通过手机进行触发使用get请求的方式访问URL,输入设备ID,告诉服务器我要控制那台摄像头,服务器收到请求进行触发。(此技术属于基本的web开发的基本技能实现起来难度不大)
②通过什么样的方式让摄像头执行自己编写的程序?
答:程序运行在服务器上,同时需要让摄像头能够通过固定IP找到这台服务器。
③摄像头怎么找到服务器?
答:摄像头具备一个功能叫做自动注册(相当于第一次在服务器上使用需要创建一样,让服务器知道这个摄像机)主动注册时输入需要摄像头执行相应程序的服务器的固定IP,以及摄像机的端口号(可自定义)设备登录账号和设备登录密码即可完成注册,这个时候形成长连接就可以相互获取信息和传输数据了,服务器也就可以获取到设备的ID,与手机端发送的设备ID对应就可以触发相应的设备进行工作。

技术逻辑图

使用大华的SDK进行二次开发都会有相应的资料,内部也有很多逻辑流程图,这里提取出相关的那一块内容进行展示:

由上图可见,在我们利用手机触发摄像头的时候服务器内程序执行的逻辑是,首先要进行设备的初始化,然后服务器会寻找在记录中是否注册过本台摄像机,是否有此设备的信息,如果没有摄像机的信息需要进行主动注册。这个时候摄像机会执行访问本服务器固定IP的程序进行自动的注册,注册完成后服务器就会收到本台摄像机的设备ID(此时服务器与摄像头已经形成长连接),然后就可以等待客户端利用手机对摄像头进行触发时发出的信号做比对,让对应的摄像头开始工作。最后就是定义录制的时长,和自动保存的功能,完成后会清理内存。

代码实现

前期准备

首先打开我们心爱的idea(我这里使用的是2021 64位版本的)

进入到项目列表的窗口,选择New Project 新建立一个Spring Boot项目

然后,选择Spring Initializr,在右侧的对话框中进行项目名称的修改(我这里名字为CameraTest),然后根据自己的命名习惯将Group、Artifact、Package name名字做修改(这里仅修改了Grop的名称为com.ruoyi),最后注意自己的java的版本号。修改完后点击Next。

创建成功后我们会看到如图所示的界面
src/main/java/com.ruoyi.cameratest/CameraTestApplication
为springboot的启动程序。

然后我们需要修改下自己的maven的文件保存的路径,之后此项目所要用到的外部jar包都会保存在这些文件夹下。
①我们点击File/Settings


②在搜索框内搜索maven会出现如图所示的内容(Maven home path 为当时我们下载maven的源文件文件位置;User settings file首先找到自己的maven安装位置下conf文件下的settings.xml文件;Local repository找到自己maven安装位置下的自己创建的一个用于保存第三方jar包的文件夹)

例如我的maven安装在D:\\SoftWares\\apache-maven-3.6.1配置如下,完成后点击Apply,点击OK完成设置。

最后我们修改pom.xml文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ruoyi</groupId>
    <artifactId>CameraTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>CameraTest</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>5.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.dahua.netsdk</groupId>
            <artifactId>dahua-netsdk-jni</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <!-- mysql驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>$spring-boot.version</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

这里我们修改了pom.xml文件后需要进行刷新,最后测试一下是否可以成功启动springboot

再来就是进入大华公司的官网找到对应的SDK进行下载

1.消息接收实现类和接口

首先创建如下图所示的文件和类

instructController.java

package com.ruoyi.controller;


import com.ruoyi.service.instructService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/test")
public class instructController 
    @Autowired
    private instructService service;

    public String VideoPath;
    public String getVideoPath() 
        return VideoPath;
    
    public void setVideoPath(String videoPath) 
        VideoPath = videoPath;
    

    @GetMapping("/ss")
    public Map<String,Object> SendMessage(PreInfo info)
        service.SendMessage(info);
        instructController instr = new instructController();

        Map<String, Object> result = new HashMap<String, Object>();
        result.put("videopath", instr.getVideoPath());
        return result;
    




PreInfo.java

package com.ruoyi.controller;

import lombok.Data;

@Data
public class PreInfo 
    private String PreDeviceId;


instructService.java

package com.ruoyi.service;

import com.ruoyi.controller.PreInfo;

public interface instructService 
    void SendMessage(PreInfo info);


ServiceCB.java

package com.ruoyi.demo.frame;

import com.ruoyi.controller.PreInfo;
import com.ruoyi.service.instructService;
import org.springframework.stereotype.Component;

@Component
public class ServiceCB implements instructService 
    @Override
    public void SendMessage(PreInfo info) 
        System.out.println("成功触发!");
    


2.主动注册

首先在src/mian/resources文件中将SDK中的dynamic-lib-load.xml文件复制过来,这个文件是为了满足不同平台下需要加载的C动态库

dynamic-lib-load.xml

<?xml version="1.0" encoding="UTF-8" ?>
<dynamic-lib>
    <win64>
        <lib>avnetsdk</lib>
        <lib>dhconfigsdk</lib>
        <lib>dhnetsdk</lib>
        <lib>dhplay</lib>
        <lib>ImageAlg</lib>
        <lib>Infra</lib>
        <lib>IvsDrawer</lib>
        <lib>StreamConvertor</lib>
        <lib>jninetsdk</lib>
    </win64>
    <win32>
        <lib>avnetsdk</lib>
        <lib>dhconfigsdk</lib>
        <lib>dhnetsdk</lib>
        <lib>dhplay</lib>
        <lib>Infra</lib>
        <lib>ImageAlg</lib>
        <lib>StreamConvertor</lib>
        <lib>jninetsdk</lib>
    </win32>
    <linux64>
        <lib>avnetsdk</lib>
        <lib>dhnetsdk</lib>
        <lib>dhconfigsdk</lib>
        <lib>StreamConvertor</lib>
        <lib>jninetsdk</lib>
    </linux64>
    <linux32>
        <lib>avnetsdk</lib>
        <lib>dhconfigsdk</lib>
        <lib>dhnetsdk</lib>
        <lib>StreamConvertor</lib>
        <lib>jninetsdk</lib>
    </linux32>
    <mac64>
        <lib>avnetsdk</lib>
        <lib>dhnetsdk</lib>
        <lib>dhconfigsdk</lib>
        <lib>StreamConvertor</lib>
    </mac64>
</dynamic-lib>

接着在项目目录中新建子文件夹com.ruoyi/lib

打开我们的大华SDK项目在一个新的窗口中,将我们需要的实现类拷贝过来。如下图是大华SDK-java-x64版本的文件目录。

首先我们将SDK中的lib文件中的所有文件全部拷贝到刚建立的lib中。(如果复制过来出现报错,检查import 中的文件路径名称,因为SDK中的项目名称为com.netsdk我们的不是所以需要修改)。后面功能实现类,由于官方的SDK中的实现类功能太多太杂,我做了整理,大家可以复制我整理后的代码。首先我们需要主动注册的实现类AutoRegisterModule.java,登录设备的实现类LoginModule.java,设备的信息类DeviceInfo.java

AutoRegisterModule.java

package com.ruoyi.demo.module;

import com.ruoyi.demo.module.LoginModule;
import com.ruoyi.lib.NetSDKLib.LLong;
import com.ruoyi.lib.NetSDKLib.fServiceCallBack;
import com.ruoyi.lib.NetSDKLib;
import com.ruoyi.lib.ToolKits;

public class AutoRegisterModule 

    // 监听服务句柄
    public static LLong mServerHandler = new LLong(0);

    // 设备信息
    public static NetSDKLib.NET_DEVICEINFO_Ex m_stDeviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();

    /**
     * 开启服务
     *
     * @param address  本地IP地址
     * @param port     本地端口, 可以任意
     * @param callback 回调函数
     */
    public static boolean startServer(String address, int port, fServiceCallBack callback) 
        //调用监听接口实现服务的开启
        mServerHandler = LoginModule.netsdk.CLIENT_ListenServer(address, port, 1000, callback, null);
        if (0 == mServerHandler.longValue()) 
            System.err.println("Failed to start server." + ToolKits.getErrorCodePrint());
         else 
            System.out.printf("Start server, [Server address %s][Server port %d]\\n", address, port);
        
        return mServerHandler.longValue() != 0;
    

    /**
     * 结束服务
     */
    public static boolean stopServer() 
        boolean bRet = false;

        if (mServerHandler.longValue() != 0) 
            bRet = LoginModule.netsdk.CLIENT_StopListenServer(mServerHandler);
            mServerHandler.setValue(0);
            System.out.println("Stop server!");
        

        return bRet;
    


LoginModule.java

package com.ruoyi.demo.module;

import com.ruoyi.lib.NetSDKLib;
import com.ruoyi.lib.NetSDKLib.LLong;
import com.ruoyi.lib.NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY;
import com.ruoyi.lib.NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY;
import com.ruoyi.lib.ToolKits;

import java.io.File;

/**
 * 登陆接口实现
 * 主要有 :初始化、登陆、登出功能
 */
public class LoginModule 

	public static NetSDKLib netsdk 		= NetSDKLib.NETSDK_INSTANCE;

	// 设备信息
	public static NetSDKLib.NET_DEVICEINFO_Ex m_stDeviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();
	
	// 登陆句柄
	public static LLong m_hLoginHandle = new LLong(0);   
	
	private static boolean bInit    = false;
	private static boolean bLogopen = false;
	
	/**
	 * 初始化
	 */
	public static boolean init(NetSDKLib.fDisConnect disConnect, NetSDKLib.fHaveReConnect haveReConnect) 	
		bInit = netsdk.CLIENT_Init(disConnect, null);
		if(!bInit) 
			System.out.println("Initialize SDK failed");
			return false几种场景需求

根据SignalR的设计规则,Client端可以主动调用服务端Hub的多个方法,但是客户端被动接收消息的方法只能有一个
根据门禁功能需求,我们将Client分为两组:

  • doorclient:指Web客户端
  • doorserver:指门禁服务端

这样便于服务端区分Web客户端和门禁服务端这两类client。

技术分享图片

项目中主要实现以下几个场景:

Web客户端初始加载,刷新全部门禁状态

技术分享图片

  • A. 浏览器主动请求初始化门禁状态;
  • B. web服务端接收信息,并转发到doorserver组;
  • C. 门禁服务查询门禁状态,主动发送门禁状态列表;
  • D. web服务端接收消息,并根据connectId转发给指定浏览器。
//web客户端
chat.server.sendMessageByBrowser();

// 定义AddMessage供服务器调用
chat.client.AddMessage = function (result) {
    for (var i = 0; i < result.length; i++) {
        try {
            //前端响应门禁状态变化
        } catch (error) {
        }
    }
};

//web服务端
/// <summary>
/// 浏览器发送消息,向doorServer请求所有门禁状态,用于初始化门禁状态
/// </summary>
/// <param name="name"></param>
public void SendMessageByBrowser()
{
    var messageList = new List<DoorStateInfo>();
    var dc = new DoorStateInfo {ConnectId = Context.ConnectionId};
    messageList.Add(dc);
    Clients.Group("doorserver").AddMessage(messageList);
}


/// <summary>
/// 门禁服务发送多条开关门消息给某个浏览器,hubserver转发给浏览器
/// 浏览器id放在messagelist[0].ConnectId
/// </summary>
/// <param name="name"></param>
/// <param name="messageList"></param>
public void SendManyMessageByDoorServer(string name, List<DoorStateInfo> messageList)
{
    Clients.Client(messageList[0].ConnectId).AddMessage(messageList);
}

//门禁服务端
// 创建一个集线器代理对象
HubProxy = Connection.CreateHubProxy("DoorAlarmHub");

// 供服务端调用,将消息输出到消息列表框中
HubProxy.On<string, List<DoorStateInfo>>("AddMessage", (message) =>
{
    var alarmMsg = new AlarmMsg
    {
        Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
        AlarmInfo = message,
        AlarmType = EM_ALARM_TYPE.ALARM_SIGNALR_QUERY
    };
    if (message != null && message.Count > 0) 
    {
        //无门禁状态,为门禁查询命令
        if (message[0].DoorState == EM_NET_DOOR_STATUS_TYPE.EM_NET_DOOR_STATUS_UNKNOWN)
        {
            alarmMsg.AlarmType = EM_ALARM_TYPE.ALARM_SIGNALR_QUERY;
        }
        //有门禁状态,为门禁控制命令
        else 
        {
            alarmMsg.AlarmType = EM_ALARM_TYPE.ALARM_SIGNALR_CONTROL;
        }
        m_AlarmMsgQueue.Enqueue(alarmMsg);                    
    }
});

Web端请求打开某个门

技术分享图片

  • A. 浏览器主动请求开门;
  • B. web服务端接收信息,并转发到doorserver组;
  • C. 门禁服务发送开门命令,接收到门禁状态变化消息后,主动发送门禁状态变化信息;
  • D. web服务端接收信息,并转发到doorclient组。
//web服务端
/// <summary>
/// 浏览器端调用,请求开门
/// </summary>
public void SendOpenDoorByBrowser(/*业务参数用于标识门禁*/)
{
    
    var messageList = new List<DoorStateInfo>();
    
    //业务处理
    ...
    
    Clients.Group("doorserver").AddMessage("doorserver", messageList);
}


/// <summary>
/// 门禁服务发送开关门消息,hubserver转发给浏览器
/// </summary>
/// <param name="name"></param>
/// <param name="message"></param>
public void SendOneMessageByDoorServer(string name, DoorStateInfo message)
{
    
    //业务处理
    Clients.Group("doorclient").AddMessage(message);            
}

门禁服务发送门禁状态变化

技术分享图片

  • A.这种情况主要发生在门禁刷卡等开门操作,引起的门禁状态变化,门禁服务接收到消息后,主动发送门禁状态变化信息;
  • B.web服务端接收信息,并转发到doorclient组。

几个问题说明

AddMessage方法

客户端使用AddMessage接收server转发来的消息,由于client监听接收消息只能有一个方法,但是存在单个门禁状态变化消息和多个两种情况。因此AddMessage的消息参数统一使用List<Message>,然后在server端转发时相应加入业务标记,便于client端处理。

门禁服务程序的事件机制

门禁服务程序采用事件机制

  • 刷卡等开发事件接收到后,门禁服务会主动进行消息发送,通知所有浏览器更新相应门禁状态;
  • 浏览器初始化请求所有门禁状态时,由于消息通信是不能直接返回的,因此信息传递时携带connectId,用于下一条消息确认发送对象;
  • 与初始化请求一样,浏览器发送开门命令后,门禁服务接收到开门命令发送给大华门禁服务器后,会在收到门禁状态变化事件时,向doorclient组发送消息。两条消息是相对独立的。

以上是关于大华SDK+JAVA+4g网络摄像头进行二次开发的主要内容,如果未能解决你的问题,请参考以下文章

JavaCV音视频开发宝典:使用javacv读取GB28181海康大华平台和网络摄像头sdk回调视频码流并转码推流rtmp流媒体服务

大华门禁SDK二次开发

大华网络摄像机 如何嵌入到自己的网页中

JavaCV开发详解之21补充篇1:使用javacv读取海康大华平台和海康大华摄像头sdk回调视频裸流并解析

JavaCV开发详解之21补充篇1:使用javacv读取海康大华平台和海康大华摄像头sdk回调视频裸流并解析

java调用大华摄像头,web端播放和云控制,谁研究过?谢谢……