java 对接国标摄像头流程、代码整合 springboot SIP -GB2818

news2024/11/27 12:51:00

java 对接设备的代码资料较少,这里介绍GB2818的基本对接流程,有用自取👇

  • java负责SIP信令的注册交互,推流、拉流鉴权
  • 摄像头负责推流、流媒体负责拉流、转码

wvp-GB28181-pro项目 ,如果java对接各种摄像头,这个项目很👍,比较完善,可参考。进去star 支持一波

做到需要播放摄像头视频需要:

  • 摄像头:视频摄像数据的输出端,协议输出端。
  • SIP服务端:java开发sip信令注册交互,流媒体(推流、播放)鉴权
  • 流媒体服务器:负责注册rtp流媒体,摄像头推流输出端

概念:

国标协议2818 组成:

  • SIP:会话初始协议(Session Initiation Protocol),是一个应用层的 点对点协议,用于初始、管理和终止网络中的语音和视频会话,是 GB28181 的核心之一
    1
  • 流媒体:音视频流的传输与转换

sip服务端:

  • java实现一般采用 JAIN-SIP
  • 项目启动时,初始化tpc、udp端口监听,当有接收sip信令时,会触发相关请求事件

流媒体:

  • 用作视频、音频流的接入服务的,拉流、推流、解编码服务端;
  • 一般用 ZLMediaKit 用做流媒体服务器,C++11 性能高、部署较方便
  • https://gitee.com/xia-chu/ZLMediaKit?_from=gitee_search在这里插入图片描述

配置摄像头接入:
1

sip注册交互流程:

  • 在这里插入图片描述
    在这里插入图片描述

拉流交互过程:

在这里插入图片描述

摄像头返回推流端口:

在这里插入图片描述

invite 抓包:
1

部分代码示例:

<!-- sip协议栈 -->
  <dependency>
       <groupId>javax.sip</groupId>
       <artifactId>jain-sip-ri</artifactId>
       <version>1.3.0-91</version>
   </dependency>

初始监听端口:

  • 参考 panll / wvp-GB28181-pro 项目,👆上面有地址
import com.config.SipConfig;
import com.sip.uitl.SipUtil;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import javax.sip.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Sip信令服务器启动监听
 * @author heyonghao
 * @date 2023/5/11
 */
@Slf4j
@Component
public class SipInitListen implements CommandLineRunner {

    @Resource
    private SipConfig sipConfig;

    /**
     * sip通信,消息监听处理
     */
    @Resource
    private SipProcessListener sipProcessListener;

    private SipFactory sipFactory;

    /**
     * tcp-sip提供
     */
    private final Map<String, SipProviderImpl> tcpSipProviderMap = new ConcurrentHashMap<>();

    /**
     * udp-sip提供
     */
    private final Map<String, SipProviderImpl> udpSipProviderMap = new ConcurrentHashMap<>();


