src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -28,6 +28,8 @@ public static final String PLATFORM_REGISTER_PREFIX = "VMP_platform_register_"; public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_platform_register_info_"; public static final String Pattern_Topic = "VMP_keeplive_platform_"; public static final String EVENT_ONLINE_REGISTER = "1"; src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
@@ -45,11 +45,11 @@ ParentPlatformCatch parentPlatformCatch = new ParentPlatformCatch(); parentPlatformCatch.setParentPlatform(parentPlatform); parentPlatformCatch.setId(parentPlatform.getDeviceGBId()); parentPlatformCatch.setId(parentPlatform.getServerGBId()); redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch); // 发送平台未注册消息 publisher.platformNotRegisterEventPublish(parentPlatform.getDeviceGBId()); publisher.platformNotRegisterEventPublish(parentPlatform.getServerGBId()); } } } src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java
@@ -10,6 +10,8 @@ // 注册未回复次数 private int registerAliveReply; private String callId; private ParentPlatform parentPlatform; public String getId() { @@ -43,4 +45,12 @@ public void setParentPlatform(ParentPlatform parentPlatform) { this.parentPlatform = parentPlatform; } public String getCallId() { return callId; } public void setCallId(String callId) { this.callId = callId; } } src/main/java/com/genersoft/iot/vmp/gb28181/sdp/Codec.java
New file @@ -0,0 +1,65 @@ /* This file is part of Peers, a java SIP softphone. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Yohann Martineau */ package com.genersoft.iot.vmp.gb28181.sdp; public class Codec { private int payloadType; private String name; public int getPayloadType() { return payloadType; } public void setPayloadType(int payloadType) { this.payloadType = payloadType; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object obj) { if (!(obj instanceof Codec)) { return false; } Codec codec = (Codec)obj; if (codec.getName() == null) { return name == null; } return codec.getName().equalsIgnoreCase(name); } @Override public String toString() { StringBuffer buf = new StringBuffer(); buf.append(RFC4566_28181.TYPE_ATTRIBUTE).append(RFC4566_28181.SEPARATOR); buf.append(RFC4566_28181.ATTR_RTPMAP).append(RFC4566_28181.ATTR_SEPARATOR); buf.append(payloadType).append(" ").append(name).append("/"); buf.append(9000).append("\r\n"); return buf.toString(); } } src/main/java/com/genersoft/iot/vmp/gb28181/sdp/MediaDescription.java
New file @@ -0,0 +1,123 @@ /* This file is part of Peers, a java SIP softphone. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2007, 2008, 2009, 2010 Yohann Martineau */ package com.genersoft.iot.vmp.gb28181.sdp; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Hashtable; import java.util.List; public class MediaDescription { private String type; private InetAddress ipAddress; // attributes not codec-related private Hashtable<String, String> attributes; private int port; private List<Codec> codecs; public String getType() { return type; } public void setType(String type) { this.type = type; } public Hashtable<String, String> getAttributes() { return attributes; } public void setAttributes(Hashtable<String, String> attributes) { this.attributes = attributes; } public InetAddress getIpAddress() { return ipAddress; } public void setIpAddress(InetAddress ipAddress) { this.ipAddress = ipAddress; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public List<Codec> getCodecs() { return codecs; } public void setCodecs(List<Codec> codecs) { this.codecs = codecs; } @Override public String toString() { StringBuffer buf = new StringBuffer(); buf.append(RFC4566_28181.TYPE_MEDIA).append(RFC4566_28181.SEPARATOR); buf.append(type).append(" ").append(port); buf.append(" RTP/AVP"); if (codecs != null) { for (Codec codec: codecs) { buf.append(" "); buf.append(codec.getPayloadType()); } buf.append("\r\n"); } if (ipAddress != null) { int ipVersion; if (ipAddress instanceof Inet4Address) { ipVersion = 4; } else if (ipAddress instanceof Inet6Address) { ipVersion = 6; } else { throw new RuntimeException("unknown ip version: " + ipAddress); } buf.append(RFC4566_28181.TYPE_CONNECTION).append(RFC4566_28181.SEPARATOR); buf.append("IN IP").append(ipVersion).append(" "); buf.append(ipAddress.getHostAddress()).append("\r\n"); } if (codecs != null) { for (Codec codec: codecs) { buf.append(codec.toString()); } } if (attributes != null) { for (String attributeName: attributes.keySet()) { buf.append(RFC4566_28181.TYPE_ATTRIBUTE).append(RFC4566_28181.SEPARATOR); buf.append(attributeName); String attributeValue = attributes.get(attributeName); if (attributeValue != null && !"".equals(attributeValue.trim())) { buf.append(":").append(attributeValue); } buf.append("\r\n"); } } return buf.toString(); } } src/main/java/com/genersoft/iot/vmp/gb28181/sdp/RFC4566_28181.java
New file @@ -0,0 +1,51 @@ /* This file is part of Peers, a java SIP softphone. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2007, 2008, 2009, 2010 Yohann Martineau */ package com.genersoft.iot.vmp.gb28181.sdp; public class RFC4566_28181 { public static final char VERSION = '0'; public static final char TYPE_VERSION = 'v'; public static final char TYPE_ORIGIN = 'o'; public static final char TYPE_SUBJECT = 's'; public static final char TYPE_INFO = 'i'; public static final char TYPE_URI = 'u'; public static final char TYPE_EMAIL = 'e'; public static final char TYPE_PHONE = 'p'; public static final char TYPE_CONNECTION = 'c'; public static final char TYPE_BANDWITH = 'b'; public static final char TYPE_TIME = 't'; public static final char TYPE_REPEAT = 'r'; public static final char TYPE_ZONE = 'z'; public static final char TYPE_KEY = 'k'; public static final char TYPE_ATTRIBUTE = 'a'; public static final char TYPE_MEDIA = 'm'; public static final char TYPE_SSRC = 'y'; public static final char TYPE_MEDIA_DES = 'f'; public static final char SEPARATOR = '='; public static final char ATTR_SEPARATOR = ':'; public static final String MEDIA_AUDIO = "audio"; public static final String ATTR_RTPMAP = "rtpmap"; public static final String ATTR_SENDRECV = "sendrecv"; } src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SdpLine.java
New file @@ -0,0 +1,38 @@ /* This file is part of Peers, a java SIP softphone. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2007, 2008, 2009, 2010 Yohann Martineau */ package com.genersoft.iot.vmp.gb28181.sdp; public class SdpLine { private char type; private String value; public char getType() { return type; } public void setType(char type) { this.type = type; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SdpParser.java
New file @@ -0,0 +1,230 @@ /* This file is part of Peers, a java SIP softphone. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2007, 2008, 2009, 2010 Yohann Martineau */ package com.genersoft.iot.vmp.gb28181.sdp; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; public class SdpParser { public SessionDescription parse(byte[] body) throws IOException { if (body == null || body.length == 0) { return null; } ByteArrayInputStream in = new ByteArrayInputStream(body); InputStreamReader inputStreamReader = new InputStreamReader(in); BufferedReader reader = new BufferedReader(inputStreamReader); SessionDescription sessionDescription = new SessionDescription(); //version String line = reader.readLine(); if (line.length() < 3) { return null; } if (line.charAt(0) != RFC4566_28181.TYPE_VERSION || line.charAt(1) != RFC4566_28181.SEPARATOR || line.charAt(2) != RFC4566_28181.VERSION) { return null; } //origin line = reader.readLine(); if (line.length() < 3) { return null; } if (line.charAt(0) != RFC4566_28181.TYPE_ORIGIN || line.charAt(1) != RFC4566_28181.SEPARATOR) { return null; } line = line.substring(2); String[] originArr = line.split(" "); if (originArr == null || originArr.length != 6) { return null; } sessionDescription.setUsername(originArr[0]); sessionDescription.setId(Long.parseLong(originArr[1])); sessionDescription.setVersion(Long.parseLong(originArr[2])); sessionDescription.setIpAddress(InetAddress.getByName(originArr[5])); //name line = reader.readLine(); if (line.length() < 3) { return null; } if (line.charAt(0) != RFC4566_28181.TYPE_SUBJECT || line.charAt(1) != RFC4566_28181.SEPARATOR) { return null; } sessionDescription.setName(line.substring(2)); //session connection and attributes Hashtable<String, String> sessionAttributes = new Hashtable<String, String>(); sessionDescription.setAttributes(sessionAttributes); while ((line = reader.readLine()) != null && line.charAt(0) != RFC4566_28181.TYPE_MEDIA) { if (line.length() > 3 && line.charAt(0) == RFC4566_28181.TYPE_CONNECTION && line.charAt(1) == RFC4566_28181.SEPARATOR) { String connection = parseConnection(line.substring(2)); if (connection == null) { continue; } sessionDescription.setIpAddress(InetAddress.getByName(connection)); } else if (line.length() > 3 && line.charAt(0) == RFC4566_28181.TYPE_ATTRIBUTE && line.charAt(1) == RFC4566_28181.SEPARATOR) { String value = line.substring(2); int pos = value.indexOf(RFC4566_28181.ATTR_SEPARATOR); if (pos > -1) { sessionAttributes.put(value.substring(0, pos), value.substring(pos + 1)); } else { sessionAttributes.put(value, ""); } } } if (line == null) { return null; } //we are at the first media line ArrayList<SdpLine> mediaLines = new ArrayList<SdpLine>(); do { if (line.length() < 2) { return null; } if (line.charAt(1) != RFC4566_28181.SEPARATOR) { return null; } if (line.charAt(0) == RFC4566_28181.TYPE_SSRC) { sessionDescription.setSsrc(line.length() >=2 ?line.substring(2):""); }else if (line.charAt(0) == RFC4566_28181.TYPE_MEDIA_DES) { sessionDescription.setGbMediaDescriptions(line.length() >=2 ?line.substring(2):""); }else { SdpLine mediaLine = new SdpLine(); mediaLine.setType(line.charAt(0)); mediaLine.setValue(line.substring(2)); mediaLines.add(mediaLine); } } while ((line = reader.readLine()) != null ); ArrayList<MediaDescription> mediaDescriptions = new ArrayList<MediaDescription>(); sessionDescription.setMediaDescriptions(mediaDescriptions); for (SdpLine sdpLine : mediaLines) { MediaDescription mediaDescription; if (sdpLine.getType() == RFC4566_28181.TYPE_MEDIA) { String[] mediaArr = sdpLine.getValue().split(" "); if (mediaArr == null || mediaArr.length < 4) { return null; } mediaDescription = new MediaDescription(); mediaDescription.setType(mediaArr[0]); //TODO manage port range mediaDescription.setPort(Integer.parseInt(mediaArr[1])); mediaDescription.setAttributes(new Hashtable<String, String>()); List<Codec> codecs = new ArrayList<Codec>(); for (int i = 3; i < mediaArr.length; ++i) { int payloadType = Integer.parseInt(mediaArr[i]); Codec codec = new Codec(); codec.setPayloadType(payloadType); codec.setName("unsupported"); codecs.add(codec); } mediaDescription.setCodecs(codecs); mediaDescriptions.add(mediaDescription); } else { mediaDescription = mediaDescriptions.get(mediaDescriptions.size() - 1); String sdpLineValue = sdpLine.getValue(); if (sdpLine.getType() == RFC4566_28181.TYPE_CONNECTION) { String ipAddress = parseConnection(sdpLineValue); mediaDescription.setIpAddress(InetAddress.getByName(ipAddress)); } else if (sdpLine.getType() == RFC4566_28181.TYPE_ATTRIBUTE) { Hashtable<String, String> attributes = mediaDescription.getAttributes(); int pos = sdpLineValue.indexOf(RFC4566_28181.ATTR_SEPARATOR); if (pos > -1) { String name = sdpLineValue.substring(0, pos); String value = sdpLineValue.substring(pos + 1); pos = value.indexOf(" "); if (pos > -1) { int payloadType; try { payloadType = Integer.parseInt(value.substring(0, pos)); List<Codec> codecs = mediaDescription.getCodecs(); for (Codec codec: codecs) { if (codec.getPayloadType() == payloadType) { value = value.substring(pos + 1); pos = value.indexOf("/"); if (pos > -1) { value = value.substring(0, pos); codec.setName(value); } break; } } } catch (NumberFormatException e) { attributes.put(name, value); } } else { attributes.put(name, value); } } else { attributes.put(sdpLineValue, ""); } } } } sessionDescription.setMediaDescriptions(mediaDescriptions); for (MediaDescription description : mediaDescriptions) { if (description.getIpAddress() == null) { InetAddress sessionAddress = sessionDescription.getIpAddress(); if (sessionAddress == null) { return null; } description.setIpAddress(sessionAddress); } } return sessionDescription; } private String parseConnection(String line) { String[] connectionArr = line.split(" "); if (connectionArr == null || connectionArr.length != 3) { return null; } return connectionArr[2]; } } src/main/java/com/genersoft/iot/vmp/gb28181/sdp/SessionDescription.java
New file @@ -0,0 +1,162 @@ /* This file is part of Peers, a java SIP softphone. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Copyright 2007, 2008, 2009, 2010 Yohann Martineau */ package com.genersoft.iot.vmp.gb28181.sdp; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Hashtable; import java.util.List; public class SessionDescription { private long id; private long version; private String name; private String username; private InetAddress ipAddress; private List<MediaDescription> mediaDescriptions; private Hashtable<String, String> attributes; private String ssrc; private String gbMediaDescriptions; public SessionDescription() { } public long getId() { return id; } public void setId(long id) { this.id = id; } public InetAddress getIpAddress() { return ipAddress; } public void setIpAddress(InetAddress ipAddress) { this.ipAddress = ipAddress; } public List<MediaDescription> getMediaDescriptions() { return mediaDescriptions; } public void setMediaDescriptions(List<MediaDescription> mediaDescriptions) { this.mediaDescriptions = mediaDescriptions; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public long getVersion() { return version; } public void setVersion(long version) { this.version = version; } public Hashtable<String, String> getAttributes() { return attributes; } public void setAttributes(Hashtable<String, String> attributes) { this.attributes = attributes; } public String getSsrc() { return ssrc; } public void setSsrc(String ssrc) { this.ssrc = ssrc; } public String getGbMediaDescriptions() { return gbMediaDescriptions; } public void setGbMediaDescriptions(String gbMediaDescriptions) { this.gbMediaDescriptions = gbMediaDescriptions; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append("v=0\r\n"); buf.append("o=").append(username).append(" ").append(id); buf.append(" ").append(version); int ipVersion; if (ipAddress instanceof Inet4Address) { ipVersion = 4; } else if (ipAddress instanceof Inet6Address) { ipVersion = 6; } else { throw new RuntimeException("unknown ip version: " + ipAddress); } buf.append(" IN IP").append(ipVersion).append(" "); String hostAddress = ipAddress.getHostAddress(); buf.append(hostAddress).append("\r\n"); buf.append("s=").append(name).append("\r\n"); buf.append("c=IN IP").append(ipVersion).append(" "); buf.append(hostAddress).append("\r\n"); buf.append("t=0 0\r\n"); if (attributes != null){ for (String attributeName: attributes.keySet()) { String attributeValue = attributes.get(attributeName); buf.append("a=").append(attributeName); if (attributeValue != null && !"".equals(attributeValue.trim())) { buf.append(":"); buf.append(attributeValue); buf.append("\r\n"); } } } if (mediaDescriptions != null){ for (MediaDescription mediaDescription: mediaDescriptions) { buf.append(mediaDescription.toString()); } } if (ssrc != null){ buf.append("y=").append(ssrc).append("\r\n"); } if (gbMediaDescriptions != null){ buf.append("f=").append(gbMediaDescriptions).append("\r\n"); } return buf.toString(); } } src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
@@ -119,6 +119,9 @@ processor.setRequestEvent(evt); processor.setTcpSipProvider(getTcpSipProvider()); processor.setUdpSipProvider(getUdpSipProvider()); processor.setCmderFroPlatform(cmderFroPlatform); processor.setStorager(storager); return processor; } else if (Request.REGISTER.equals(method)) { RegisterRequestProcessor processor = new RegisterRequestProcessor(); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -70,7 +70,7 @@ @Override public boolean unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) { parentPlatform.setExpires("0"); ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getDeviceGBId()); ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId()); if (parentPlatformCatch != null) { parentPlatformCatch.setParentPlatform(parentPlatform); redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch); @@ -86,11 +86,21 @@ if (www == null ) { request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, 1L, null, null); // 将 callid 写入缓存, 等注册成功可以更新状态 CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME); redisCatchStorage.updatePlatformRegisterInfo(callIdHeader.getCallId(), parentPlatform.getServerGBId()); sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (event)->{ redisCatchStorage.delPlatformRegisterInfo(callIdHeader.getCallId()); if (errorEvent != null) { errorEvent.response(event); } }); }else { request = headerProviderPlarformProvider.createRegisterRequest(parentPlatform, null, null, callId, www); } transmitRequest(parentPlatform, request, errorEvent, okEvent); transmitRequest(parentPlatform, request, null, okEvent); return true; } catch (ParseException e) { e.printStackTrace(); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
@@ -1,71 +1,231 @@ package com.genersoft.iot.vmp.gb28181.transmit.request.impl; import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.address.SipURI; import javax.sip.header.ContentTypeHeader; import javax.sip.header.FromHeader; import javax.sip.header.SubjectHeader; import javax.sip.message.Request; import javax.sip.message.Response; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.sdp.Codec; import com.genersoft.iot.vmp.gb28181.sdp.MediaDescription; import com.genersoft.iot.vmp.gb28181.sdp.SdpParser; import com.genersoft.iot.vmp.gb28181.sdp.SessionDescription; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.address.SipUri; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.text.ParseException; import java.util.List; /** * @Description:处理INVITE请求 * @author: swwheihei * @date: 2020年5月3日 下午4:43:52 * @author: panll * @date: 2021年1月14日 */ public class InviteRequestProcessor extends SIPRequestAbstractProcessor { private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class); private SIPCommanderFroPlatform cmderFroPlatform; private IVideoManagerStorager storager; /** * 处理invite请求 * * @param request * @param evt * 请求消息 */ @Override public void process(RequestEvent evt) { // TODO 优先级99 Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 // Request request = requestEvent.getRequest(); // // try { // // 发送100 Trying // ServerTransaction serverTransaction = getServerTransaction(requestEvent); // // 查询目标地址 // URI reqUri = request.getRequestURI(); // URI contactURI = currUser.get(reqUri); // // System.out.println("processInvite rqStr=" + reqUri + " contact=" + contactURI); // // // 根据Request uri来路由,后续的响应消息通过VIA来路由 // Request cliReq = messageFactory.createRequest(request.toString()); // cliReq.setRequestURI(contactURI); // // HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory(); // Via callerVia = (Via) request.getHeader(Via.NAME); // Via via = (Via) headerFactory.createViaHeader(SIPMain.ip, SIPMain.port, "UDP", // callerVia.getBranch() + "sipphone"); // // cliReq.removeHeader(Via.NAME); // cliReq.addHeader(via); // // // 更新contact的地址 // ContactHeader contactHeader = headerFactory.createContactHeader(); // Address address = SipFactory.getInstance().createAddressFactory() // .createAddress("sip:sipsoft@" + SIPMain.ip + ":" + SIPMain.port); // contactHeader.setAddress(address); // contactHeader.setExpires(3600); // cliReq.setHeader(contactHeader); // // clientTransactionId = sipProvider.getNewClientTransaction(cliReq); // clientTransactionId.sendRequest(); // // System.out.println("processInvite clientTransactionId=" + clientTransactionId.toString()); // // System.out.println("send invite to callee: " + cliReq); // } catch (TransactionUnavailableException e1) { // e1.printStackTrace(); // } catch (SipException e) { // e.printStackTrace(); // } catch (ParseException e) { // e.printStackTrace(); // } catch (Exception e) { // e.printStackTrace(); // } // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 try { Request request = evt.getRequest(); SipURI sipURI = (SipURI) request.getRequestURI(); String channelId = sipURI.getUser(); String platformId = null; // SubjectHeader subjectHeader = (SubjectHeader)request.getHeader(SubjectHeader.NAME); // // 查询通道是否存在 不存在回复404 // if (subjectHeader != null) { // 存在则从subjectHeader 获取平台信息 // String subject = subjectHeader.getSubject(); // if (subject != null) { // String[] info1 = subject.split(","); // if (info1 != null && info1 .length == 2) { // String[] info2 = info1[1].split(":"); // if (info2 != null && info2.length == 2) { // platformId = info2[0]; // } // } // } // } FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME); AddressImpl address = (AddressImpl) fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); platformId = uri.getUser(); // if (platformId == null) { // 不存在则从fromHeader 获取平台信息 // FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME); // platformId = fromHeader.getName(); // } if (platformId == null || channelId == null) { response400Ack(evt); // 参数不全, 发400,请求错误 return; } // 查询平台下是否有该通道 DeviceChannel channel = storager.queryChannelInParentPlatform(platformId, channelId); if (channel == null) { response404Ack(evt); // 通道不存在,发404,资源不存在 return; }else { response100Ack(evt); // 通道存在,发100,trying } // 解析sdp消息 byte[] sdpByteArray = request.getRawContent(); SdpParser sdpParser = new SdpParser(); // TODO keng SessionDescription sdp = sdpParser.parse(sdpByteArray); // 获取支持的格式 List<MediaDescription> mediaDescriptions = sdp.getMediaDescriptions(); // 查看是否支持PS 负载96 String ip = null; int port = -1; for (MediaDescription mediaDescription : mediaDescriptions) { List<Codec> codecs = mediaDescription.getCodecs(); for (Codec codec : codecs) { if("96".equals(codec.getPayloadType()) || "PS".equals(codec.getName()) || "ps".equals(codec.getName())) { ip = mediaDescription.getIpAddress().getHostName(); port = mediaDescription.getPort(); break; } } } if (ip == null || port == -1) { // TODO 没有合适的视频流格式, 可配置是否使用第一个media信息 if (mediaDescriptions.size() > 0) { ip = mediaDescriptions.get(0).getIpAddress().getHostName(); port = mediaDescriptions.get(0).getPort(); } } if (ip == null || port == -1) { response488Ack(evt); return; } String ssrc = sdp.getSsrc(); // 通知下级推流, // 查找合适的端口推流, // 发送 200ok // 收到ack后调用推流接口 } catch (SipException | InvalidArgumentException | ParseException e) { e.printStackTrace(); } catch (IOException e) { logger.warn("sdp解析错误"); e.printStackTrace(); } } /*** * 回复100 trying * @param evt * @throws SipException * @throws InvalidArgumentException * @throws ParseException */ private void response100Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException { Response response = getMessageFactory().createResponse(Response.TRYING, evt.getRequest()); getServerTransaction(evt).sendResponse(response); } /*** * 回复404 * @param evt * @throws SipException * @throws InvalidArgumentException * @throws ParseException */ private void response404Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException { Response response = getMessageFactory().createResponse(Response.NOT_FOUND, evt.getRequest()); getServerTransaction(evt).sendResponse(response); } /*** * 回复400 * @param evt * @throws SipException * @throws InvalidArgumentException * @throws ParseException */ private void response400Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException { Response response = getMessageFactory().createResponse(Response.BAD_REQUEST, evt.getRequest()); getServerTransaction(evt).sendResponse(response); } /*** * 回复488 * @param evt * @throws SipException * @throws InvalidArgumentException * @throws ParseException */ private void response488Ack(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException { Response response = getMessageFactory().createResponse(Response.NOT_ACCEPTABLE_HERE, evt.getRequest()); getServerTransaction(evt).sendResponse(response); } /*** * 回复200 OK * @param evt * @throws SipException * @throws InvalidArgumentException * @throws ParseException */ private void responseAck(RequestEvent evt, String sdp) throws SipException, InvalidArgumentException, ParseException { Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest()); ContentTypeHeader contentTypeHeader = getHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); response.setContent(sdp, contentTypeHeader); getServerTransaction(evt).sendResponse(response); } public SIPCommanderFroPlatform getCmderFroPlatform() { return cmderFroPlatform; } public void setCmderFroPlatform(SIPCommanderFroPlatform cmderFroPlatform) { this.cmderFroPlatform = cmderFroPlatform; } public IVideoManagerStorager getStorager() { return storager; } public void setStorager(IVideoManagerStorager storager) { this.storager = storager; } } src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -15,6 +15,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce; import gov.nist.javax.sip.address.AddressImpl; import gov.nist.javax.sip.address.SipUri; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; @@ -166,10 +168,15 @@ Element deviceIdElement = rootElement.element("DeviceID"); String deviceId = deviceIdElement.getText(); Element deviceListElement = rootElement.element("DeviceList"); FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); AddressImpl address = (AddressImpl) fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); String platformId = uri.getUser(); // if (deviceListElement == null) { // 存在DeviceList则为响应 catalog, 不存在DeviceList则为查询请求 if (name == "Query") { // 区分是Response——查询响应,还是Query——查询请求 // TODO 后续将代码拆分 ParentPlatform parentPlatform = storager.queryParentPlatById(deviceId); ParentPlatform parentPlatform = storager.queryParentPlatById(platformId); if (parentPlatform == null) { response404Ack(evt); return; @@ -179,9 +186,8 @@ Element snElement = rootElement.element("SN"); String sn = snElement.getText(); FromHeader fromHeader = (FromHeader)evt.getRequest().getHeader(FromHeader.NAME); // 准备回复通道信息 List<ChannelReduce> channelReduces = storager.queryChannelListInParentPlatform(parentPlatform.getDeviceGBId()); List<ChannelReduce> channelReduces = storager.queryChannelListInParentPlatform(parentPlatform.getServerGBId()); if (channelReduces.size() >0 ) { for (ChannelReduce channelReduce : channelReduces) { DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId()); @@ -499,7 +505,7 @@ } /*** * 回复200 OK * 回复404 * @param evt * @throws SipException * @throws InvalidArgumentException src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java
@@ -61,10 +61,14 @@ public void process(ResponseEvent evt, SipLayer layer, SipConfig config) { // TODO Auto-generated method stub Response response = evt.getResponse(); ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); SipUri uri = (SipUri)toHeader.getAddress().getURI(); String platformGBId = uri.getAuthority().getUser(); CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME); String callId = callIdHeader.getCallId(); String platformGBId = redisCatchStorage.queryPlatformRegisterInfo(callId); if (platformGBId == null) { logger.info(String.format("未找到callId: %s 的注册/注销平台id", callId )); return; } logger.info(String.format("收到 %s 的注册/注销%S响应", platformGBId, response.getStatusCode() )); ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformGBId); @@ -80,18 +84,13 @@ if (response.getStatusCode() == 401) { WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME); CallIdHeader callIdHeader = (CallIdHeader)response.getHeader(CallIdHeader.NAME); String callId = callIdHeader.getCallId(); sipCommanderForPlatform.register(parentPlatform, callId, www, null, null); }else if (response.getStatusCode() == 200){ // 注册成功 logger.info(String.format("%s 注册成功", platformGBId )); redisCatchStorage.delPlatformRegisterInfo(callId); parentPlatform.setStatus(true); storager.updateParentPlatform(parentPlatform); // redisCatchStorage.updatePlatformRegister(parentPlatform); redisCatchStorage.updatePlatformKeepalive(parentPlatform); src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -73,4 +73,9 @@ void delPlatformRegister(String platformGbId); void updatePlatformRegisterInfo(String callId, String platformGbId); String queryPlatformRegisterInfo(String callId); void delPlatformRegisterInfo(String callId); } src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -233,4 +233,5 @@ int delChannelForGB(String platformId, List<ChannelReduce> channelReduces); DeviceChannel queryChannelInParentPlatform(String platformId, String channelId); } src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java
@@ -24,7 +24,7 @@ @Update("UPDATE parent_platform " + "SET enable=#{enable}, " + "name=#{name}," + "serverGBId=#{serverGBId}," + "deviceGBId=#{deviceGBId}," + "serverGBDomain=#{serverGBDomain}, " + "serverIP=#{serverIP}," + "serverPort=#{serverPort}, " + @@ -39,13 +39,13 @@ "PTZEnable=#{PTZEnable}, " + "rtcp=#{rtcp}, " + "status=#{status} " + "WHERE deviceGBId=#{deviceGBId}") "WHERE serverGBId=#{serverGBId}") int updateParentPlatform(ParentPlatform parentPlatform); @Delete("DELETE FROM parent_platform WHERE deviceGBId=#{deviceGBId}") @Delete("DELETE FROM parent_platform WHERE serverGBId=#{serverGBId}") int delParentPlatform(ParentPlatform parentPlatform); @Select("SELECT *,( SELECT count(0) FROM platform_gb_channel pc WHERE pc.platformId = pp.deviceGBId) as channelCount FROM parent_platform pp ") @Select("SELECT *,( SELECT count(0) FROM platform_gb_channel pc WHERE pc.platformId = pp.serverGBId) as channelCount FROM parent_platform pp ") List<ParentPlatform> getParentPlatformList(); @Select("SELECT * FROM parent_platform WHERE enable=#{enable}") src/main/java/com/genersoft/iot/vmp/storager/dao/PatformChannelMapper.java
@@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; @@ -39,4 +40,9 @@ "DELETE FROM platform_gb_channel WHERE platformId='${platformId}'" + "</script>") int cleanChannelForGB(String platformId); @Select("SELECT * FROM device_channel WHERE deviceId = (SELECT deviceId FROM platform_gb_channel WHERE " + "platformId='${platformId}' AND channelId='${channelId}' ) AND channelId='${channelId}'") DeviceChannel queryChannelInParentPlatform(String platformId, String channelId); } src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -169,13 +169,13 @@ @Override public void updatePlatformKeepalive(ParentPlatform parentPlatform) { String key = VideoManagerConstants.PLATFORM_KEEPLIVEKEY_PREFIX + parentPlatform.getDeviceGBId(); String key = VideoManagerConstants.PLATFORM_KEEPLIVEKEY_PREFIX + parentPlatform.getServerGBId(); redis.set(key, "", Integer.parseInt(parentPlatform.getKeepTimeout())); } @Override public void updatePlatformRegister(ParentPlatform parentPlatform) { String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + parentPlatform.getDeviceGBId(); String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + parentPlatform.getServerGBId(); redis.set(key, "", Integer.parseInt(parentPlatform.getExpires())); } @@ -198,4 +198,22 @@ public void delPlatformRegister(String platformGbId) { redis.del(VideoManagerConstants.PLATFORM_REGISTER_PREFIX + platformGbId); } @Override public void updatePlatformRegisterInfo(String callId, String platformGbId) { String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId; redis.set(key, platformGbId); } @Override public String queryPlatformRegisterInfo(String callId) { return (String)redis.get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId); } @Override public void delPlatformRegisterInfo(String callId) { redis.del(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId); } } src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
@@ -254,7 +254,7 @@ public boolean deleteParentPlatform(ParentPlatform parentPlatform) { int result = platformMapper.delParentPlatform(parentPlatform); // 删除关联的通道 patformChannelMapper.cleanChannelForGB(parentPlatform.getDeviceGBId()); patformChannelMapper.cleanChannelForGB(parentPlatform.getServerGBId()); return result > 0; } @@ -329,4 +329,10 @@ return result; } @Override public DeviceChannel queryChannelInParentPlatform(String platformId, String channelId) { DeviceChannel channel = patformChannelMapper.queryChannelInParentPlatform(platformId, channelId); return channel; } } src/main/java/com/genersoft/iot/vmp/vmanager/platform/PlatformController.java
@@ -110,7 +110,7 @@ if (logger.isDebugEnabled()) { logger.debug("查询所有上级设备API调用"); } if (StringUtils.isEmpty(parentPlatform.getDeviceGBId()) if (StringUtils.isEmpty(parentPlatform.getServerGBId()) ){ return new ResponseEntity<>("missing parameters", HttpStatus.BAD_REQUEST); } @@ -118,14 +118,14 @@ // 发送离线消息,无论是否成功都删除缓存 commanderForPlatform.unregister(parentPlatform, (event -> { // 清空redis缓存 redisCatchStorage.delPlatformCatchInfo(parentPlatform.getDeviceGBId()); redisCatchStorage.delPlatformKeepalive(parentPlatform.getDeviceGBId()); redisCatchStorage.delPlatformRegister(parentPlatform.getDeviceGBId()); redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId()); redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId()); redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId()); }), (event -> { // 清空redis缓存 redisCatchStorage.delPlatformCatchInfo(parentPlatform.getDeviceGBId()); redisCatchStorage.delPlatformKeepalive(parentPlatform.getDeviceGBId()); redisCatchStorage.delPlatformRegister(parentPlatform.getDeviceGBId()); redisCatchStorage.delPlatformCatchInfo(parentPlatform.getServerGBId()); redisCatchStorage.delPlatformKeepalive(parentPlatform.getServerGBId()); redisCatchStorage.delPlatformRegister(parentPlatform.getServerGBId()); })); boolean deleteResult = storager.deleteParentPlatform(parentPlatform);