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 33 // 设置所有平台离线
34 34 storager.outlineForAllParentPlatform();
35 35  
  36 + // 清理所有平台注册缓存
  37 + redisCatchStorage.cleanPlatformRegisterInfos();
  38 +
36 39 List<ParentPlatform> parentPlatforms = storager.queryEnableParentPlatformList(true);
37 40  
38 41 for (ParentPlatform parentPlatform : parentPlatforms) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
... ... @@ -16,7 +16,7 @@ import org.slf4j.Logger;
16 16 import org.slf4j.LoggerFactory;
17 17 import org.springframework.beans.factory.annotation.Autowired;
18 18 import org.springframework.context.annotation.Bean;
19   -import org.springframework.context.annotation.ComponentScan;
  19 +// import org.springframework.context.annotation.ComponentScan;
20 20 import org.springframework.context.annotation.DependsOn;
21 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 41  
42 42 public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
43 43  
  44 + public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
  45 +
44 46 private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
45 47  
46 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 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 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 3 import com.genersoft.iot.vmp.conf.SipConfig;
4 4 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
5 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 7 import org.springframework.stereotype.Component;
8 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 6 import javax.sip.InvalidArgumentException;
7 7 import javax.sip.PeerUnavailableException;
8 8 import javax.sip.SipFactory;
9   -import javax.sip.SipProvider;
  9 +// import javax.sip.SipProvider;
10 10 import javax.sip.address.Address;
11 11 import javax.sip.address.SipURI;
12 12 import javax.sip.header.*;
13 13 import javax.sip.message.Request;
14 14  
15 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 17 import org.springframework.stereotype.Component;
18 18  
19 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 23 import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.beans.factory.annotation.Qualifier;
25 25 import org.springframework.beans.factory.annotation.Value;
26   -import org.springframework.context.annotation.ComponentScan;
  26 +// import org.springframework.context.annotation.ComponentScan;
27 27 import org.springframework.context.annotation.DependsOn;
28 28 import org.springframework.context.annotation.Lazy;
29 29 import org.springframework.stereotype.Component;
... ... @@ -47,8 +47,7 @@ import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
47 47 public class SIPCommander implements ISIPCommander {
48 48  
49 49 private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
50   -
51   -
  50 +
52 51 @Autowired
53 52 private SipConfig sipConfig;
54 53  
... ... @@ -623,11 +622,67 @@ public class SIPCommander implements ISIPCommander {
623 622 */
624 623 @Override
625 624 public boolean audioBroadcastCmd(Device device, String channelId) {
626   - // TODO Auto-generated method stub
  625 + // 改为新的实现
627 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 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 15 import org.springframework.beans.factory.annotation.Autowired;
16 16 import org.springframework.beans.factory.annotation.Qualifier;
17 17 import org.springframework.beans.factory.annotation.Value;
18   -import org.springframework.context.annotation.ComponentScan;
  18 +// import org.springframework.context.annotation.ComponentScan;
19 19 import org.springframework.context.annotation.DependsOn;
20 20 import org.springframework.context.annotation.Lazy;
21 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 14 import com.genersoft.iot.vmp.conf.MediaServerConfig;
15 15 import com.genersoft.iot.vmp.gb28181.bean.Device;
16 16 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  17 +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
17 18 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
18 19 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
19 20 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
... ... @@ -74,144 +75,216 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
74 75 Request request = evt.getRequest();
75 76 SipURI sipURI = (SipURI) request.getRequestURI();
76 77 String channelId = sipURI.getUser();
77   - String platformId = null;
  78 + String requesterId = null;
78 79  
79 80 FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
80 81 AddressImpl address = (AddressImpl) fromHeader.getAddress();
81 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 87 responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误
87 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 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 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 93 private static final String MESSAGE_ALARM = "Alarm";
94 94 private static final String MESSAGE_RECORD_INFO = "RecordInfo";
95 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 97 private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
98 98 private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl";
99 99 private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig";
... ... @@ -123,7 +123,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
123 123 logger.info("接收到Catalog消息");
124 124 processMessageCatalogList(evt);
125 125 } else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
126   - //DeviceInfo消息处理
  126 + // DeviceInfo消息处理
127 127 processMessageDeviceInfo(evt);
128 128 } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
129 129 // DeviceStatus消息处理
... ... @@ -149,6 +149,9 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
149 149 } else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
150 150 logger.info("接收到PresetQuery消息");
151 151 processMessagePresetQuery(evt);
  152 + } else if (MESSAGE_BROADCAST.equals(cmd)) {
  153 + // Broadcast消息处理
  154 + processMessageBroadcast(evt);
152 155 } else {
153 156 logger.info("接收到消息:" + cmd);
154 157 responseAck(evt);
... ... @@ -298,7 +301,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
298 301 // 远程启动功能
299 302 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
300 303 if (deviceId.equals(targetGBId)) {
301   - // 远程启动功能:需要在重新启动程序后先对SipStack解绑
  304 + // 远程启动本平台:需要在重新启动程序后先对SipStack解绑
302 305 logger.info("执行远程启动本平台命令");
303 306 ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
304 307 cmderFroPlatform.unregister(parentPlatform, null, null);
... ... @@ -333,6 +336,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
333 336 // 远程启动指定设备
334 337 }
335 338 }
  339 + // 云台/前端控制命令
