Commit f8fe76add24fc2a26449c6b4007a303decb46d99

Authored by 648540858
Committed by GitHub
2 parents edb033cc f9d30bdf

Merge pull request #74 from lawrencehj/wvp-28181-2.0

实现语音广播信令等(web语音推流开发中)
src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java
@@ -33,6 +33,9 @@ public class SipPlatformRunner implements CommandLineRunner { @@ -33,6 +33,9 @@ public class SipPlatformRunner implements CommandLineRunner {
33 // 设置所有平台离线 33 // 设置所有平台离线
34 storager.outlineForAllParentPlatform(); 34 storager.outlineForAllParentPlatform();
35 35
  36 + // 清理所有平台注册缓存
  37 + redisCatchStorage.cleanPlatformRegisterInfos();
  38 +
36 List<ParentPlatform> parentPlatforms = storager.queryEnableParentPlatformList(true); 39 List<ParentPlatform> parentPlatforms = storager.queryEnableParentPlatformList(true);
37 40
38 for (ParentPlatform parentPlatform : parentPlatforms) { 41 for (ParentPlatform parentPlatform : parentPlatforms) {
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
@@ -16,7 +16,7 @@ import org.slf4j.Logger; @@ -16,7 +16,7 @@ import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory; 16 import org.slf4j.LoggerFactory;
17 import org.springframework.beans.factory.annotation.Autowired; 17 import org.springframework.beans.factory.annotation.Autowired;
18 import org.springframework.context.annotation.Bean; 18 import org.springframework.context.annotation.Bean;
19 -import org.springframework.context.annotation.ComponentScan; 19 +// import org.springframework.context.annotation.ComponentScan;
20 import org.springframework.context.annotation.DependsOn; 20 import org.springframework.context.annotation.DependsOn;
21 import org.springframework.stereotype.Component; 21 import org.springframework.stereotype.Component;
22 22
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
@@ -41,6 +41,8 @@ public class DeferredResultHolder { @@ -41,6 +41,8 @@ public class DeferredResultHolder {
41 41
42 public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; 42 public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
43 43
  44 + public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
  45 +
44 private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>(); 46 private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
45 47
46 public void put(String key, DeferredResult result) { 48 public void put(String key, DeferredResult result) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -120,6 +120,14 @@ public interface ISIPCommander { @@ -120,6 +120,14 @@ public interface ISIPCommander {
120 boolean audioBroadcastCmd(Device device,String channelId); 120 boolean audioBroadcastCmd(Device device,String channelId);
121 121
122 /** 122 /**
  123 + * 语音广播
  124 + *
  125 + * @param device 视频设备
  126 + */
  127 + void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent);
  128 + boolean audioBroadcastCmd(Device device);
  129 +
  130 + /**
123 * 音视频录像控制 131 * 音视频录像控制
124 * 132 *
125 * @param device 视频设备 133 * @param device 视频设备
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
@@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd; @@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
3 import com.genersoft.iot.vmp.conf.SipConfig; 3 import com.genersoft.iot.vmp.conf.SipConfig;
4 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; 4 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
5 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.beans.factory.annotation.Autowired;
6 -import org.springframework.beans.factory.annotation.Qualifier; 6 +// import org.springframework.beans.factory.annotation.Qualifier;
7 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
8 import org.springframework.util.DigestUtils; 8 import org.springframework.util.DigestUtils;
9 9
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
@@ -6,14 +6,14 @@ import java.util.ArrayList; @@ -6,14 +6,14 @@ import java.util.ArrayList;
6 import javax.sip.InvalidArgumentException; 6 import javax.sip.InvalidArgumentException;
7 import javax.sip.PeerUnavailableException; 7 import javax.sip.PeerUnavailableException;
8 import javax.sip.SipFactory; 8 import javax.sip.SipFactory;
9 -import javax.sip.SipProvider; 9 +// import javax.sip.SipProvider;
10 import javax.sip.address.Address; 10 import javax.sip.address.Address;
11 import javax.sip.address.SipURI; 11 import javax.sip.address.SipURI;
12 import javax.sip.header.*; 12 import javax.sip.header.*;
13 import javax.sip.message.Request; 13 import javax.sip.message.Request;
14 14
15 import org.springframework.beans.factory.annotation.Autowired; 15 import org.springframework.beans.factory.annotation.Autowired;
16 -import org.springframework.beans.factory.annotation.Qualifier; 16 +// import org.springframework.beans.factory.annotation.Qualifier;
17 import org.springframework.stereotype.Component; 17 import org.springframework.stereotype.Component;
18 18
19 import com.genersoft.iot.vmp.conf.SipConfig; 19 import com.genersoft.iot.vmp.conf.SipConfig;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory; @@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory;
23 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.beans.factory.annotation.Qualifier; 24 import org.springframework.beans.factory.annotation.Qualifier;
25 import org.springframework.beans.factory.annotation.Value; 25 import org.springframework.beans.factory.annotation.Value;
26 -import org.springframework.context.annotation.ComponentScan; 26 +// import org.springframework.context.annotation.ComponentScan;
27 import org.springframework.context.annotation.DependsOn; 27 import org.springframework.context.annotation.DependsOn;
28 import org.springframework.context.annotation.Lazy; 28 import org.springframework.context.annotation.Lazy;
29 import org.springframework.stereotype.Component; 29 import org.springframework.stereotype.Component;
@@ -47,8 +47,7 @@ import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; @@ -47,8 +47,7 @@ import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
47 public class SIPCommander implements ISIPCommander { 47 public class SIPCommander implements ISIPCommander {
48 48
49 private final Logger logger = LoggerFactory.getLogger(SIPCommander.class); 49 private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
50 -  
51 - 50 +
52 @Autowired 51 @Autowired
53 private SipConfig sipConfig; 52 private SipConfig sipConfig;
54 53
@@ -623,11 +622,67 @@ public class SIPCommander implements ISIPCommander { @@ -623,11 +622,67 @@ public class SIPCommander implements ISIPCommander {
623 */ 622 */
624 @Override 623 @Override
625 public boolean audioBroadcastCmd(Device device, String channelId) { 624 public boolean audioBroadcastCmd(Device device, String channelId) {
626 - // TODO Auto-generated method stub 625 + // 改为新的实现
627 return false; 626 return false;
628 } 627 }
629 628
630 /** 629 /**
  630 + * 语音广播
  631 + *
  632 + * @param device 视频设备
  633 + * @param channelId 预览通道
  634 + */
  635 + @Override
  636 + public boolean audioBroadcastCmd(Device device) {
  637 + try {
  638 + StringBuffer broadcastXml = new StringBuffer(200);
  639 + broadcastXml.append("<?xml version=\"1.0\" ?>\r\n");
  640 + broadcastXml.append("<Notify>\r\n");
  641 + broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
  642 + broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
  643 + broadcastXml.append("<SourceID>" + sipConfig.getSipId() + "</SourceID>\r\n");
  644 + broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
  645 + broadcastXml.append("</Notify>\r\n");
  646 +
  647 + String tm = Long.toString(System.currentTimeMillis());
  648 +
  649 + CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
  650 + : udpSipProvider.getNewCallId();
  651 +
  652 + Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader);
  653 + transmitRequest(device, request);
  654 + return true;
  655 + } catch (SipException | ParseException | InvalidArgumentException e) {
  656 + e.printStackTrace();
  657 + }
  658 + return false;
  659 + }
  660 + @Override
  661 + public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) {
  662 + try {
  663 + StringBuffer broadcastXml = new StringBuffer(200);
  664 + broadcastXml.append("<?xml version=\"1.0\" ?>\r\n");
  665 + broadcastXml.append("<Notify>\r\n");
  666 + broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
  667 + broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
  668 + broadcastXml.append("<SourceID>" + sipConfig.getSipId() + "</SourceID>\r\n");
  669 + broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
  670 + broadcastXml.append("</Notify>\r\n");
  671 +
  672 + String tm = Long.toString(System.currentTimeMillis());
  673 +
  674 + CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
  675 + : udpSipProvider.getNewCallId();
  676 +
  677 + Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader);
  678 + transmitRequest(device, request, errorEvent);
  679 + } catch (SipException | ParseException | InvalidArgumentException e) {
  680 + e.printStackTrace();
  681 + }
  682 + }
  683 +
  684 +
  685 + /**
631 * 音视频录像控制 686 * 音视频录像控制
632 * 687 *
633 * @param device 视频设备 688 * @param device 视频设备
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -15,7 +15,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -15,7 +15,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
15 import org.springframework.beans.factory.annotation.Autowired; 15 import org.springframework.beans.factory.annotation.Autowired;
16 import org.springframework.beans.factory.annotation.Qualifier; 16 import org.springframework.beans.factory.annotation.Qualifier;
17 import org.springframework.beans.factory.annotation.Value; 17 import org.springframework.beans.factory.annotation.Value;
18 -import org.springframework.context.annotation.ComponentScan; 18 +// import org.springframework.context.annotation.ComponentScan;
19 import org.springframework.context.annotation.DependsOn; 19 import org.springframework.context.annotation.DependsOn;
20 import org.springframework.context.annotation.Lazy; 20 import org.springframework.context.annotation.Lazy;
21 import org.springframework.lang.Nullable; 21 import org.springframework.lang.Nullable;
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
@@ -14,6 +14,7 @@ import javax.sip.message.Response; @@ -14,6 +14,7 @@ import javax.sip.message.Response;
14 import com.genersoft.iot.vmp.conf.MediaServerConfig; 14 import com.genersoft.iot.vmp.conf.MediaServerConfig;
15 import com.genersoft.iot.vmp.gb28181.bean.Device; 15 import com.genersoft.iot.vmp.gb28181.bean.Device;
16 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; 16 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  17 +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
17 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; 18 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
18 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; 19 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
19 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; 20 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
@@ -74,144 +75,216 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor { @@ -74,144 +75,216 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
74 Request request = evt.getRequest(); 75 Request request = evt.getRequest();
75 SipURI sipURI = (SipURI) request.getRequestURI(); 76 SipURI sipURI = (SipURI) request.getRequestURI();
76 String channelId = sipURI.getUser(); 77 String channelId = sipURI.getUser();
77 - String platformId = null; 78 + String requesterId = null;
78 79
79 FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME); 80 FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
80 AddressImpl address = (AddressImpl) fromHeader.getAddress(); 81 AddressImpl address = (AddressImpl) fromHeader.getAddress();
81 SipUri uri = (SipUri) address.getURI(); 82 SipUri uri = (SipUri) address.getURI();
82 - platformId = uri.getUser(); 83 + requesterId = uri.getUser();
83 84
84 - if (platformId == null || channelId == null) {  
85 - logger.info("无法从FromHeader的Address中获取到平台id,返回404"); 85 + if (requesterId == null || channelId == null) {
  86 + logger.info("无法从FromHeader的Address中获取到平台id,返回400");
86 responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误 87 responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误
87 return; 88 return;
88 } 89 }
89 - // 查询平台下是否有该通道  
90 - DeviceChannel channel = storager.queryChannelInParentPlatform(platformId, channelId);  
91 - if (channel == null) {  
92 - logger.info("通道不存在,返回404");  
93 - responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在  
94 - return;  
95 - }else {  
96 - responseAck(evt, Response.TRYING); // 通道存在,发100,trying  
97 - }  
98 - // 解析sdp消息, 使用jainsip 自带的sdp解析方式  
99 - String contentString = new String(request.getRawContent());  
100 -  
101 - // jainSip不支持y=字段, 移除移除以解析。  
102 - int ssrcIndex = contentString.indexOf("y=");  
103 - String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);  
104 - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段  
105 - // String ssrc = contentString.substring(ssrcIndex + 2, contentString.length())  
106 - // .replace("\r\n", "").replace("\n", "");  
107 -  
108 - String substring = contentString.substring(0, contentString.indexOf("y="));  
109 - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);  
110 -  
111 - // 获取支持的格式  
112 - Vector mediaDescriptions = sdp.getMediaDescriptions(true);  
113 - // 查看是否支持PS 负载96  
114 - //String ip = null;  
115 - int port = -1;  
116 - //boolean recvonly = false;  
117 - boolean mediaTransmissionTCP = false;  
118 - Boolean tcpActive = null;  
119 - for (int i = 0; i < mediaDescriptions.size(); i++) {  
120 - MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);  
121 - Media media = mediaDescription.getMedia();  
122 -  
123 - Vector mediaFormats = media.getMediaFormats(false);  
124 - if (mediaFormats.contains("96")) {  
125 - port = media.getMediaPort();  
126 - //String mediaType = media.getMediaType();  
127 - String protocol = media.getProtocol();  
128 -  
129 - // 区分TCP发流还是udp, 当前默认udp  
130 - if ("TCP/RTP/AVP".equals(protocol)) {  
131 - String setup = mediaDescription.getAttribute("setup");  
132 - if (setup != null) {  
133 - mediaTransmissionTCP = true;  
134 - if ("active".equals(setup)) {  
135 - tcpActive = true;  
136 - }else if ("passive".equals(setup)) {  
137 - tcpActive = false; 90 +
  91 + // 查询请求方是否上级平台
  92 + ParentPlatform platform = storager.queryParentPlatById(requesterId);
  93 + if (platform != null) {
  94 + // 查询平台下是否有该通道
  95 + DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
  96 + if (channel == null) {
  97 + logger.info("通道不存在,返回404");
  98 + responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在
  99 + return;
  100 + }else {
  101 + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
  102 + }
  103 + // 解析sdp消息, 使用jainsip 自带的sdp解析方式
  104 + String contentString = new String(request.getRawContent());
  105 +
  106 + // jainSip不支持y=字段, 移除移除以解析。
  107 + int ssrcIndex = contentString.indexOf("y=");
  108 + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
  109 + String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  110 + String substring = contentString.substring(0, contentString.indexOf("y="));
  111 + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
  112 +
  113 + // 获取支持的格式
  114 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  115 + // 查看是否支持PS 负载96
  116 + //String ip = null;
  117 + int port = -1;
  118 + //boolean recvonly = false;
  119 + boolean mediaTransmissionTCP = false;
  120 + Boolean tcpActive = null;
  121 + for (int i = 0; i < mediaDescriptions.size(); i++) {
  122 + MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
  123 + Media media = mediaDescription.getMedia();
  124 +
  125 + Vector mediaFormats = media.getMediaFormats(false);
  126 + if (mediaFormats.contains("96")) {
  127 + port = media.getMediaPort();
  128 + //String mediaType = media.getMediaType();
  129 + String protocol = media.getProtocol();
  130 +
  131 + // 区分TCP发流还是udp, 当前默认udp
  132 + if ("TCP/RTP/AVP".equals(protocol)) {
  133 + String setup = mediaDescription.getAttribute("setup");
  134 + if (setup != null) {
  135 + mediaTransmissionTCP = true;
  136 + if ("active".equals(setup)) {
  137 + tcpActive = true;
  138 + }else if ("passive".equals(setup)) {
  139 + tcpActive = false;
  140 + }
138 } 141 }
139 } 142 }
  143 + break;
140 } 144 }
141 - break;  
142 } 145 }
143 - }  
144 - if (port == -1) {  
145 - logger.info("不支持的媒体格式,返回415");  
146 - // 回复不支持的格式  
147 - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415  
148 - return;  
149 - }  
150 - String username = sdp.getOrigin().getUsername();  
151 - String addressStr = sdp.getOrigin().getAddress();  
152 - //String sessionName = sdp.getSessionName().getValue();  
153 - logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc);  
154 -  
155 - Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, channelId);  
156 - if (device == null) {  
157 - logger.warn("点播平台{}的通道{}时未找到设备信息", platformId, channel);  
158 - responseAck(evt, Response.SERVER_INTERNAL_ERROR);  
159 - return;  
160 - }  
161 - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, platformId, device.getDeviceId(), channelId,  
162 - mediaTransmissionTCP);  
163 - if (tcpActive != null) {  
164 - sendRtpItem.setTcpActive(tcpActive);  
165 - }  
166 - if (sendRtpItem == null) {  
167 - logger.warn("服务器端口资源不足");  
168 - responseAck(evt, Response.BUSY_HERE);  
169 - return;  
170 - } 146 + if (port == -1) {
  147 + logger.info("不支持的媒体格式,返回415");
  148 + // 回复不支持的格式
  149 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
  150 + return;
  151 + }
  152 + String username = sdp.getOrigin().getUsername();
  153 + String addressStr = sdp.getOrigin().getAddress();
  154 + //String sessionName = sdp.getSessionName().getValue();
  155 + logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc);
  156 +
  157 + Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId);
  158 + if (device == null) {
  159 + logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel);
  160 + responseAck(evt, Response.SERVER_INTERNAL_ERROR);
  161 + return;
  162 + }
  163 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId, device.getDeviceId(), channelId,
  164 + mediaTransmissionTCP);
  165 + if (tcpActive != null) {
  166 + sendRtpItem.setTcpActive(tcpActive);
  167 + }
  168 + if (sendRtpItem == null) {
  169 + logger.warn("服务器端口资源不足");
  170 + responseAck(evt, Response.BUSY_HERE);
  171 + return;
  172 + }
171 173
172 - // 写入redis, 超时时回复  
173 - redisCatchStorage.updateSendRTPSever(sendRtpItem);  
174 - // 通知下级推流,  
175 - PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{  
176 - // 收到推流, 回复200OK, 等待ack  
177 - sendRtpItem.setStatus(1); 174 + // 写入redis, 超时时回复
178 redisCatchStorage.updateSendRTPSever(sendRtpItem); 175 redisCatchStorage.updateSendRTPSever(sendRtpItem);
179 - // TODO 添加对tcp的支持  
180 - MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();  
181 - StringBuffer content = new StringBuffer(200);  
182 - content.append("v=0\r\n");  
183 - content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");  
184 - content.append("s=Play\r\n");  
185 - content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");  
186 - content.append("t=0 0\r\n");  
187 - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");  
188 - content.append("a=sendonly\r\n");  
189 - content.append("a=rtpmap:96 PS/90000\r\n");  
190 - content.append("y="+ ssrc + "\r\n");  
191 - content.append("f=\r\n");  
192 -  
193 - try {  
194 - responseAck(evt, content.toString());  
195 - } catch (SipException e) {  
196 - e.printStackTrace();  
197 - } catch (InvalidArgumentException e) {  
198 - e.printStackTrace();  
199 - } catch (ParseException e) {  
200 - e.printStackTrace(); 176 + // 通知下级推流,
  177 + PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{
  178 + // 收到推流, 回复200OK, 等待ack
  179 + sendRtpItem.setStatus(1);
  180 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  181 + // TODO 添加对tcp的支持
  182 + MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
  183 + StringBuffer content = new StringBuffer(200);
  184 + content.append("v=0\r\n");
  185 + content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
  186 + content.append("s=Play\r\n");
  187 + content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
  188 + content.append("t=0 0\r\n");
  189 + content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
  190 + content.append("a=sendonly\r\n");
  191 + content.append("a=rtpmap:96 PS/90000\r\n");
  192 + content.append("y="+ ssrc + "\r\n");
  193 + content.append("f=\r\n");
  194 +
  195 + try {
  196 + responseAck(evt, content.toString());
  197 + } catch (SipException e) {
  198 + e.printStackTrace();
  199 + } catch (InvalidArgumentException e) {
  200 + e.printStackTrace();
  201 + } catch (ParseException e) {
  202 + e.printStackTrace();
  203 + }
  204 + },(event -> {
  205 + // 未知错误。直接转发设备点播的错误
  206 + Response response = null;
  207 + try {
  208 + response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest());
  209 + getServerTransaction(evt).sendResponse(response);
  210 + } catch (ParseException | SipException | InvalidArgumentException e) {
  211 + e.printStackTrace();
  212 + }
  213 + }));
  214 + if (logger.isDebugEnabled()) {
  215 + logger.debug(playResult.getResult().toString());
201 } 216 }
202 - },(event -> {  
203 - // 未知错误。直接转发设备点播的错误  
204 - Response response = null;  
205 - try {  
206 - response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest());  
207 - getServerTransaction(evt).sendResponse(response);  
208 -  
209 - } catch (ParseException | SipException | InvalidArgumentException e) {  
210 - e.printStackTrace(); 217 + } else {
  218 + // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
  219 + Device device = storager.queryVideoDevice(requesterId);
  220 + if (device != null) {
  221 + logger.info("收到设备" + requesterId + "的语音广播Invite请求");
  222 + responseAck(evt, Response.TRYING);
  223 +
  224 + String contentString = new String(request.getRawContent());
  225 + // jainSip不支持y=字段, 移除移除以解析。
  226 + String substring = contentString;
  227 + String ssrc = "0000000404";
  228 + int ssrcIndex = contentString.indexOf("y=");
  229 + if (ssrcIndex > 0) {
  230 + substring = contentString.substring(0, ssrcIndex);
  231 + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  232 + }
  233 + ssrcIndex = substring.indexOf("f=");
  234 + if (ssrcIndex > 0) {
  235 + substring = contentString.substring(0, ssrcIndex);
  236 + }
  237 + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
  238 +
  239 + // 获取支持的格式
  240 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  241 + // 查看是否支持PS 负载96
  242 + int port = -1;
  243 + //boolean recvonly = false;
  244 + boolean mediaTransmissionTCP = false;
  245 + Boolean tcpActive = null;
  246 + for (int i = 0; i < mediaDescriptions.size(); i++) {
  247 + MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
  248 + Media media = mediaDescription.getMedia();
  249 +
  250 + Vector mediaFormats = media.getMediaFormats(false);
  251 + if (mediaFormats.contains("8")) {
  252 + port = media.getMediaPort();
  253 + String protocol = media.getProtocol();
  254 + // 区分TCP发流还是udp, 当前默认udp
  255 + if ("TCP/RTP/AVP".equals(protocol)) {
  256 + String setup = mediaDescription.getAttribute("setup");
  257 + if (setup != null) {
  258 + mediaTransmissionTCP = true;
  259 + if ("active".equals(setup)) {
  260 + tcpActive = true;
  261 + } else if ("passive".equals(setup)) {
  262 + tcpActive = false;
  263 + }
  264 + }
  265 + }
  266 + break;
  267 + }
  268 + }
  269 + if (port == -1) {
  270 + logger.info("不支持的媒体格式,返回415");
  271 + // 回复不支持的格式
  272 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
  273 + return;
  274 + }
  275 + String username = sdp.getOrigin().getUsername();
  276 + String addressStr = sdp.getOrigin().getAddress();
  277 + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
  278 +
  279 +
  280 +
  281 +
  282 +
  283 +
  284 + } else {
  285 + logger.warn("来自无效设备/平台的请求");
  286 + responseAck(evt, Response.BAD_REQUEST);
211 } 287 }
212 - }));  
213 - if (logger.isDebugEnabled()) {  
214 - logger.debug(playResult.getResult().toString());  
215 } 288 }
216 289
217 } catch (SipException | InvalidArgumentException | ParseException e) { 290 } catch (SipException | InvalidArgumentException | ParseException e) {
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -93,7 +93,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -93,7 +93,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
93 private static final String MESSAGE_ALARM = "Alarm"; 93 private static final String MESSAGE_ALARM = "Alarm";
94 private static final String MESSAGE_RECORD_INFO = "RecordInfo"; 94 private static final String MESSAGE_RECORD_INFO = "RecordInfo";
95 private static final String MESSAGE_MEDIA_STATUS = "MediaStatus"; 95 private static final String MESSAGE_MEDIA_STATUS = "MediaStatus";
96 - // private static final String MESSAGE_BROADCAST = "Broadcast"; 96 + private static final String MESSAGE_BROADCAST = "Broadcast";
97 private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus"; 97 private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
98 private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl"; 98 private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl";
99 private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig"; 99 private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig";
@@ -123,7 +123,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -123,7 +123,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
123 logger.info("接收到Catalog消息"); 123 logger.info("接收到Catalog消息");
124 processMessageCatalogList(evt); 124 processMessageCatalogList(evt);
125 } else if (MESSAGE_DEVICE_INFO.equals(cmd)) { 125 } else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
126 - //DeviceInfo消息处理 126 + // DeviceInfo消息处理
127 processMessageDeviceInfo(evt); 127 processMessageDeviceInfo(evt);
128 } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) { 128 } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
129 // DeviceStatus消息处理 129 // DeviceStatus消息处理
@@ -149,6 +149,9 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -149,6 +149,9 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
149 } else if (MESSAGE_PRESET_QUERY.equals(cmd)) { 149 } else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
150 logger.info("接收到PresetQuery消息"); 150 logger.info("接收到PresetQuery消息");
151 processMessagePresetQuery(evt); 151 processMessagePresetQuery(evt);
  152 + } else if (MESSAGE_BROADCAST.equals(cmd)) {
  153 + // Broadcast消息处理
  154 + processMessageBroadcast(evt);
152 } else { 155 } else {
153 logger.info("接收到消息:" + cmd); 156 logger.info("接收到消息:" + cmd);
154 responseAck(evt); 157 responseAck(evt);
@@ -298,7 +301,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -298,7 +301,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
298 // 远程启动功能 301 // 远程启动功能
299 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) { 302 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
300 if (deviceId.equals(targetGBId)) { 303 if (deviceId.equals(targetGBId)) {
301 - // 远程启动功能:需要在重新启动程序后先对SipStack解绑 304 + // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
302 logger.info("执行远程启动本平台命令"); 305 logger.info("执行远程启动本平台命令");
303 ParentPlatform parentPlatform = storager.queryParentPlatById(platformId); 306 ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
304 cmderFroPlatform.unregister(parentPlatform, null, null); 307 cmderFroPlatform.unregister(parentPlatform, null, null);
@@ -333,6 +336,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -333,6 +336,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
333 // 远程启动指定设备 336 // 远程启动指定设备
334 } 337 }
335 } 338 }
  339 + // 云台/前端控制命令
336 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) { 340 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
337 String cmdString = XmlUtil.getText(rootElement,"PTZCmd"); 341 String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
338 Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId); 342 Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
@@ -895,6 +899,37 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -895,6 +899,37 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
895 } 899 }
896 } 900 }
897 901
  902 + /**
  903 + * 处理AudioBroadcast语音广播Message
  904 + *
  905 + * @param evt
  906 + */
  907 + private void processMessageBroadcast(RequestEvent evt) {
  908 + try {
  909 + Element rootElement = getRootElement(evt);
  910 + String deviceId = XmlUtil.getText(rootElement, "DeviceID");
  911 + // 回复200 OK
  912 + responseAck(evt);
  913 + if (rootElement.getName().equals("Response")) {
  914 + // 此处是对本平台发出Broadcast指令的应答
  915 + JSONObject json = new JSONObject();
  916 + XmlUtil.node2Json(rootElement, json);
  917 + if (logger.isDebugEnabled()) {
  918 + logger.debug(json.toJSONString());
  919 + }
  920 + RequestMessage msg = new RequestMessage();
  921 + msg.setDeviceId(deviceId);
  922 + msg.setType(DeferredResultHolder.CALLBACK_CMD_BROADCAST);
  923 + msg.setData(json);
  924 + deferredResultHolder.invokeResult(msg);
  925 + } else {
  926 + // 此处是上级发出的Broadcast指令
  927 + }
  928 + } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
  929 + e.printStackTrace();
  930 + }
  931 + }
  932 +
898 933
899 /*** 934 /***
900 * 回复200 OK 935 * 回复200 OK
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java
@@ -50,7 +50,6 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor { @@ -50,7 +50,6 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor {
50 */ 50 */
51 @Override 51 @Override
52 public void process(ResponseEvent evt, SipLayer layer, SipConfig config) { 52 public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
53 - // TODO Auto-generated method stub  
54 Response response = evt.getResponse(); 53 Response response = evt.getResponse();
55 CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME); 54 CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
56 String callId = callIdHeader.getCallId(); 55 String callId = callIdHeader.getCallId();
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -81,6 +81,8 @@ public interface IRedisCatchStorage { @@ -81,6 +81,8 @@ public interface IRedisCatchStorage {
81 81
82 void delPlatformRegisterInfo(String callId); 82 void delPlatformRegisterInfo(String callId);
83 83
  84 + void cleanPlatformRegisterInfos();
  85 +
84 void updateSendRTPSever(SendRtpItem sendRtpItem); 86 void updateSendRTPSever(SendRtpItem sendRtpItem);
85 87
86 /** 88 /**
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -13,6 +13,7 @@ import org.springframework.stereotype.Component; @@ -13,6 +13,7 @@ import org.springframework.stereotype.Component;
13 13
14 import java.util.*; 14 import java.util.*;
15 15
  16 +@SuppressWarnings("rawtypes")
16 @Component 17 @Component
17 public class RedisCatchStorageImpl implements IRedisCatchStorage { 18 public class RedisCatchStorageImpl implements IRedisCatchStorage {
18 19
@@ -213,6 +214,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -213,6 +214,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
213 } 214 }
214 215
215 @Override 216 @Override
  217 + public void cleanPlatformRegisterInfos() {
  218 + List regInfos = redis.scan(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + "*");
  219 + for (Object key : regInfos) {
  220 + redis.del(key.toString());
  221 + }
  222 + }
  223 +
  224 + @Override
216 public void updateSendRTPSever(SendRtpItem sendRtpItem) { 225 public void updateSendRTPSever(SendRtpItem sendRtpItem) {
217 String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + sendRtpItem.getPlatformId() + "_" + sendRtpItem.getChannelId(); 226 String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + sendRtpItem.getPlatformId() + "_" + sendRtpItem.getChannelId();
218 redis.set(key, sendRtpItem); 227 redis.set(key, sendRtpItem);
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.play; @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.play;
2 2
3 import com.genersoft.iot.vmp.common.StreamInfo; 3 import com.genersoft.iot.vmp.common.StreamInfo;
4 import com.genersoft.iot.vmp.conf.MediaServerConfig; 4 import com.genersoft.iot.vmp.conf.MediaServerConfig;
  5 +import com.genersoft.iot.vmp.gb28181.bean.Device;
5 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; 6 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
6 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; 7 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
7 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 8 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
@@ -27,6 +28,8 @@ import org.springframework.web.context.request.async.DeferredResult; @@ -27,6 +28,8 @@ import org.springframework.web.context.request.async.DeferredResult;
27 28
28 import java.util.UUID; 29 import java.util.UUID;
29 30
  31 +import javax.sip.message.Response;
  32 +
30 @CrossOrigin 33 @CrossOrigin
31 @RestController 34 @RestController
32 @RequestMapping("/api") 35 @RequestMapping("/api")
@@ -204,5 +207,47 @@ public class PlayController { @@ -204,5 +207,47 @@ public class PlayController {
204 } 207 }
205 return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK); 208 return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
206 } 209 }
  210 +
  211 + /**
  212 + * 语音广播命令API接口
  213 + *
  214 + * @param deviceId
  215 + */
  216 + @GetMapping("/broadcast/{deviceId}")
  217 + @PostMapping("/broadcast/{deviceId}")
  218 + public DeferredResult<ResponseEntity<String>> broadcastApi(@PathVariable String deviceId) {
  219 + if (logger.isDebugEnabled()) {
  220 + logger.debug("语音广播API调用");
  221 + }
  222 + Device device = storager.queryVideoDevice(deviceId);
  223 + cmder.audioBroadcastCmd(device, event -> {
  224 + Response response = event.getResponse();
  225 + RequestMessage msg = new RequestMessage();
  226 + msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId);
  227 + JSONObject json = new JSONObject();
  228 + json.put("DeviceID", deviceId);
  229 + json.put("CmdType", "Broadcast");
  230 + json.put("Result", "Failed");
  231 + json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
  232 + msg.setData(json);
  233 + resultHolder.invokeResult(msg);
  234 + });
  235 + DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
  236 + result.onTimeout(() -> {
  237 + logger.warn(String.format("语音广播操作超时, 设备未返回应答指令"));
  238 + RequestMessage msg = new RequestMessage();
  239 + msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId);
  240 + JSONObject json = new JSONObject();
  241 + json.put("DeviceID", deviceId);
  242 + json.put("CmdType", "Broadcast");
  243 + json.put("Result", "Failed");
  244 + json.put("Error", "Timeout. Device did not response to broadcast command.");
  245 + msg.setData(json);
  246 + resultHolder.invokeResult(msg);
  247 + });
  248 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId, result);
  249 + return result;
  250 + }
  251 +
207 } 252 }
208 253
web_src/src/components/channelList.vue
@@ -99,7 +99,7 @@ export default { @@ -99,7 +99,7 @@ export default {
99 currentPage: parseInt(this.$route.params.page), 99 currentPage: parseInt(this.$route.params.page),
100 count: parseInt(this.$route.params.count), 100 count: parseInt(this.$route.params.count),
101 total: 0, 101 total: 0,
102 - beforeUrl: "/videoList", 102 + beforeUrl: "/deviceList",
103 isLoging: false, 103 isLoging: false,
104 autoList: true 104 autoList: true
105 }; 105 };
@@ -131,7 +131,7 @@ export default { @@ -131,7 +131,7 @@ export default {
131 this.currentPage = parseInt(this.$route.params.page); 131 this.currentPage = parseInt(this.$route.params.page);
132 this.count = parseInt(this.$route.params.count); 132 this.count = parseInt(this.$route.params.count);
133 if (this.parentChannelId == "" || this.parentChannelId == 0) { 133 if (this.parentChannelId == "" || this.parentChannelId == 0) {
134 - this.beforeUrl = "/videoList" 134 + this.beforeUrl = "/deviceList"
135 } 135 }
136 136
137 }, 137 },
web_src/src/components/devicePosition.vue
@@ -81,7 +81,7 @@ export default { @@ -81,7 +81,7 @@ export default {
81 parentChannelId: this.$route.params.parentChannelId, 81 parentChannelId: this.$route.params.parentChannelId,
82 updateLooper: 0, //数据刷新轮训标志 82 updateLooper: 0, //数据刷新轮训标志
83 total: 0, 83 total: 0,
84 - beforeUrl: "/videoList", 84 + beforeUrl: "/deviceList",
85 isLoging: false, 85 isLoging: false,
86 autoList: false, 86 autoList: false,
87 }; 87 };
@@ -111,7 +111,7 @@ export default { @@ -111,7 +111,7 @@ export default {
111 // this.currentPage = parseInt(this.$route.params.page); 111 // this.currentPage = parseInt(this.$route.params.page);
112 // this.count = parseInt(this.$route.params.count); 112 // this.count = parseInt(this.$route.params.count);
113 // if (this.parentChannelId == "" || this.parentChannelId == 0) { 113 // if (this.parentChannelId == "" || this.parentChannelId == 0) {
114 - // this.beforeUrl = "/videoList"; 114 + // this.beforeUrl = "/deviceList";
115 // } 115 // }
116 }, 116 },
117 initBaiduMap() { 117 initBaiduMap() {