    @Override
    public void run(String... args) {
        List<String> monitorIps = new ArrayList<>();
        // 使用逗号分割多个ip
        String separator = ",";
        if (sipConfig.getIp().indexOf(separator) > 0) {
            String[] split = sipConfig.getIp().split(separator);
            monitorIps.addAll(Arrays.asList(split));
        }else {
            monitorIps.add(sipConfig.getIp());
        }

        sipFactory = SipFactory.getInstance();
        sipFactory.setPathName("gov.nist");
        if (monitorIps.size() > 0) {
            for (String monitorIp : monitorIps) {
                addListeningPoint(monitorIp, sipConfig.getPort());
            }
            if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) {
                System.exit(1);
            }
        }
    }

    /**
     * 添加 监听ip
     * @param monitorIp 监听ip
     * @param port      端口
     */
    private void addListeningPoint(String monitorIp, int port){
        //sip协议栈
        SipStackImpl sipStack;
        try {
            sipStack = (SipStackImpl)sipFactory.createSipStack(SipUtil.defaultProperties(monitorIp, Boolean.FALSE));
        } catch (PeerUnavailableException e) {
            e.printStackTrace();
            log.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
            return;
        }
        try {
            //创建 TCP传输监听
            ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP");
            //tcp 消息处理实现
            SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
            tcpSipProvider.setDialogErrorsAutomaticallyHandled();
            tcpSipProvider.addSipListener(sipProcessListener);
            tcpSipProviderMap.put(monitorIp, tcpSipProvider);
            log.info("[Sip Server] tcp://{}:{} 启动成功", monitorIp, port);
        } catch (TransportNotSupportedException
                 | TooManyListenersException
                 | ObjectInUseException
                 | InvalidArgumentException e) {
            log.error("[Sip Server] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
                    , monitorIp, port);
        }
        try {
            //创建 UDP传输监听
            ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP");
            //udp 消息处理实现
            SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
            udpSipProvider.addSipListener(sipProcessListener);
            udpSipProviderMap.put(monitorIp, udpSipProvider);

            log.info("[Sip Server] udp://{}:{} 启动成功", monitorIp, port);
        } catch (TransportNotSupportedException
                 | TooManyListenersException
                 | ObjectInUseException
                 | InvalidArgumentException e) {
            log.error("[Sip Server] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
                    , monitorIp, port);
        }
    }

    public SipFactory getSipFactory() {
        return sipFactory;
    }

    public SipProviderImpl getUdpSipProvider(String ip) {
        if (ObjectUtils.isEmpty(ip)) {
            return null;
        }
        return udpSipProviderMap.get(ip);
    }

    public SipProviderImpl getUdpSipProvider() {
        if (udpSipProviderMap.size() != 1) {
            return null;
        }
        return udpSipProviderMap.values().stream().findFirst().get();
    }

    public SipProviderImpl getTcpSipProvider() {
        if (tcpSipProviderMap.size() != 1) {
            return null;
        }
        return tcpSipProviderMap.values().stream().findFirst().get();
    }

    public SipProviderImpl getTcpSipProvider(String ip) {
        if (ObjectUtils.isEmpty(ip)) {
            return null;
        }
        return tcpSipProviderMap.get(ip);
    }

    public String getLocalIp(String deviceLocalIp) {
        if (!ObjectUtils.isEmpty(deviceLocalIp)) {
            return deviceLocalIp;
        }
        return getUdpSipProvider().getListeningPoint().getIPAddress();
    }
}

摄像头消息监听

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sip.*;
import javax.sip.header.CSeqHeader;
import javax.sip.message.Response;

/**
 * 消息回调处理
 *
 * @author heyonghao
 * @date 2023/5/11
 */
@Component
@Slf4j
public class SipProcessListener implements SipListener {
    @Resource
    private SipEventService sipEventService;

    /**
     * 服务收到请求出发
     * @param requestEvent - 请求事件
     */
    @Override
    public void processRequest(RequestEvent requestEvent) {
        log.info("收到摄像机服务请求");
        String method = requestEvent.getRequest().getMethod();
        log.info("method:"+method);
        if (method.equals("REGISTER")){
            sipEventService.requestRegister(requestEvent);
        }
        if (method.equals("MESSAGE")){
            sipEventService.requestMessage(requestEvent);
        }
        if (method.equals("BYE")){
            sipEventService.requestBye(requestEvent);
        }
    }