336 340 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
337 341 String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
338 342 Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
... ... @@ -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 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 50 */
51 51 @Override
52 52 public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
53   - // TODO Auto-generated method stub
54 53 Response response = evt.getResponse();
55 54 CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
56 55 String callId = callIdHeader.getCallId();
... ...
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
... ... @@ -81,6 +81,8 @@ public interface IRedisCatchStorage {
81 81  
82 82 void delPlatformRegisterInfo(String callId);
83 83  
  84 + void cleanPlatformRegisterInfos();
  85 +
84 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 13  
14 14 import java.util.*;
15 15  
  16 +@SuppressWarnings("rawtypes")
16 17 @Component
17 18 public class RedisCatchStorageImpl implements IRedisCatchStorage {
18 19  
... ... @@ -213,6 +214,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
213 214 }
214 215  
215 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 225 public void updateSendRTPSever(SendRtpItem sendRtpItem) {
217 226 String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + sendRtpItem.getPlatformId() + "_" + sendRtpItem.getChannelId();
218 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 2  
3 3 import com.genersoft.iot.vmp.common.StreamInfo;
4 4 import com.genersoft.iot.vmp.conf.MediaServerConfig;
  5 +import com.genersoft.iot.vmp.gb28181.bean.Device;
5 6 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
6 7 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
7 8 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
... ... @@ -27,6 +28,8 @@ import org.springframework.web.context.request.async.DeferredResult;
27 28  
28 29 import java.util.UUID;
29 30  
  31 +import javax.sip.message.Response;
  32 +
30 33 @CrossOrigin
31 34 @RestController
32 35 @RequestMapping("/api")
... ... @@ -204,5 +207,47 @@ public class PlayController {
204 207 }
205 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 99 currentPage: parseInt(this.$route.params.page),
100 100 count: parseInt(this.$route.params.count),
101 101 total: 0,
102   - beforeUrl: "/videoList",
  102 + beforeUrl: "/deviceList",
103 103 isLoging: false,
104 104 autoList: true
105 105 };
... ... @@ -131,7 +131,7 @@ export default {
131 131 this.currentPage = parseInt(this.$route.params.page);
132 132 this.count = parseInt(this.$route.params.count);
133 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 81 parentChannelId: this.$route.params.parentChannelId,
82 82 updateLooper: 0, //数据刷新轮训标志
83 83 total: 0,
84   - beforeUrl: "/videoList",
  84 + beforeUrl: "/deviceList",
85 85 isLoging: false,
86 86 autoList: false,
87 87 };
... ... @@ -111,7 +111,7 @@ export default {
111 111 // this.currentPage = parseInt(this.$route.params.page);
112 112 // this.count = parseInt(this.$route.params.count);
113 113 // if (this.parentChannelId == "" || this.parentChannelId == 0) {
114   - // this.beforeUrl = "/videoList";
  114 + // this.beforeUrl = "/deviceList";
115 115 // }
116 116 },
117 117 initBaiduMap() {
... ...