[实战]Springboot与GB28181摄像头对接。摄像头注册上线(一),Springboot对接GB28181摄像头实战教程,摄像头注册上线指南(一)

马肤

温馨提示:这篇文章已超过466天没有更新,请注意相关的内容是否还可用!

摘要:本文将介绍Springboot与GB28181摄像头对接的实战操作,重点阐述摄像头注册上线的过程。通过本文,读者将了解如何使Springboot应用程序与符合GB28181标准的摄像头进行通信,实现摄像头的注册和上线功能,为后续的监控和管理操作打下基础。

与支持国标摄像头对接

  • 前言:不想看教程?
  • 1、准备阶段
    • 1.1、我们会学到什么?
    • 1.2、创建项目
    • 1.3、pom中用到的依赖
    • 1.4 打开摄像头的网址(了解配置方式)
    • 2、代码编写
      • 2.1、增加项目配置
      • 2.2、在config目录下创建SipConfig
      • 2.3、在service目录下创建SipService
      • 2.4、在adapter目录下创建如下类
      • 2.5、在listener准备如下类
      • 2.7、准备基础的枚举类(enums目录下)
      • 3、按照项目配置摄像头的SIP
      • 4、启动项目

        前言:不想看教程?

           想直接拿源码?Qq:1101165230

        1、准备阶段

          电脑和idea这个肯定是要准备的,然后准备几台(或一台)支持国标对接的摄像头。要知道摄像头的访问地址账号和密码。

        1.1、我们会学到什么?

           在这个项目中我们会用到一些设计模式以及一些注解平时可能不太常用的注解。我们还额外的知道了与物联网相关的一些对接知识。(文章中可能更多的是代码上的,为了便于大家直接CV。对于理论知识的大家可以私我,我可以出一期视频讲解这个项目)

        1.2、创建项目

          我们在这里创建的是一个WebFlux的项目,便须后期处理设备的命令。

        结构如下:(ps: 在idea的Terminal中输入tree或者tree -f,-f会包含目录下文件名)

        tree
        
        tree -f
        
        tree /f >> D:/tree.txt
        
        gb28181-sg
        │
        ├─src
        │  ├─main
        │  │  ├─java
        │  │  │  └─org
        │  │  │      └─ougnuhs
        │  │  │          └─gb
        │  │  │              │  GbApplication.java 
        │  │  │              ├─adapter
        │  │  │              ├─config 
        │  │  │              ├─controller     
        │  │  │              ├─enums
        │  │  │              │  └─base        
        │  │  │              ├─listener    
        │  │  │              ├─service     
        │  │  │              └─utils                   
        │  │  └─resources         
        │  └─test
        │      └─java
        └─target
        

        1.3、pom中用到的依赖

         &emsp(这里只放了dependencies)SIP依赖特别需要。

         
                
                
                    org.springframework.boot
                    spring-boot-starter
                    
                        
                            spring-boot-starter-logging
                            org.springframework.boot
                        
                    
                
                
                    org.springframework.boot
                    spring-boot-starter-aop
                
                
                    org.springframework.boot
                    spring-boot-starter-webflux
                
                
                    org.projectlombok
                    lombok
                    true
                
                
                
                
                    javax.sip
                    jain-sip-ri
                    1.3.0-91
                
                
                    log4j
                    log4j
                    1.2.17
                
                
                
                    org.springframework.boot
                    spring-boot-starter-log4j2
                
            
        

        1.4 打开摄像头的网址(了解配置方式)

        这里大家先看一下,熟悉摄像头配置位置,不用填写。

        [实战]Springboot与GB28181摄像头对接。摄像头注册上线(一),Springboot对接GB28181摄像头实战教程,摄像头注册上线指南(一) 第1张

        我这里用的是海康摄像头,摄像头的网页配置地址就是摄像头的ip地址。我们可以看到在平台接入的地方需要填写SIP的一些信息,以及提供服务的ip。

        [实战]Springboot与GB28181摄像头对接。摄像头注册上线(一),Springboot对接GB28181摄像头实战教程,摄像头注册上线指南(一) 第2张

        这个是我之前配置过的,对于新摄像头可能没有,不过没关系,我们在项目中配置成什么这里就填写成什么即可。

        2、代码编写

        2.1、增加项目配置

          在前面我们创建好了项目,并知道了摄像头SIP的配置位置。接着我们在application.yml文件中增加我们的sip配置。

        application.yml

        server:
          port: 8087
        sip:
          ip: 192.168.20.78
          port: 5060
          id: 34020000002000000001
          domain: 3402000000
          password: sg@123456
        

        2.2、在config目录下创建SipConfig

          需要在config目录下创建。

        import lombok.Getter;
        import lombok.Setter;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import org.springframework.context.annotation.Configuration;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description SipConfig
         * @date 2024/3/22 10:44
         */
        @Getter
        @Setter
        @Configuration
        @ConfigurationProperties(prefix = SipConfig.SIP)
        public class SipConfig {
            public static final String SIP = "sip";
            /**
             * 默认使用 0.0.0.0
             */
            private String monitorIp = "0.0.0.0";
            private String ip;
            private Integer port;
            private String id;
            private String password;
            private String domain;
        }
        

        2.3、在service目录下创建SipService

        import gov.nist.javax.sip.SipProviderImpl;
        import gov.nist.javax.sip.SipStackImpl;
        import lombok.extern.slf4j.Slf4j;
        import org.ougnuhs.gb.config.SipConfig;
        import org.ougnuhs.gb.listener.MySipListener;
        import org.ougnuhs.gb.utils.SIPDefaultProperties;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.context.annotation.DependsOn;
        import javax.sip.*;
        import java.util.Properties;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description SipService need SipConfig
         * @date 2024/3/22 11:02
         */
        @Slf4j
        @Configuration
        public class SipService {
            @Autowired
            private SipConfig sipConfig;
            @Autowired
            private MySipListener mySipListener;
            private SipFactory sipFactory;
            private SipStackImpl sipStack;
            @Bean("sipFactory")
            SipFactory createSipFactory(){
                sipFactory = SipFactory.getInstance();
                sipFactory.setPathName("gov.nist");
                return sipFactory;
            }
            @Bean("sipStack")
            @DependsOn("sipFactory")
            SipStackImpl createSipStackImpl() throws PeerUnavailableException {
                Properties sipProperties = SIPDefaultProperties.getSipProperties(sipConfig.getMonitorIp(), false);
                sipStack =(SipStackImpl) sipFactory.createSipStack(sipProperties);
                return sipStack;
            }
            /**
             * 监听TCP 对于产生的异常需要在这个地方处理
             * @return
             * @throws Exception
             */
            @Bean("tcpSipProvider")
            @DependsOn("sipStack")
            public SipProviderImpl startTcpListener() throws Exception {
                // 创建SIP Provider并绑定到SIP Stack
                ListeningPoint lp = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getPort(), "TCP");
                SipProviderImpl sipProvider = (SipProviderImpl)sipStack.createSipProvider(lp);
                // 注册SIP Servlet
                sipProvider.addSipListener(mySipListener);
                log.info("TCP Start SUCCESS");
                return sipProvider;
            }
            /**
             * 监听TCP 对于产生的异常需要在这个地方处理
             * @return
             * @throws Exception
             */
            @Bean("udpSipProvider")
            @DependsOn("sipStack")
            public SipProviderImpl startUdpListener() throws Exception {
                // 创建SIP Provider并绑定到SIP Stack
                ListeningPoint lp = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getPort(), "UDP");
                SipProviderImpl sipProvider =  (SipProviderImpl)sipStack.createSipProvider(lp);
                // 注册SIP Servlet
                sipProvider.addSipListener(mySipListener);
                log.info("UDP Start SUCCESS");
                return sipProvider;
            }
        }
        

        2.4、在adapter目录下创建如下类

        import org.springframework.beans.factory.InitializingBean;
        import javax.sip.RequestEvent;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description SIP事件处理接口
         * @date 2024/3/22 13:59
         */
        public interface ISIPEventHandler extends InitializingBean {
            /**
             * 事件处理方法
             * @param requestEvent
             */
            void process(RequestEvent requestEvent);
        }
        
        @Slf4j
        public class SIPFactory {
            public static Map requestHandlerMap = new ConcurrentHashMap();
            public static void register(String key, ISIPEventHandler handler){
                if(!StringUtils.hasText(key) || ObjectUtils.isEmpty(handler)){
                    log.info("error: key or handler is null");
                    return;
                }
                requestHandlerMap.put(key, handler);
                log.info("id:{}, handler:{}, register success", key, handler );
            }
            public static ISIPEventHandler getInvokeStrategy(String key) throws NoSuchMethodException {
                if(!requestHandlerMap.containsKey(key)){
                    throw new NoSuchMethodException("未找到执行该方法的策略 key: " + key);
                }
                return requestHandlerMap.get(key);
            }
        }
        
        package org.ougnuhs.gb.adapter;
        import gov.nist.javax.sip.SipProviderImpl;
        import gov.nist.javax.sip.SipStackImpl;
        import gov.nist.javax.sip.message.SIPRequest;
        import gov.nist.javax.sip.stack.SIPServerTransactionImpl;
        import lombok.extern.slf4j.Slf4j;
        import org.ougnuhs.gb.enums.TransportTypeEnum;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.beans.factory.annotation.Qualifier;
        import javax.sip.*;
        import javax.sip.header.ViaHeader;
        import javax.sip.message.MessageFactory;
        import javax.sip.message.Request;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description 模板方法
         * @date 2024/3/22 16:43
         */
        @Slf4j
        public abstract class SIPRequestProcessorParent {
            @Autowired
            @Qualifier(value="tcpSipProvider")
            private SipProviderImpl tcpSipProvider;
            @Autowired
            @Qualifier(value="udpSipProvider")
            private SipProviderImpl udpSipProvider;
            /**
             * 根据 RequestEvent 获取 ServerTransaction
             * @param evt
             * @return
             */
            public ServerTransaction getServerTransaction(RequestEvent evt) {
                Request request = evt.getRequest();
                SIPServerTransactionImpl serverTransaction = (SIPServerTransactionImpl)evt.getServerTransaction();
                // 判断TCP还是UDP
                boolean isTcp = transportTypeIsTcp(request);
                if (serverTransaction != null && serverTransaction.getOriginalRequest() == null) {
                    serverTransaction.setOriginalRequest((SIPRequest) evt.getRequest());
                }
                if (serverTransaction == null) {
                    try {
                        if (isTcp) {
                            SipStackImpl stack = (SipStackImpl)tcpSipProvider.getSipStack();
                            serverTransaction = (SIPServerTransactionImpl) stack.findTransaction((SIPRequest)request, true);
                            if (serverTransaction == null) {
                                serverTransaction = (SIPServerTransactionImpl)tcpSipProvider.getNewServerTransaction(request);
                            }
                        } else {
                            SipStackImpl stack = (SipStackImpl)udpSipProvider.getSipStack();
                            serverTransaction = (SIPServerTransactionImpl) stack.findTransaction((SIPRequest)request, true);
                            if (serverTransaction == null) {
                                serverTransaction = (SIPServerTransactionImpl)udpSipProvider.getNewServerTransaction(request);
                            }
                        }
                    } catch (TransactionAlreadyExistsException e) {
                        log.error(e.getMessage());
                    } catch (TransactionUnavailableException e) {
                        log.error(e.getMessage());
                    }
                }
                return serverTransaction;
            }
            /**
             * 判断通讯的方式是TCP吗
             * @param request
             * @return
             */
            private boolean transportTypeIsTcp(Request request){
                ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
                String transport = reqViaHeader.getTransport();
                log.info("通讯方式是:{}", transport);
                return transport.equalsIgnoreCase(TransportTypeEnum.TCP.getValue());
            }
            //FIXME 在SipService中不是有一个这个对象吗?
            public MessageFactory getMessageFactory() {
                try {
                    return SipFactory.getInstance().createMessageFactory();
                } catch (PeerUnavailableException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }
        
        package org.ougnuhs.gb.adapter;
        import gov.nist.javax.sip.RequestEventExt;
        import gov.nist.javax.sip.address.AddressImpl;
        import gov.nist.javax.sip.address.SipUri;
        import gov.nist.javax.sip.clientauthutils.DigestServerAuthenticationHelper;
        import lombok.extern.slf4j.Slf4j;
        import org.ougnuhs.gb.config.SipConfig;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;
        import org.springframework.util.ObjectUtils;
        import javax.sip.InvalidArgumentException;
        import javax.sip.RequestEvent;
        import javax.sip.ServerTransaction;
        import javax.sip.SipException;
        import javax.sip.header.AuthorizationHeader;
        import javax.sip.header.ExpiresHeader;
        import javax.sip.header.FromHeader;
        import javax.sip.header.ViaHeader;
        import javax.sip.message.Request;
        import javax.sip.message.Response;
        import java.security.NoSuchAlgorithmException;
        import java.text.ParseException;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description 提供注册事件的处理器
         * TODO 返回Response; 以及错误信息返回需要统一,不能随便写返回的错误!
         * @date 2024/3/22 14:17
         */
        @Slf4j
        @Component
        public class SIPRegisterEventHandler extends SIPRequestProcessorParent implements ISIPEventHandler {
            @Autowired
            private SipConfig sipConfig;
            private static final String KEY = "REGISTER";
            private Response response;
            @Override
            public void afterPropertiesSet() throws Exception {
                SIPFactory.register(KEY, this);
            }
            @Override
            public void process(RequestEvent requestEvent) {
                RequestEventExt requestEventExt = (RequestEventExt) requestEvent;
                String ipAddress = requestEventExt.getRemoteIpAddress() + ":" + requestEventExt.getRemotePort();
                log.info("ipAddress:{}", ipAddress);
                //取出request对象
                Request request = requestEventExt.getRequest();
                //FromHeader
                fromHeader(request);
                //AuthorizationHeader
                authorizationHeader(request);
                //ExpiresHeader
                expiresHeader(request);
                //ViaHeader
                viaHeader(request);
                //TODO 假设一切都ok。我们直接进行回复成功
                try {
                    response = getMessageFactory().createResponse(Response.OK, request);
                    sendResponse(requestEvent, response);
                }catch (ParseException parseException){
                    parseException.printStackTrace();
                    log.error("SIPRegisterEventHandler createResponse fail");
                }
            }
            /**
             * 注册参数-主要是为了拿到SIP用户名,这个用户不能重复,如果平台需要级联多个摄像头,该SIP必须唯一
             * deviceId 对应的就是SIP用户名
             * @param request
             */
            private void fromHeader(Request request){
                FromHeader fromHeader =(FromHeader) request.getHeader(FromHeader.NAME);
                AddressImpl addressImpl = (AddressImpl) fromHeader.getAddress();
                SipUri sipUri = (SipUri)addressImpl.getURI();
                String deviceId = sipUri.getUser();
                log.info("fromHeader:{} \t addressImpl:{} \t sipUrl:{} \t deviceId:{}",
                        fromHeader, addressImpl, sipUri, deviceId);
            }
            /**
             * 携带参数
             * @param request
             */
            private void expiresHeader(Request request){
                ExpiresHeader expiresHeader =(ExpiresHeader) request.getHeader(ExpiresHeader.NAME);
                if(ObjectUtils.isEmpty(expiresHeader)){
                    log.error("[注册请求] {}", Response.BAD_REQUEST);
                    return;
                }
                if(expiresHeader.getExpires() == 0){
                    log.error("[注册请求] 用户申请注销!");
                    return;
                }else{
                    log.info("[注册请求] 用户申请注册!");
                    //TODO 放入数据库
                }
                log.info("expiresHeader:{}", expiresHeader);
            }
            /**
             * 口令密码等信息
             * @param request
             */
            private void authorizationHeader(Request request) {
                AuthorizationHeader authorizationHeader =(AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
                if (ObjectUtils.isEmpty(authorizationHeader) && ObjectUtils.isEmpty(sipConfig.getPassword())){
                    log.error("[注册请求] 未携带授权");
                    return;
                }
                try{
                    boolean passwordCorrect = ObjectUtils.isEmpty(sipConfig.getPassword()) ||
                            new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword());
                    if(!passwordCorrect){
                        log.error("[注册请求] 密码/SIP服务器ID错误, 回复403");
                        return;
                    }
                }catch (NoSuchAlgorithmException e){
                    log.error("[注册请求] 验证授权发生错误!");
                    return;
                }
                log.info("authorizationHeader:{}", authorizationHeader.toString());
            }
            /**
             * 主要用来查看是UDP还是TCP传输
             * @param request
             */
            private void viaHeader(Request request){
                ViaHeader viaHeader =(ViaHeader) request.getHeader(ViaHeader.NAME);
                String transport = viaHeader.getTransport();
                log.info("viaHeader:{} \t transport:{}", viaHeader, transport);
            }
            /**
             * 响应回复
             * @param requestEvent
             * @param response
             * @throws InvalidArgumentException
             * @throws SipException
             */
            private void sendResponse(RequestEvent requestEvent, Response response){
                ServerTransaction serverTransaction = getServerTransaction(requestEvent);
                if (serverTransaction == null) {
                    log.warn("[回复失败]:{}", response);
                    return;
                }
                try {
                    serverTransaction.sendResponse(response);
                    log.info("SIPRegisterEventHandler sendResponse success");
                }catch (InvalidArgumentException | SipException exception){
                    exception.printStackTrace();
                    log.error("[回复发生异常]:{}", response);
                }finally {
                    if (serverTransaction.getDialog() != null) {
                        serverTransaction.getDialog().delete();
                    }
                }
            }
        }
        

        2.5、在listener准备如下类

        import javax.sip.SipListener;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description MySipService 继承 SipListener
         * @date 2024/3/22 11:14
         */
        public interface MySipListener extends SipListener {
        }
        
        import lombok.extern.slf4j.Slf4j;
        import org.ougnuhs.gb.adapter.SIPFactory;
        import org.springframework.stereotype.Service;
        import javax.sip.*;
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description IMySipService 实现 MySipService
         * @date 2024/3/22 11:16
         */
        @Slf4j
        @Service
        public class MySipListenerImpl implements MySipListener{
            /**
             * 摄像头上报事件
             * @param requestEvent
             */
            @Override
            public void processRequest(RequestEvent requestEvent) {
                String method = requestEvent.getRequest().getMethod();
                try {
                    SIPFactory.getInvokeStrategy(method).process(requestEvent);
                }catch (Exception e){
                    log.error("processRequest error", e);
                }
            }
            @Override
            public void processResponse(ResponseEvent responseEvent) {
                log.info("processResponse");
            }
            @Override
            public void processTimeout(TimeoutEvent timeoutEvent) {
                log.info("processTimeout");
            }
            @Override
            public void processIOException(IOExceptionEvent exceptionEvent) {
                log.info("processIOException");
            }
            @Override
            public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
                log.info("processTransactionTerminated");
            }
            @Override
            public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
                log.info("processDialogTerminated");
            }
        }
        

        2.7、准备基础的枚举类(enums目录下)

        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description 传输方式类型
         * @date 2024/3/27 9:52
         */
        public enum TransportTypeEnum implements BaseEnum {
            TCP("TCP"),
            UDP("UDP");
            private String value;
            TransportTypeEnum(String value) {
                this.value = value;
            }
            public String getValue() {
                return value;
            }
            @Override
            public String getName() {
                return null;
            }
            public void setValue(String value) {
                this.value = value;
            }
        }
        
        /**
         * @author by Guoshun
         * @version 1.0.0
         * @description 枚举基类 TODO 没有增加反序列化
         * @date 2024/3/27 9:54
         */
        public interface BaseEnum {
            Object getValue();
            default String getText() {
                return null;
            }
            String getName();
            /**
             * 根据枚举value生成枚举
             * @author liyuanxi
             * @date 2019/4/17 11:47
             */
            static  E valueOf(Object value, Class clazz) {
                E em;
                E[] enums = clazz.getEnumConstants();
                String enumName = null;
                for (BaseEnum e : enums) {
                    if (e.getValue().equals(value)) {
                        enumName = e.getName();
                    }
                }
                if (null != enumName) {
                    em = Enum.valueOf(clazz, enumName);
                } else {
                    throw new RuntimeException(value + "未匹配上对应的枚举");
                }
                return em;
            }
            /**
             * 根据枚举name生成枚举类型
             * @author liyuanxi
             * @date 2019/4/17 11:47
             */
            static  E nameOf(String name, Class clazz) {
                return Enum.valueOf(clazz, name);
            }
        }
        

        以上全部cv走。

        3、按照项目配置摄像头的SIP

        [实战]Springboot与GB28181摄像头对接。摄像头注册上线(一),Springboot对接GB28181摄像头实战教程,摄像头注册上线指南(一) 第3张

        需要说明的是多个摄像头的话,SIP用户名请保证唯一。

        4、启动项目

          启动项目后,设备会自动注册上来,我方会告知他注册成功。


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码