    /**
     * 服务收到响应出发
     * @param responseEvent - 响应事件
     */
    @Override
    public void processResponse(ResponseEvent responseEvent) {
        log.info("收到摄像机服务响应");
        Response response = responseEvent.getResponse();
        int status = response.getStatusCode();
        // Success
        if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) {
            CSeqHeader cseqHeader = (CSeqHeader) responseEvent.getResponse().getHeader(CSeqHeader.NAME);
            String method = cseqHeader.getMethod();
            log.info("method:"+method);
            sipEventService.responseInvite(responseEvent);
        } else if ((status >= Response.TRYING) && (status < Response.OK)) {
            // 增加其它无需回复的响应,如101、180等
        } else {
            log.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase());
            if (responseEvent.getDialog() != null) {
                responseEvent.getDialog().delete();
            }
        }
    }

    /**
     * 超时回调
     * @param timeoutEvent - 超时事件
     */
    @Override
    public void processTimeout(TimeoutEvent timeoutEvent) {
        log.info("收到摄像机 超时回调");
    }

    /**
     * IO异常的回调
     * @param exceptionEvent - 异常事件
     */
    @Override
    public void processIOException(IOExceptionEvent exceptionEvent) {
        log.info("收到摄像机 IO异常的回调");
    }

    /**
     * 事务中断回调
     * @param transactionTerminatedEvent - 事务事件
     */
    @Override
    public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
        log.info("收到摄像机 事务中断回调");
    }

    /**
     * 对话框关闭事件
     * @param dialogTerminatedEvent - 对话框事件
     */
    @Override
    public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
        log.info("收到摄像机 对话框关闭事件");
    }
}

业务处理 service

import com.sip.bean.Device;
import com.sip.bean.SSRCInfo;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;

/**
 * sip 交互事件处理
 *
 * @author heyonghao
 * @date 2023/5/12
 */
public interface SipEventService {

    /**
     * 接收请求,注册事件
     * @param requestEvent requestEvent
     */
    void requestRegister(RequestEvent requestEvent);

    /**
     * 接收请求,消息事件
     * @param requestEvent requestEvent
     */
    void requestMessage(RequestEvent requestEvent);

    /**
     * 响应invite请求
     * @param responseEvent
     */
    void responseInvite(ResponseEvent responseEvent);

    /**
     * 接收请求,bye事件
     * @param requestEvent requestEvent
     */
    void requestBye(RequestEvent requestEvent);

    /**
     * 发送 invite -> 摄像机
     */
    void sendInvite(Device device, SSRCInfo ssrcInfo);

    /**
     * 获取设备详情
     * @param device 设备-摄像头
     */
    void getDeviceInfo(Device device);


}
@Slf4j
@Service
public class SipEventServiceImpl implements SipEventService {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;
    @Resource
    private SipConfig sipConfig;
    @Resource
    private SIPSender sipSender;
    @Resource
    private SipInitListen sipInitListen;
    @Resource
    private SipProcessResponse sipProcessResponse;
    @Resource
    private SIPRequestHeaderProvider requestHeaderProvider;

