java 对接国标摄像头流程、代码整合 springboot SIP -GB2818
作者:mmseoamin日期:2023-12-21

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

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

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

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

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

概念:

国标协议2818 组成:

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

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

  • 流媒体:音视频流的传输与转换

sip服务端:

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

流媒体:

  • 用作视频、音频流的接入服务的,拉流、推流、解编码服务端;
  • 一般用 ZLMediaKit 用做流媒体服务器,C++11 性能高、部署较方便
  • https://gitee.com/xia-chu/ZLMediaKit?_from=gitee_searchjava 对接国标摄像头流程、代码整合 springboot SIP -GB2818,在这里插入图片描述,第2张

配置摄像头接入:

java 对接国标摄像头流程、代码整合 springboot SIP -GB2818,1,第3张

sip注册交互流程:

  • java 对接国标摄像头流程、代码整合 springboot SIP -GB2818,在这里插入图片描述,第4张

    java 对接国标摄像头流程、代码整合 springboot SIP -GB2818,在这里插入图片描述,第5张

拉流交互过程:

java 对接国标摄像头流程、代码整合 springboot SIP -GB2818,在这里插入图片描述,第6张

摄像头返回推流端口:

java 对接国标摄像头流程、代码整合 springboot SIP -GB2818,在这里插入图片描述,第7张

invite 抓包:

java 对接国标摄像头流程、代码整合 springboot SIP -GB2818,1,第8张

部分代码示例:


  
       javax.sip
       jain-sip-ri
       1.3.0-91
   

初始监听端口:

  • 参考 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 tcpSipProviderMap = new ConcurrentHashMap<>();
    /**
     * udp-sip提供
     */
    private final Map udpSipProviderMap = new ConcurrentHashMap<>();
    @Override
    public void run(String... args) {
        List 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 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("\r\n");
        catalogXml.append("\r\n");
        catalogXml.append("DeviceInfo\r\n");
        catalogXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n");
        catalogXml.append("" + device.getDeviceId() + "\r\n");
        catalogXml.append("\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);
    }
}