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

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

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

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

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

概念:

国标协议2818 组成:

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

sip服务端:

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

流媒体:

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

配置摄像头接入:

sip注册交互流程:


拉流交互过程:

摄像头返回推流端口:

invite 抓包:

部分代码示例:

<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@Componentpublic class SipInitListen implements CommandLineRunner {@Resourceprivate SipConfig sipConfig;/** * sip通信,消息监听处理 */@Resourceprivate 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<>();@Overridepublic void run(String... args) {List<String> monitorIps = new ArrayList<>();// 使用逗号分割多个ipString 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@Slf4jpublic class SipProcessListener implements SipListener {@Resourceprivate SipEventService sipEventService;/** * 服务收到请求出发 * @param requestEvent - 请求事件 */@Overridepublic 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 - 响应事件 */@Overridepublic void processResponse(ResponseEvent responseEvent) {log.info("收到摄像机服务响应");Response response = responseEvent.getResponse();int status = response.getStatusCode();// Successif (((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 - 超时事件 */@Overridepublic void processTimeout(TimeoutEvent timeoutEvent) {log.info("收到摄像机 超时回调");}/** * IO异常的回调 * @param exceptionEvent - 异常事件 */@Overridepublic void processIOException(IOExceptionEvent exceptionEvent) {log.info("收到摄像机 IO异常的回调");}/** * 事务中断回调 * @param transactionTerminatedEvent - 事务事件 */@Overridepublic void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {log.info("收到摄像机 事务中断回调");}/** * 对话框关闭事件 * @param dialogTerminatedEvent - 对话框事件 */@Overridepublic 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@Servicepublic class SipEventServiceImpl implements SipEventService {@Resourceprivate RedisTemplate<String,Object> redisTemplate;@Resourceprivate SipConfig sipConfig;@Resourceprivate SIPSender sipSender;@Resourceprivate SipInitListen sipInitListen;@Resourceprivate SipProcessResponse sipProcessResponse;@Resourceprivate SIPRequestHeaderProvider requestHeaderProvider;@SneakyThrows@Overridepublic 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还是UDPViaHeader reqViaHeader = (ViaHeader) sipRequest.getHeader(ViaHeader.NAME);String transport = reqViaHeader.getTransport();device.setTransport("TCP".equalsIgnoreCase(transport) " />"TCP" : "UDP");}sipSender.send(sipRequest.getLocalAddress().getHostAddress(), response);// 注册成功// 保存到redisif (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);}@Overridepublic 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) {// 不存在则回复404sipProcessResponse.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);// 不存在则回复404sipProcessResponse.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);}}@Overridepublic void responseInvite(ResponseEvent evt) {log.debug("响应invite:" + evt.getResponse());try {SIPResponse response = (SIPResponse)evt.getResponse();int statusCode = response.getStatusCode();// trying不会回复if (statusCode == Response.TRYING) {}// 成功响应// 下发ackif (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 );}}@Overridepublic 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());}}@Overridepublic 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 -SDPif (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");//ssrcSystem.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);}}@Overridepublic void getDeviceInfo(Device device) {try {sipSender.deviceInfoQuery(device);} catch (InvalidArgumentException | SipException | ParseException e) {throw new RuntimeException(e);}}}

消息发送:

@Slf4j@Componentpublic class SIPSender {@Autowiredprivate SipInitListen sipInitListen;@Autowiredprivate SIPRequestHeaderProvider headerProvider;/** * 发送消息 * @param ip目标ip * @param message 消息体 */@SneakyThrowspublic 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("\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);}}