    @SneakyThrows
    @Override
    public void requestRegister(RequestEvent evt) {
        RequestEventExt evtExt = (RequestEventExt) evt;
        String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort();
        log.info("[注册请求] 开始处理: {}", requestAddress);
        SIPRequest sipRequest = (SIPRequest) evt.getRequest();
        Response response = null;
        //密码是否正确
        boolean passwordCorrect = false;
        // 注册标志
        boolean registerFlag;
        FromHeader fromHeader = (FromHeader) sipRequest.getHeader(FromHeader.NAME);
        AddressImpl address = (AddressImpl) fromHeader.getAddress();
        SipUri uri = (SipUri) address.getURI();
        //设备ID(保留)
        String deviceId = uri.getUser();
        //是否携带认证信息
        AuthorizationHeader authHead = (AuthorizationHeader) sipRequest.getHeader(AuthorizationHeader.NAME);
        String password = sipConfig.getPassword();
        if (authHead == null) {
            log.info("[注册请求] 摄像头未携带认证信息");
            log.info("[注册请求] 回复401: {}", requestAddress);
            response = sipProcessResponse.getMessageFactory().createResponse(Response.UNAUTHORIZED, sipRequest);
            new DigestServerAuthenticationHelper().generateChallenge(sipProcessResponse.getHeaderFactory(), response, sipConfig.getDomain());
            sipSender.send(sipRequest.getLocalAddress().getHostAddress(), response);
            return;
        }
        passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(sipRequest, password);
        //密码验证失败
        if (!passwordCorrect) {
            // 注册失败
            log.info("[注册请求] 携带认证信息,但是密码验证错误");
            response = sipProcessResponse.getMessageFactory().createResponse(Response.FORBIDDEN, sipRequest);
            response.setReasonPhrase("wrong password");
            log.info("[注册请求] 密码/SIP服务器ID错误, 回复403: {}", requestAddress);
            sipSender.send(sipRequest.getLocalAddress().getHostAddress(), response);
            return;
        }
        // 携带授权头并且密码正确
        response = sipProcessResponse.getMessageFactory().createResponse(Response.OK, sipRequest);
        // 添加date头
        SIPDateHeader dateHeader = new SIPDateHeader();
        // 使用自己修改的
        WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
        dateHeader.setDate(wvpSipDate);
        response.addHeader(dateHeader);

        if (sipRequest.getExpires() == null) {
            response = sipProcessResponse.getMessageFactory().createResponse(Response.BAD_REQUEST, sipRequest);
            sipSender.send(sipRequest.getLocalAddress().getHostAddress(), response);
            return;
        }
        // 添加Contact头
        response.addHeader(sipRequest.getHeader(ContactHeader.NAME));
        // 添加Expires头
        response.addHeader(sipRequest.getExpires());
        RemoteAddressInfo remoteAddressInfo = SipUtil.getRemoteAddressFromRequest(sipRequest,false);
        String key = "camera-device:"+deviceId;
        Device device = (Device)redisTemplate.opsForValue().get(key);

        if (device == null) {
            device = new Device();
            device.setStreamMode("UDP");
            device.setCharset("GB2312");
            device.setGeoCoordSys("WGS84");
            device.setTreeType("CivilCode");
            device.setDeviceId(deviceId);
            device.setOnline(0);
        }
        device.setIp(remoteAddressInfo.getIp());
        device.setPort(remoteAddressInfo.getPort());
        device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
        device.setLocalIp(sipRequest.getLocalAddress().getHostAddress());
        if (sipRequest.getExpires().getExpires() == 0) {
            // 注销成功
            registerFlag = false;
        } else {
            // 注册成功
            device.setExpires(sipRequest.getExpires().getExpires());
            registerFlag = true;
            // 判断TCP还是UDP
            ViaHeader reqViaHeader = (ViaHeader) sipRequest.getHeader(ViaHeader.NAME);
            String transport = reqViaHeader.getTransport();
            device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
        }
        sipSender.send(sipRequest.getLocalAddress().getHostAddress(), response);
        // 注册成功
        // 保存到redis
        if (registerFlag) {
            log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress);
            device.setRegisterTime(DateUtil.getNow());
            device.setOnline(1);
        } else {
            log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
            device.setOnline(0);
        }
        redisTemplate.opsForValue().set(key,device);
    }

    @Override
    public void requestMessage(RequestEvent evt) {
        SIPRequest sipRequest = (SIPRequest)evt.getRequest();
        log.info("接收到消息:" + evt.getRequest());
        String deviceId = SipUtil.getUserIdFromFromHeader(evt.getRequest());
        CallIdHeader callIdHeader = sipRequest.getCallIdHeader();
        SIPRequest request = (SIPRequest) evt.getRequest();
        // 查询设备是否存在
        String key = "camera-device:"+deviceId;
        Device device = (Device)redisTemplate.opsForValue().get(key);
        // 查询上级平台是否存在
        try {
            if (device == null) {
                // 不存在则回复404
                sipProcessResponse.responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found");
                log.warn("[设备未找到 ]deviceId: {}, callId: {}", deviceId, callIdHeader.getCallId());
            }else {
                Element rootElement = null;
                try {
                    rootElement = sipProcessResponse.getRootElement(evt);
                    if (rootElement == null) {
                        log.error("处理MESSAGE请求  未获取到消息体{}", evt.getRequest());
                        sipProcessResponse.responseAck(request, Response.BAD_REQUEST, "content is null");
                        return;
                    }
                    //message 命令类型
                    String cmdType = XmlUtil.getText(rootElement, "CmdType");
                    switch (cmdType){
                        case "DeviceInfo":
                            //厂家
                            String manufacturer = XmlUtil.getText(rootElement, "Manufacturer");
                            String Channel = XmlUtil.getText(rootElement, "DeviceID");
                            sipProcessResponse.responseAck(sipRequest, Response.OK);
                            return;
                        case "Keepalive":
                            sipProcessResponse.responseAck(sipRequest, Response.OK,"注册成功");
                            return;
                        default:
                            sipProcessResponse.responseAck(sipRequest, Response.OK);
                    }
                } catch (DocumentException e) {
                    log.warn("解析XML消息内容异常", e);
                    // 不存在则回复404
                    sipProcessResponse.responseAck(request, Response.BAD_REQUEST, e.getMessage());
                }
            }
        } catch (SipException e) {
            log.warn("SIP 回复错误", e);
        } catch (InvalidArgumentException e) {
            log.warn("参数无效", e);
        } catch (ParseException e) {
            log.warn("SIP回复时解析异常", e);
        }
    }

    @Override
    public void responseInvite(ResponseEvent evt) {
        log.debug("响应invite:" + evt.getResponse());
        try {
            SIPResponse response = (SIPResponse)evt.getResponse();
            int statusCode = response.getStatusCode();
            // trying不会回复
            if (statusCode == Response.TRYING) {
            }
            // 成功响应
            // 下发ack
            if (statusCode == Response.OK) {
                log.info("回复ACK,准备推流");
                ResponseEventExt event = (ResponseEventExt)evt;

                String contentString = new String(response.getRawContent());
                // jainSip不支持y=字段, 移除以解析。
                int ssrcIndex = contentString.indexOf("y=");
                // 检查是否有y字段
                SessionDescription sdp;
                if (ssrcIndex >= 0) {
                    //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
                    String substring = contentString.substring(0, contentString.indexOf("y="));
                    sdp = SdpFactory.getInstance().createSessionDescription(substring);
                } else {
                    sdp = SdpFactory.getInstance().createSessionDescription(contentString);
                }

                SipURI requestUri = sipInitListen.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
                Request reqAck = requestHeaderProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);

                log.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort());
                sipSender.send( response.getLocalAddress().getHostAddress(), reqAck);
            }
        } catch (InvalidArgumentException | ParseException | SipException | SdpParseException e) {
            log.info("[点播回复ACK],异常:", e );
        }
    }

    @Override
    public void requestBye(RequestEvent evt) {
        log.info("处理BYE请求");
        try {
            sipProcessResponse.responseAck((SIPRequest) evt.getRequest(), Response.OK);
        } catch (SipException | InvalidArgumentException | ParseException e) {
            log.error("[回复BYE信息失败],{}", e.getMessage());
        }
    }

    @Override
    public void sendInvite(Device device,SSRCInfo ssrcInfo) {
        try {
            String channelId="34020000001320000001";
            String sdpIp;
            if (!ObjectUtils.isEmpty(device.getSdpIp())) {
                sdpIp = device.getSdpIp();
            }else {
                sdpIp = "192.168.1.250";
            }
            //封装 sdp协议信息,告诉摄像头 推流配置
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
            content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
            content.append("s=Play\r\n");
            content.append("c=IN IP4 " + sdpIp + "\r\n");
            content.append("t=0 0\r\n");
            //is -SDP
            if (Boolean.FALSE) {
                if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
                    content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
                    content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
                } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
                    content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
                content.append("a=rtpmap:126 H264/90000\r\n");
                content.append("a=rtpmap:125 H264S/90000\r\n");
                content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
                } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
                    content.append("a=setup:active\r\n");
                    content.append("a=connection:new\r\n");
                }
            } else {
                if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
                    content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
                } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
                    content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
                } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
                    content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
                }
                content.append("a=recvonly\r\n");
                content.append("a=rtpmap:96 PS/90000\r\n");
                content.append("a=rtpmap:98 H264/90000\r\n");
                content.append("a=rtpmap:97 MPEG4/90000\r\n");
                content.append("a=rtpmap:99 H265/90000\r\n");
                if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
                    content.append("a=setup:passive\r\n");
                    content.append("a=connection:new\r\n");
                } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
                    content.append("a=setup:active\r\n");
                    content.append("a=connection:new\r\n");
                }
            }

            content.append("y=" + ssrcInfo + "\r\n");//ssrc
            System.out.println(content);

            CallIdHeader newCallIdHeader=sipSender.getNewCallIdHeader(sipInitListen.getLocalIp(device.getLocalIp()),device.getTransport());
            Request request = requestHeaderProvider.createInviteRequest(device, channelId,
                    content.toString(), SipUtil.getNewViaTag(), SipUtil.getNewFromTag(), null, ssrcInfo.getSsrc(), newCallIdHeader);
            sipSender.send(sipConfig.getIp(),request);
        } catch (ParseException | InvalidArgumentException | PeerUnavailableException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void getDeviceInfo(Device device) {
        try {
            sipSender.deviceInfoQuery(device);
        } catch (InvalidArgumentException | SipException | ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

消息发送:

@Slf4j
@Component
public class SIPSender {

    @Autowired
    private SipInitListen sipInitListen;

    @Autowired
    private SIPRequestHeaderProvider headerProvider;

    /**
     * 发送消息
     * @param ip      目标ip
     * @param message 消息体
     */
    @SneakyThrows
    public void send(String ip, Message message) {
        ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
        String transport = "UDP";
        if (viaHeader == null) {
            log.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据");
        }else {
            transport = viaHeader.getTransport();
        }
        if (message.getHeader(UserAgentHeader.NAME) == null) {
            message.addHeader(SipUtil.createUserAgentHeader(sipInitListen.getSipFactory()));
        }
        switch (transport){
            case "TCP":
                sendTCP(ip,message);
                return;
            case "UDP":
                sendUDP(ip,message);
                return;
            default:
                sendTCP(ip,message);
        }
    }

    private boolean sendUDP(String ip, Message message) throws SipException {
        SipProviderImpl sipProvider = sipInitListen.getUdpSipProvider(ip);
        if (sipProvider == null) {
            log.error("[发送信息失败] 未找到udp://{}的监听信息", ip);
            return true;
        }
        if (message instanceof Request) {
            sipProvider.sendRequest((Request) message);
        }else if (message instanceof Response) {
            sipProvider.sendResponse((Response) message);
        }
        return false;
    }

    private boolean sendTCP(String ip, Message message) throws SipException {
        SipProviderImpl tcpSipProvider = sipInitListen.getTcpSipProvider(ip);
        if (tcpSipProvider == null) {
            log.error("[发送信息失败] 未找到tcp://{}的监听信息", ip);
            return true;
        }
        if (message instanceof Request) {
            tcpSipProvider.sendRequest((Request) message);
        }else if (message instanceof Response) {
            tcpSipProvider.sendResponse((Response) message);
        }
        return false;
    }

    public CallIdHeader getNewCallIdHeader(String ip, String transport){
        if (ObjectUtils.isEmpty(transport)) {
            return sipInitListen.getUdpSipProvider().getNewCallId();
        }
        SipProviderImpl sipProvider;
        if (ObjectUtils.isEmpty(ip)) {
            sipProvider = transport.equalsIgnoreCase("TCP") ? sipInitListen.getTcpSipProvider()
                    : sipInitListen.getUdpSipProvider();
        }else {
            sipProvider = transport.equalsIgnoreCase("TCP") ? sipInitListen.getTcpSipProvider(ip)
                    : sipInitListen.getUdpSipProvider(ip);
        }

        if (sipProvider == null) {
            sipProvider = sipInitListen.getUdpSipProvider();
        }

        if (sipProvider != null) {
            return sipProvider.getNewCallId();
        }else {
            log.warn("[新建CallIdHeader失败], ip={}, transport={}", ip, transport);
            return null;
        }
    }

    /**
     * 查询设备信息
     * @param device
     * @throws InvalidArgumentException
     * @throws SipException
     * @throws ParseException
     */
    public void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException {
        StringBuffer catalogXml = new StringBuffer(200);
        String charset = device.getCharset();
        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
        catalogXml.append("<Query>\r\n");
        catalogXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
        catalogXml.append("</Query>\r\n");
        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtil.getNewViaTag(), SipUtil.getNewFromTag(), null,getNewCallIdHeader(sipInitListen.getLocalIp(device.getLocalIp()),device.getTransport()));
        send(sipInitListen.getLocalIp(device.getLocalIp()), request);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java流程控制(二)

⭐ 循环结构⭐ 嵌套循环⭐ break 语句和 continue 语句⭐ 方法⭐ 方法的重载(overload)⭐ 递归结构 ⭐ 循环结构 循环结构分两大类&#xff0c;一类是当型&#xff0c;一类是直到型。 &#x1f41f; 当型&#xff1a; 当布尔表达式条件为 true 时&#xff0c;反复执行某语句&a…

【eNSP】win11解决virtualbox5.2.44无法安装、不兼容的问题

问题描述&#xff1a; 本人大三学生一枚&#xff0c;这学期上计算机网络&#xff0c;老师要求安装华为eNSP软件&#xff0c;安装环节一切顺利&#xff0c;直到安装到依赖组件中VirtualBox-5.2.44时&#xff0c;发生了问题&#xff0c;Windows提示此应用无法在此设备上运行&…

AI工具第三期:本周超16款国内精选AI工具分享!

1. 未来百科 未来百科&#xff0c;是一个知名的AI产品导航网站——为发现全球优质AI工具而生。目前已聚集全球2500优质AI工具产品&#xff0c;旨在帮助用户发现全球最好的AI工具&#xff0c;同时为研发AI垂直应用的创业公司提供展示窗口&#xff0c;迎接未来的AI时代。未来百科…

RocketMq源码分析(七)--消息发送流程

文章目录 一、消息发送入口二、消息发送流程1、消息验证1&#xff09;消息主题验证2&#xff09;消息内容验证 2、查找路由3、消息发送1&#xff09;选择消息队列2&#xff09;消息发送-内核实现sendKernelImpl方法参数获取brokerAddr添加消息全局唯一id设置实例id设置系统标记…

Linux Audio (5) DAPM-2 Widget/Path/Route

DAPM-2 Widget/Path/Route WM8960结构图WidgetRoutePath总结 课程&#xff1a;韦东山音频专题 内核&#xff1a;Kernel 3.5 实例&#xff1a;WM8960 WM8960结构图 录音时的音频通路 抽象图为&#xff1a; Widget wm8960.c sound\soc\codecs static const struct snd_soc_dap…

C++进阶——mapset的实现

C进阶——map&set的实现 红黑树的迭代器 迭代器的好处是可以方便遍历&#xff0c;是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器&#xff0c;需要考虑以前问题&#xff1a; 迭代器的定义 begin()与end() STL明确规定&#xff0c;begin()与end()代表的是一…

OS7安装rabbitmq

1.卸载存在的rabbitmq 停止rabbitmq服务&#xff1a; systemctl stop rabbitmq-server 查看rabbitmq安装的相关列表: yum list | grep rabbitmq 卸载rabbitmq已安装的相关内容: yum -y remove rabbitmq-server.noarch 查看erlang安装的相关列表: yum list | grep erlang 卸…

shell脚本----免交互操作

文章目录 一、Here Document免交互1.1免交互概述1.2语法格式1.3操作实验1.4tee命令 二、expect命令 一、Here Document免交互 1.1免交互概述 使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp、cat 或 read 命令。 是标准输入的一种替代品可以帮助脚…

【Unity3D】立方体纹理(Cubemap)和天空盒子(Skybox)

1 立方体纹理&#xff08;Cubemap&#xff09; 本文完整资源见 → 立方体纹理&#xff08;Cubemap&#xff09;和天空盒子&#xff08;Skybox&#xff09; 。 1&#xff09;立方体纹理简介 立方体纹理是指由上、下、左、右、前、后 6 张纹理组成的立方体结构纹理&#xff0c;其…

X3运行paddle-lite Demo

仓库地址GitHub - PaddlePaddle/Paddle-Lite-Demo at master git clone直接下载到X3上 环境准备 $ sudo apt-get update $ sudo apt-get install gcc g make wget unzip libopencv-dev pkg-config $ wget https://www.cmake.org/files/v3.10/cmake-3.10.3.tar.gz $ tar -zxvf …

Node.js--》深入理解 PM2:Node.js 应用部署和管理利器

目录 pm2&#xff1a;进程自动化管理工具 pm2的安装与使用 pm2&#xff1a;进程自动化管理工具 PM2&#xff1a;是一个流行的Node.js进程管理器&#xff0c;它可以帮助您在生产环境中管理和保持Node.js应用程序运行。PM2的功能包括监视您的应用程序、自动重启您的应用程序、…

手撕代码——异步FIFO

手撕代码——异步FIFO 一、异步FIFO原理与设计读写地址指针控制读写地址指针跨时钟处理与空满信号判断读写地址与读写操作 二、完整代码与仿真文件三、仿真结果 一、异步FIFO原理与设计 在FIFO的设计中&#xff0c;无论是同步FIFO&#xff0c;还是异步FIFO&#xff0c;最最最最…

ChatGPT:4. 使用OpenAI API创建自己的AI网站:3. flask web框架将OpenAI 创作的图片显示在网页界面上

ChatGPT&#xff1a;4. 使用OpenAI API创建自己的AI网站&#xff1a;3. flask web框架将OpenAI 创作的图片显示在网页界面上 如果你还是一个OpenAI的小白&#xff0c;有OpenAI的账号&#xff0c;但想调用OpenAI的API搞一些有意思的事&#xff0c;那么这一系列的教程将仔细的为…

nginx缓存

实验&#xff1a; 步骤一 tomcat1&&tomcat2部署&#xff1a; tomcat部署参照下面博客 (232条消息) Tomcat部署及优化_zhangchang3的博客-CSDN博客 两个网页入下图设计 步骤二 nginx 七层转发设置 编译安装nginx 修改配置文件 vim /usr/local/nginx/conf/nginx.…

5.21学习周报

文章目录 前言文献阅读摘要简介方法介绍讨论结论 时间序列预测 前言 本周阅读文献《Streamflow and rainfall forecasting by two long short-term memory-based models》&#xff0c;文献主要提出两种基于长短时记忆网络的混合模型用于对水流量和降雨量进行预测。小波-LSTM&a…

全网最全,性能测试-性能瓶颈分析详全,优秀的性能测试工程师养成记...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 内存分析 内存的…

06:mysql---约束

目录 1介绍 2:约束演示(建表) 3:外键约束 4:外建行为 5:外建是否可以删除 6:多表查询 1介绍 1:概念:约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 2:目的:保证数据库中数据的正确、有效性和完整性。 3:分类: 约束描述关键字非空约束限制…

ESP32S3---eFuse固化VDD_SPI释放GPIO45

使用esp-idf里面的esptool_py工具集吧.首先切换到工具所在目录. 比如WROOM设置(默认ttyUSB0或者ttyACM0): espefuse.py set_flash_voltage 3.3V 对于WROVER设置(默认ttyUSB0或者ttyACM0): espefuse.py set_flash_voltage 1.8V 运行后会提示你输入BURN,然后确认才能写,因为…

Vue电商项目--平台售卖属性和的排序操作制作

平台售卖属性的操作 就是点击平台的售卖属性&#xff0c;下面显示对应的内容 这里我们要借助这个props属性 这里块是平台的售卖属性&#xff0c;我们在这里绑定回调&#xff0c;一点击就把id传给父组件 我们需要把这俩个参数传进入 商品属性的数组: ["属性ID:属性值:…

FFmpeg学习:FFmpeg4数据结构分析

FFmpeg数据结构分析 FFMPEG中结构体很多。最关键的结构体可以分成以下几类&#xff1a; 1、解协议&#xff08;http,rtsp,rtmp,mms&#xff09; AVIOContext&#xff0c;URLProtocol&#xff0c;URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音…