Commit bb22908cf7cf698853a06d51593a22eaa64e789d

Authored by 648540858
Committed by GitHub
2 parents b05ddb20 2a273acd

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

增加上级平台信令功能实现,解决上级点播的一些问题
Showing 19 changed files with 391 additions and 98 deletions
README.md
... ... @@ -60,21 +60,24 @@ https://gitee.com/18010473990/wvp-GB28181.git
60 60 15. 支持订阅与通知方法
61 61 - [X] 移动位置订阅
62 62 - [X] 移动位置通知处理
63   - - [ ] 报警事件订阅
  63 + - [X] 报警事件订阅
64 64 - [X] 报警事件通知处理
65 65 - [ ] 设备目录订阅
66 66 - [X] 设备目录通知处理
67 67 16. 移动位置查询和显示,可通过配置文件设置移动位置历史是否存储
68 68  
69 69 # 2.0 支持特性
70   -- [ ] 国标通道向上级联
  70 +- [X] 国标通道向上级联
71 71 - [X] WEB添加上级平台
72 72 - [X] 注册
73 73 - [X] 心跳保活
74 74 - [X] 通道选择
75 75 - [X] 通道推送
76   - - [ ] 点播
77   - - [ ] 云台控制
  76 + - [X] 点播
  77 + - [X] 云台控制
  78 + - [X] 平台状态查询
  79 + - [X] 平台信息查询
  80 + - [X] 平台远程启动
78 81 - [ ] 添加RTSP视频
79 82 - [ ] 添加ONVIF探测局域网内的设备
80 83 - [ ] 添加RTMP视频
... ...
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
... ... @@ -4,10 +4,20 @@ import java.util.logging.LogManager;
4 4  
5 5 import org.springframework.boot.SpringApplication;
6 6 import org.springframework.boot.autoconfigure.SpringBootApplication;
  7 +import org.springframework.context.ConfigurableApplicationContext;
7 8  
8 9 @SpringBootApplication
9 10 public class VManageBootstrap extends LogManager {
  11 + private static String[] args;
  12 + private static ConfigurableApplicationContext context;
10 13 public static void main(String[] args) {
11   - SpringApplication.run(VManageBootstrap.class, args);
  14 + VManageBootstrap.args = args;
  15 + VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
  16 + }
  17 + // 项目重启
  18 + public static void restart() {
  19 + context.close();
  20 + VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
  21 +
12 22 }
13 23 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
... ... @@ -15,6 +15,7 @@ import org.springframework.stereotype.Component;
15 15 public class VideoStreamSessionManager {
16 16  
17 17 private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
  18 + private ConcurrentHashMap<String, String> ssrcMap = new ConcurrentHashMap<>();
18 19  
19 20 public String createPlaySsrc(){
20 21 return SsrcUtil.getPlaySsrc();
... ... @@ -24,16 +25,18 @@ public class VideoStreamSessionManager {
24 25 return SsrcUtil.getPlayBackSsrc();
25 26 }
26 27  
27   - public void put(String ssrc,ClientTransaction transaction){
28   - sessionMap.put(ssrc, transaction);
  28 + public void put(String streamId,String ssrc,ClientTransaction transaction){
  29 + sessionMap.put(streamId, transaction);
  30 + ssrcMap.put(streamId, ssrc);
29 31 }
30 32  
31   - public ClientTransaction get(String ssrc){
32   - return sessionMap.get(ssrc);
  33 + public ClientTransaction get(String streamId){
  34 + return sessionMap.get(streamId);
33 35 }
34 36  
35   - public void remove(String ssrc) {
36   - sessionMap.remove(ssrc);
37   - SsrcUtil.releaseSsrc(ssrc);
  37 + public void remove(String streamId) {
  38 + sessionMap.remove(streamId);
  39 + SsrcUtil.releaseSsrc(ssrcMap.get(streamId));
  40 + ssrcMap.remove(streamId);
38 41 }
39 42 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
... ... @@ -156,6 +156,7 @@ public class SIPProcessorFactory {
156 156 processor.setRequestEvent(evt);
157 157 processor.setRedisCatchStorage(redisCatchStorage);
158 158 processor.setZlmrtpServerFactory(zlmrtpServerFactory);
  159 + processor.setSIPCommander(cmder);
159 160 return processor;
160 161 } else if (Request.CANCEL.equals(method)) {
161 162 CancelRequestProcessor processor = new CancelRequestProcessor();
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
... ... @@ -78,6 +78,14 @@ public interface ISIPCommander {
78 78 boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2);
79 79  
80 80 /**
  81 + * 前端控制指令(用于转发上级指令)
  82 + * @param device 控制设备
  83 + * @param channelId 预览通道
  84 + * @param cmdString 前端控制指令串
  85 + */
  86 + boolean fronEndCmd(Device device, String channelId, String cmdString);
  87 +
  88 + /**
81 89 * 请求预览视频流
82 90 *
83 91 * @param device 视频设备
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
... ... @@ -42,4 +42,23 @@ public interface ISIPCommanderForPlatform {
42 42 * @return
43 43 */
44 44 boolean catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size);
  45 +
  46 + /**
  47 + * 向上级回复DeviceInfo查询信息
  48 + * @param parentPlatform 平台信息
  49 + * @param sn
  50 + * @param fromTag
  51 + * @return
  52 + */
  53 + boolean deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag);
  54 +
  55 + /**
  56 + * 向上级回复DeviceStatus查询信息
  57 + * @param parentPlatform 平台信息
  58 + * @param sn
  59 + * @param fromTag
  60 + * @return
  61 + */
  62 + boolean deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag);
  63 +
45 64 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -235,7 +235,7 @@ public class SIPCommander implements ISIPCommander {
235 235 ptzXml.append("</Control>\r\n");
236 236  
237 237 String tm = Long.toString(System.currentTimeMillis());
238   - Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtz" + tm, null);
  238 + Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "z9hG4bK-ViaPtz-" + tm, "FromPtz" + tm, null);
239 239  
240 240 transmitRequest(device, request);
241 241 return true;
... ... @@ -272,7 +272,7 @@ public class SIPCommander implements ISIPCommander {
272 272 ptzXml.append("</Control>\r\n");
273 273  
274 274 String tm = Long.toString(System.currentTimeMillis());
275   - Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtz" + tm, null);
  275 + Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "z9hG4bK-ViaPtz-" + tm, "FromPtz" + tm, null);
276 276 transmitRequest(device, request);
277 277 return true;
278 278 } catch (SipException | ParseException | InvalidArgumentException e) {
... ... @@ -282,6 +282,36 @@ public class SIPCommander implements ISIPCommander {
282 282 }
283 283  
284 284 /**
  285 + * 前端控制指令(用于转发上级指令)
  286 + * @param device 控制设备
  287 + * @param channelId 预览通道
  288 + * @param cmdString 前端控制指令串
  289 + */
  290 + @Override
  291 + public boolean fronEndCmd(Device device, String channelId, String cmdString) {
  292 + try {
  293 + StringBuffer ptzXml = new StringBuffer(200);
  294 + ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
  295 + ptzXml.append("<Control>\r\n");
  296 + ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
  297 + ptzXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
  298 + ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
  299 + ptzXml.append("<PTZCmd>" + cmdString + "</PTZCmd>\r\n");
  300 + ptzXml.append("<Info>\r\n");
  301 + ptzXml.append("</Info>\r\n");
  302 + ptzXml.append("</Control>\r\n");
  303 +
  304 + String tm = Long.toString(System.currentTimeMillis());
  305 + Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "z9hG4bK-ViaPtz-" + tm, "FromPtz" + tm, null);
  306 + transmitRequest(device, request);
  307 + return true;
  308 + } catch (SipException | ParseException | InvalidArgumentException e) {
  309 + e.printStackTrace();
  310 + }
  311 + return false;
  312 + }
  313 +
  314 + /**
285 315 * 请求预览视频流
286 316 * @param device 视频设备
287 317 * @param channelId 预览通道
... ... @@ -387,9 +417,7 @@ public class SIPCommander implements ISIPCommander {
387 417 Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc);
388 418  
389 419 ClientTransaction transaction = transmitRequest(device, request, errorEvent);
390   - streamSession.put(streamId, transaction);
391   -
392   -
  420 + streamSession.put(streamId,ssrc, transaction);
393 421  
394 422 } catch ( SipException | ParseException | InvalidArgumentException e) {
395 423 e.printStackTrace();
... ... @@ -487,7 +515,7 @@ public class SIPCommander implements ISIPCommander {
487 515 Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null);
488 516  
489 517 ClientTransaction transaction = transmitRequest(device, request, errorEvent);
490   - streamSession.put(streamId, transaction);
  518 + streamSession.put(streamId, ssrc, transaction);
491 519  
492 520 } catch ( SipException | ParseException | InvalidArgumentException e) {
493 521 e.printStackTrace();
... ... @@ -893,7 +921,7 @@ public class SIPCommander implements ISIPCommander {
893 921 catalogXml.append("</Query>\r\n");
894 922  
895 923 String tm = Long.toString(System.currentTimeMillis());
896   - Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "z9hG4bK-ViaDeviceInfo" + tm, "FromDev" + tm, null);
  924 + Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "z9hG4bK-ViaDeviceInfo-" + tm, "FromDev" + tm, null);
897 925  
898 926 transmitRequest(device, request);
899 927  
... ... @@ -923,7 +951,7 @@ public class SIPCommander implements ISIPCommander {
923 951 catalogXml.append("</Query>\r\n");
924 952  
925 953 String tm = Long.toString(System.currentTimeMillis());
926   - Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "z9hG4bK-ViaCatalog" + tm, "FromCat" + tm, null);
  954 + Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "z9hG4bK-ViaCatalog-" + tm, "FromCat" + tm, null);
927 955  
928 956 transmitRequest(device, request, errorEvent);
929 957 } catch (SipException | ParseException | InvalidArgumentException e) {
... ... @@ -958,7 +986,7 @@ public class SIPCommander implements ISIPCommander {
958 986 recordInfoXml.append("</Query>\r\n");
959 987  
960 988 String tm = Long.toString(System.currentTimeMillis());
961   - Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "fromRec" + tm, null);
  989 + Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "z9hG4bK-ViaRecordInfo-" + tm, "fromRec" + tm, null);
962 990  
963 991 transmitRequest(device, request);
964 992 } catch (SipException | ParseException | InvalidArgumentException e) {
... ... @@ -1101,7 +1129,7 @@ public class SIPCommander implements ISIPCommander {
1101 1129 mobilePostitionXml.append("</Query>\r\n");
1102 1130  
1103 1131 String tm = Long.toString(System.currentTimeMillis());
1104   - Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null);
  1132 + Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null);
1105 1133  
1106 1134 transmitRequest(device, request, errorEvent);
1107 1135  
... ... @@ -1134,7 +1162,7 @@ public class SIPCommander implements ISIPCommander {
1134 1162 subscribePostitionXml.append("</Query>\r\n");
1135 1163  
1136 1164 String tm = Long.toString(System.currentTimeMillis());
1137   - Request request = headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null, expires, "presence" ); //Position;id=" + tm.substring(tm.length() - 4));
  1165 + Request request = headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, expires, "presence" ); //Position;id=" + tm.substring(tm.length() - 4));
1138 1166 transmitRequest(device, request);
1139 1167  
1140 1168 return true;
... ... @@ -1187,7 +1215,7 @@ public class SIPCommander implements ISIPCommander {
1187 1215 cmdXml.append("</Query>\r\n");
1188 1216  
1189 1217 String tm = Long.toString(System.currentTimeMillis());
1190   - Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null, expires, "presence" );
  1218 + Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, expires, "presence" );
1191 1219 transmitRequest(device, request);
1192 1220  
1193 1221 return true;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
... ... @@ -118,7 +118,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
118 118 try {
119 119  
120 120 StringBuffer keepaliveXml = new StringBuffer(200);
121   - keepaliveXml.append("<?xml version=\"1.0\"?>\r\n");//" encoding=\"GB2312\"?>\r\n");
  121 + keepaliveXml.append("<?xml version=\"1.0\"?>\r\n");
122 122 keepaliveXml.append("<Notify>\r\n");
123 123 keepaliveXml.append("<CmdType>Keepalive</CmdType>\r\n");
124 124 keepaliveXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
... ... @@ -217,4 +217,72 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
217 217 }
218 218 return true;
219 219 }
  220 +
  221 + /**
  222 + * 向上级回复DeviceInfo查询信息
  223 + * @param parentPlatform 平台信息
  224 + * @param sn
  225 + * @param fromTag
  226 + * @return
  227 + */
  228 + @Override
  229 + public boolean deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) {
  230 + if (parentPlatform == null) {
  231 + return false;
  232 + }
  233 + try {
  234 + StringBuffer deviceInfoXml = new StringBuffer(600);
  235 + deviceInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
  236 + deviceInfoXml.append("<Response>\r\n");
  237 + deviceInfoXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
  238 + deviceInfoXml.append("<SN>" +sn + "</SN>\r\n");
  239 + deviceInfoXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
  240 + deviceInfoXml.append("<DeviceName>GB28181 Video Platform</DeviceName>\r\n");
  241 + deviceInfoXml.append("<Manufacturer>Manufacturer</Manufacturer>\r\n");
  242 + deviceInfoXml.append("<Model>wvp-28181</Model>\r\n");
  243 + deviceInfoXml.append("<Firmware>2.0.202103</Firmware>\r\n");
  244 + deviceInfoXml.append("<Result>OK</Result>\r\n");
  245 + deviceInfoXml.append("</Response>\r\n");
  246 + Request request = headerProviderPlarformProvider.createMessageRequest(parentPlatform, deviceInfoXml.toString(), fromTag);
  247 + transmitRequest(parentPlatform, request);
  248 +
  249 + } catch (SipException | ParseException | InvalidArgumentException e) {
  250 + e.printStackTrace();
  251 + return false;
  252 + }
  253 + return true;
  254 + }
  255 +
  256 + /**
  257 + * 向上级回复DeviceStatus查询信息
  258 + * @param parentPlatform 平台信息
  259 + * @param sn
  260 + * @param fromTag
  261 + * @return
  262 + */
  263 + @Override
  264 + public boolean deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) {
  265 + if (parentPlatform == null) {
  266 + return false;
  267 + }
  268 + try {
  269 + StringBuffer deviceStatusXml = new StringBuffer(600);
  270 + deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
  271 + deviceStatusXml.append("<Response>\r\n");
  272 + deviceStatusXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
  273 + deviceStatusXml.append("<SN>" +sn + "</SN>\r\n");
  274 + deviceStatusXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
  275 + deviceStatusXml.append("<Result>OK</Result>\r\n");
  276 + deviceStatusXml.append("<Online>ONLINE</Online>\r\n");
  277 + deviceStatusXml.append("<Status>OK</Status>\r\n");
  278 + deviceStatusXml.append("</Response>\r\n");
  279 + Request request = headerProviderPlarformProvider.createMessageRequest(parentPlatform, deviceStatusXml.toString(), fromTag);
  280 + transmitRequest(parentPlatform, request);
  281 +
  282 + } catch (SipException | ParseException | InvalidArgumentException e) {
  283 + e.printStackTrace();
  284 + return false;
  285 + }
  286 + return true;
  287 + }
220 288 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java
... ... @@ -4,7 +4,10 @@ import java.util.HashMap;
4 4 import java.util.Map;
5 5  
6 6 import javax.sip.*;
7   -//import javax.sip.message.Request;
  7 +import javax.sip.address.SipURI;
  8 +import javax.sip.header.FromHeader;
  9 +import javax.sip.header.HeaderAddress;
  10 +import javax.sip.header.ToHeader;
8 11  
9 12 import com.genersoft.iot.vmp.common.StreamInfo;
10 13 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
... ... @@ -12,14 +15,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcesso
12 15 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
13 16 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
14 17  
15   -import org.springframework.stereotype.Component;
16   -
17 18 /**
18 19 * @Description:ACK请求处理器
19 20 * @author: swwheihei
20 21 * @date: 2020年5月3日 下午5:31:45
21 22 */
22   -@Component
23 23 public class AckRequestProcessor extends SIPRequestAbstractProcessor {
24 24  
25 25 private IRedisCatchStorage redisCatchStorage;
... ... @@ -38,10 +38,8 @@ public class AckRequestProcessor extends SIPRequestAbstractProcessor {
38 38 if (dialog == null) return;
39 39 //DialogState state = dialog.getState();
40 40 if (/*request.getMethod().equals(Request.INVITE) &&*/ dialog.getState()== DialogState.CONFIRMED) {
41   - String remoteUri = dialog.getRemoteParty().getURI().toString();
42   - String localUri = dialog.getLocalParty().getURI().toString();
43   - String platformGbId = remoteUri.substring(remoteUri.indexOf(":") + 1, remoteUri.indexOf("@"));
44   - String channelId = localUri.substring(remoteUri.indexOf(":") + 1, remoteUri.indexOf("@"));
  41 + String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
  42 + String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
45 43 SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId);
46 44 String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
47 45 String deviceId = sendRtpItem.getDeviceId();
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
2 2  
  3 +import javax.sip.address.SipURI;
3 4 import javax.sip.Dialog;
4 5 import javax.sip.DialogState;
5 6 import javax.sip.InvalidArgumentException;
6 7 import javax.sip.RequestEvent;
7 8 import javax.sip.SipException;
  9 +import javax.sip.header.FromHeader;
  10 +import javax.sip.header.HeaderAddress;
  11 +import javax.sip.header.ToHeader;
8 12 import javax.sip.message.Response;
9 13  
10 14 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
  15 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
11 16 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
12 17 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
13 18 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
... ... @@ -18,12 +23,14 @@ import java.util.Map;
18 23  
19 24 /**
20 25 * @Description: BYE请求处理器
21   - * @author: swwheihei
22   - * @date: 2020年5月3日 下午5:32:05
  26 + * @author: lawrencehj
  27 + * @date: 2021年3月9日
23 28 */
24 29 public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
25 30  
26   - private IRedisCatchStorage redisCatchStorage;
  31 + private ISIPCommander cmder;
  32 +
  33 + private IRedisCatchStorage redisCatchStorage;
27 34  
28 35 private ZLMRTPServerFactory zlmrtpServerFactory;
29 36  
... ... @@ -38,10 +45,8 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
38 45 Dialog dialog = evt.getDialog();
39 46 if (dialog == null) return;
40 47 if (dialog.getState().equals(DialogState.TERMINATED)) {
41   - String remoteUri = dialog.getRemoteParty().getURI().toString();
42   - String localUri = dialog.getLocalParty().getURI().toString();
43   - String platformGbId = remoteUri.substring(remoteUri.indexOf(":") + 1, remoteUri.indexOf("@"));
44   - String channelId = localUri.substring(remoteUri.indexOf(":") + 1, remoteUri.indexOf("@"));
  48 + String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
  49 + String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
45 50 SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId);
46 51 String streamId = sendRtpItem.getStreamId();
47 52 Map<String, Object> param = new HashMap<>();
... ... @@ -50,6 +55,11 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
50 55 param.put("stream",streamId);
51 56 System.out.println("停止向上级推流:" + streamId);
52 57 zlmrtpServerFactory.stopSendRtpStream(param);
  58 + redisCatchStorage.deleteSendRTPServer(platformGbId, channelId);
  59 + if (zlmrtpServerFactory.totalReaderCount(streamId) == 0) {
  60 + System.out.println(streamId + "无其它观看者,通知设备停止推流");
  61 + cmder.streamByeCmd(streamId);
  62 + }
53 63 }
54 64 } catch (SipException e) {
55 65 e.printStackTrace();
... ... @@ -58,8 +68,6 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
58 68 } catch (ParseException e) {
59 69 e.printStackTrace();
60 70 }
61   - // TODO 优先级99 Bye Request消息实现,此消息一般为级联消息,上级给下级发送视频停止指令
62   -
63 71 }
64 72  
65 73 /***
... ... @@ -89,4 +97,13 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
89 97 public void setZlmrtpServerFactory(ZLMRTPServerFactory zlmrtpServerFactory) {
90 98 this.zlmrtpServerFactory = zlmrtpServerFactory;
91 99 }
  100 +
  101 + public ISIPCommander getSIPCommander() {
  102 + return cmder;
  103 + }
  104 +
  105 + public void setSIPCommander(ISIPCommander cmder) {
  106 + this.cmder = cmder;
  107 + }
  108 +
92 109 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
... ... @@ -75,20 +75,6 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
75 75 SipURI sipURI = (SipURI) request.getRequestURI();
76 76 String channelId = sipURI.getUser();
77 77 String platformId = null;
78   -// SubjectHeader subjectHeader = (SubjectHeader)request.getHeader(SubjectHeader.NAME);
79   -// // 查询通道是否存在 不存在回复404
80   -// if (subjectHeader != null) { // 存在则从subjectHeader 获取平台信息
81   -// String subject = subjectHeader.getSubject();
82   -// if (subject != null) {
83   -// String[] info1 = subject.split(",");
84   -// if (info1 != null && info1 .length == 2) {
85   -// String[] info2 = info1[1].split(":");
86   -// if (info2 != null && info2.length == 2) {
87   -// platformId = info2[0];
88   -// }
89   -// }
90   -// }
91   -// }
92 78  
93 79 FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
94 80 AddressImpl address = (AddressImpl) fromHeader.getAddress();
... ... @@ -224,7 +210,9 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
224 210 e.printStackTrace();
225 211 }
226 212 }));
227   - playResult.getResult();
  213 + if (logger.isDebugEnabled()) {
  214 + logger.debug(playResult.getResult().toString());
  215 + }
228 216  
229 217 } catch (SipException | InvalidArgumentException | ParseException e) {
230 218 e.printStackTrace();
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
... ... @@ -4,14 +4,22 @@ import java.io.ByteArrayInputStream;
4 4 import java.text.ParseException;
5 5 import java.util.*;
6 6  
  7 +import javax.sip.address.SipURI;
  8 +
7 9 import javax.sip.header.FromHeader;
  10 +import javax.sip.header.HeaderAddress;
  11 +import javax.sip.header.ToHeader;
8 12 import javax.sip.InvalidArgumentException;
  13 +import javax.sip.ListeningPoint;
  14 +import javax.sip.ObjectInUseException;
9 15 import javax.sip.RequestEvent;
10 16 import javax.sip.SipException;
  17 +import javax.sip.SipProvider;
11 18 import javax.sip.message.Request;
12 19 import javax.sip.message.Response;
13 20  
14 21 import com.alibaba.fastjson.JSONObject;
  22 +import com.genersoft.iot.vmp.VManageBootstrap;
15 23 import com.genersoft.iot.vmp.common.StreamInfo;
16 24 import com.genersoft.iot.vmp.common.VideoManagerConstants;
17 25 import com.genersoft.iot.vmp.conf.UserSetup;
... ... @@ -34,6 +42,7 @@ import com.genersoft.iot.vmp.utils.SpringBeanFactory;
34 42 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
35 43 import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
36 44  
  45 +import gov.nist.javax.sip.SipStackImpl;
37 46 import gov.nist.javax.sip.address.AddressImpl;
38 47 import gov.nist.javax.sip.address.SipUri;
39 48  
... ... @@ -114,10 +123,10 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
114 123 logger.info("接收到Catalog消息");
115 124 processMessageCatalogList(evt);
116 125 } else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
117   - logger.info("接收到DeviceInfo消息");
  126 + //DeviceInfo消息处理
118 127 processMessageDeviceInfo(evt);
119 128 } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
120   - logger.info("接收到DeviceStatus消息");
  129 + // DeviceStatus消息处理
121 130 processMessageDeviceStatus(evt);
122 131 } else if (MESSAGE_DEVICE_CONTROL.equals(cmd)) {
123 132 logger.info("接收到DeviceControl消息");
... ... @@ -211,27 +220,48 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
211 220 private void processMessageDeviceStatus(RequestEvent evt) {
212 221 try {
213 222 Element rootElement = getRootElement(evt);
214   - String deviceId = XmlUtil.getText(rootElement, "DeviceID");
215   - // 检查设备是否存在, 不存在则不回复
216   - if (storager.exists(deviceId)) {
217   - // 回复200 OK
218   - responseAck(evt);
219   - JSONObject json = new JSONObject();
220   - XmlUtil.node2Json(rootElement, json);
221   - if (logger.isDebugEnabled()) {
222   - logger.debug(json.toJSONString());
223   - }
224   - RequestMessage msg = new RequestMessage();
225   - msg.setDeviceId(deviceId);
226   - msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS);
227   - msg.setData(json);
228   - deferredResultHolder.invokeResult(msg);
  223 + String name = rootElement.getName();
  224 + Element deviceIdElement = rootElement.element("DeviceID");
  225 + String deviceId = deviceIdElement.getText();
229 226  
230   - if (offLineDetector.isOnline(deviceId)) {
231   - publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
  227 + if (name.equalsIgnoreCase("Query")) { // 区分是Response——查询响应,还是Query——查询请求
  228 + logger.info("接收到DeviceStatus查询消息");
  229 + FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
  230 + String platformId = ((SipUri) fromHeader.getAddress().getURI()).getUser();
  231 + if (platformId == null) {
  232 + response404Ack(evt);
  233 + return;
232 234 } else {
  235 + // 回复200 OK
  236 + responseAck(evt);
  237 + String sn = rootElement.element("SN").getText();
  238 + ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
  239 + cmderFroPlatform.deviceStatusResponse(parentPlatform, sn, fromHeader.getTag());
  240 + }
  241 + } else {
  242 + logger.info("接收到DeviceStatus应答消息");
  243 + // 检查设备是否存在, 不存在则不回复
  244 + if (storager.exists(deviceId)) {
  245 + // 回复200 OK
  246 + responseAck(evt);
  247 + JSONObject json = new JSONObject();
  248 + XmlUtil.node2Json(rootElement, json);
  249 + if (logger.isDebugEnabled()) {
  250 + logger.debug(json.toJSONString());
  251 + }
  252 + RequestMessage msg = new RequestMessage();
  253 + msg.setDeviceId(deviceId);
  254 + msg.setType(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS);
  255 + msg.setData(json);
  256 + deferredResultHolder.invokeResult(msg);
  257 +
  258 + if (offLineDetector.isOnline(deviceId)) {
  259 + publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
  260 + } else {
  261 + }
233 262 }
234 263 }
  264 +
235 265 } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
236 266 e.printStackTrace();
237 267 }
... ... @@ -263,6 +293,51 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
263 293 deferredResultHolder.invokeResult(msg);
264 294 } else {
265 295 // 此处是上级发出的DeviceControl指令
  296 + String platformId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
  297 + String targetGBId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
  298 + // 远程启动功能
  299 + if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
  300 + if (deviceId.equals(targetGBId)) {
  301 + // 远程启动功能:需要在重新启动程序后先对SipStack解绑
  302 + logger.info("执行远程启动本平台命令");
  303 + ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
  304 + cmderFroPlatform.unregister(parentPlatform, null, null);
  305 +
  306 + Thread restartThread = new Thread(new Runnable() {
  307 + @Override
  308 + public void run() {
  309 + try {
  310 + Thread.sleep(3000);
  311 + SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
  312 + SipStackImpl stack = (SipStackImpl)up.getSipStack();
  313 + stack.stop();
  314 + Iterator listener = stack.getListeningPoints();
  315 + while (listener.hasNext()) {
  316 + stack.deleteListeningPoint((ListeningPoint) listener.next());
  317 + }
  318 + Iterator providers = stack.getSipProviders();
  319 + while (providers.hasNext()) {
  320 + stack.deleteSipProvider((SipProvider) providers.next());
  321 + }
  322 + VManageBootstrap.restart();
  323 + } catch (InterruptedException ignored) {
  324 + } catch (ObjectInUseException e) {
  325 + e.printStackTrace();
  326 + }
  327 + }
  328 + });
  329 +
  330 + restartThread.setDaemon(false);
  331 + restartThread.start();
  332 + } else {
  333 + // 远程启动指定设备
  334 + }
  335 + }
  336 + if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
  337 + String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
  338 + Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
  339 + cmder.fronEndCmd(device, deviceId, cmdString);
  340 + }
266 341 }
267 342 } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
268 343 e.printStackTrace();
... ... @@ -374,9 +449,21 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
374 449 Element deviceIdElement = rootElement.element("DeviceID");
375 450 String deviceId = deviceIdElement.getTextTrim().toString();
376 451 if (requestName.equals("Query")) {
377   - // 回复200 OK
378   - responseAck(evt);
  452 + logger.info("接收到DeviceInfo查询消息");
  453 + FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
  454 + String platformId = ((SipUri) fromHeader.getAddress().getURI()).getUser();
  455 + if (platformId == null) {
  456 + response404Ack(evt);
  457 + return;
  458 + } else {
  459 + // 回复200 OK
  460 + responseAck(evt);
  461 + String sn = rootElement.element("SN").getText();
  462 + ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
  463 + cmderFroPlatform.deviceInfoResponse(parentPlatform, sn, fromHeader.getTag());
  464 + }
379 465 } else {
  466 + logger.info("接收到DeviceInfo应答消息");
380 467 Device device = storager.queryVideoDevice(deviceId);
381 468 if (device == null) {
382 469 return;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java
... ... @@ -60,16 +60,17 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor {
60 60 logger.info(String.format("未找到callId: %s 的注册/注销平台id", callId ));
61 61 return;
62 62 }
63   - logger.info(String.format("收到 %s 的注册/注销%S响应", platformGBId, response.getStatusCode() ));
64 63  
65 64 ParentPlatformCatch parentPlatformCatch = redisCatchStorage.queryPlatformCatchInfo(platformGBId);
66 65 if (parentPlatformCatch == null) {
67 66 logger.warn(String.format("收到 %s 的注册/注销%S请求, 但是平台缓存信息未查询到!!!", platformGBId, response.getStatusCode()));
68 67 return;
69 68 }
  69 + String action = parentPlatformCatch.getParentPlatform().getExpires().equals("0") ? "注销" : "注册";
  70 + logger.info(String.format("收到 %s %s的%S响应", platformGBId, action, response.getStatusCode() ));
70 71 ParentPlatform parentPlatform = parentPlatformCatch.getParentPlatform();
71 72 if (parentPlatform == null) {
72   - logger.warn(String.format("收到 %s 的注册/注销%S请求, 但是平台信息未查询到!!!", platformGBId, response.getStatusCode()));
  73 + logger.warn(String.format("收到 %s %s的%S请求, 但是平台信息未查询到!!!", platformGBId, action, response.getStatusCode()));
73 74 return;
74 75 }
75 76  
... ... @@ -77,11 +78,16 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor {
77 78 WWWAuthenticateHeader www = (WWWAuthenticateHeader)response.getHeader(WWWAuthenticateHeader.NAME);
78 79 sipCommanderForPlatform.register(parentPlatform, callId, www, null, null);
79 80 }else if (response.getStatusCode() == 200){
80   - // 注册成功
81   - logger.info(String.format("%s 注册成功", platformGBId ));
  81 + // 注册/注销成功
  82 + logger.info(String.format("%s %s成功", platformGBId, action));
82 83 redisCatchStorage.delPlatformRegisterInfo(callId);
83 84 parentPlatform.setStatus(true);
  85 + // 取回Expires设置,避免注销过程中被置为0
  86 + ParentPlatform parentPlatformTmp = storager.queryParentPlatById(platformGBId);
  87 + String expires = parentPlatformTmp.getExpires();
  88 + parentPlatform.setExpires(expires);
84 89 storager.updateParentPlatform(parentPlatform);
  90 +
85 91 redisCatchStorage.updatePlatformRegister(parentPlatform);
86 92  
87 93 redisCatchStorage.updatePlatformKeepalive(parentPlatform);
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -267,20 +267,25 @@ public class ZLMHttpHookListener {
267 267 }
268 268  
269 269 String streamId = json.getString("stream");
270   -
271   - cmder.streamByeCmd(streamId);
272 270 StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
273   - if (streamInfo!=null){
274   - redisCatchStorage.stopPlay(streamInfo);
275   - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  271 +
  272 + JSONObject ret = new JSONObject();
  273 + ret.put("code", 0);
  274 + ret.put("close", true);
  275 +
  276 + if (streamInfo != null) {
  277 + if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) {
  278 + ret.put("close", false);
  279 + } else {
  280 + cmder.streamByeCmd(streamId);
  281 + redisCatchStorage.stopPlay(streamInfo);
  282 + storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  283 + }
276 284 }else{
  285 + cmder.streamByeCmd(streamId);
277 286 streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
278 287 redisCatchStorage.stopPlayback(streamInfo);
279 288 }
280   -
281   - JSONObject ret = new JSONObject();
282   - ret.put("code", 0);
283   - ret.put("close", true);
284 289 return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
285 290 }
286 291  
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -153,6 +153,16 @@ public class ZLMRTPServerFactory {
153 153 }
154 154  
155 155 /**
  156 + * 查询转推的流是否有其它观看者
  157 + * @param streamId
  158 + * @return
  159 + */
  160 + public int totalReaderCount(String streamId) {
  161 + JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
  162 + return mediaInfo.getInteger("totalReaderCount");
  163 + }
  164 +
  165 + /**
156 166 * 调用zlm RESTful API —— stopSendRtp
157 167 */
158 168 public Boolean stopSendRtpStream(Map<String, Object>param) {
... ...
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
... ... @@ -89,4 +89,17 @@ public interface IRedisCatchStorage {
89 89 */
90 90 SendRtpItem querySendRTPServer(String platformGbId, String channelId);
91 91  
  92 + /**
  93 + * 删除RTP推送信息缓存
  94 + * @param platformGbId
  95 + * @param channelId
  96 + */
  97 + void deleteSendRTPServer(String platformGbId, String channelId);
  98 +
  99 + /**
  100 + * 查询某个通道是否存在上级点播(RTP推送)
  101 + * @param channelId
  102 + */
  103 + boolean isChannelSendingRTP(String channelId);
  104 +
92 105 }
... ...
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
... ... @@ -225,4 +225,30 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
225 225 return (SendRtpItem)redis.get(key);
226 226 }
227 227  
  228 + /**
  229 + * 删除RTP推送信息缓存
  230 + * @param platformGbId
  231 + * @param channelId
  232 + */
  233 + @Override
  234 + public void deleteSendRTPServer(String platformGbId, String channelId) {
  235 + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + platformGbId + "_" + channelId;
  236 + redis.del(key);
  237 + }
  238 +
  239 + /**
  240 + * 查询某个通道是否存在上级点播(RTP推送)
  241 + * @param channelId
  242 + */
  243 + @Override
  244 + public boolean isChannelSendingRTP(String channelId) {
  245 + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + "*_" + channelId;
  246 + List<Object> RtpStreams = redis.scan(key);
  247 + if (RtpStreams.size() > 0) {
  248 + return true;
  249 + } else {
  250 + return false;
  251 + }
  252 + }
  253 +
228 254 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/platform/PlatformController.java
... ... @@ -60,7 +60,7 @@ public class PlatformController {
60 60 public ResponseEntity<String> savePlatform(@RequestBody ParentPlatform parentPlatform){
61 61  
62 62 if (logger.isDebugEnabled()) {
63   - logger.debug("查询所有上级设备API调用");
  63 + logger.debug("保存上级平台信息API调用");
64 64 }
65 65 if (StringUtils.isEmpty(parentPlatform.getName())
66 66 ||StringUtils.isEmpty(parentPlatform.getServerGBId())
... ... @@ -87,13 +87,13 @@ public class PlatformController {
87 87 if (parentPlatform.isEnable()) {
88 88 // 只要保存就发送注册
89 89 commanderForPlatform.register(parentPlatform);
90   - }else if (parentPlatformOld != null && parentPlatformOld.isEnable() && !parentPlatform.isEnable()){ // 关闭启用时注销
  90 + } else if (parentPlatformOld != null && parentPlatformOld.isEnable() && !parentPlatform.isEnable()){ // 关闭启用时注销
91 91 commanderForPlatform.unregister(parentPlatform, null, null);
92 92 }
93 93  
94   -
  94 +
95 95 return new ResponseEntity<>("success", HttpStatus.OK);
96   - }else {
  96 + } else {
97 97 return new ResponseEntity<>("fail", HttpStatus.OK);
98 98 }
99 99 }
... ... @@ -103,7 +103,7 @@ public class PlatformController {
103 103 public ResponseEntity<String> deletePlatform(@RequestBody ParentPlatform parentPlatform){
104 104  
105 105 if (logger.isDebugEnabled()) {
106   - logger.debug("查询所有上级设备API调用");
  106 + logger.debug("删除上级平台API调用");
107 107 }
108 108 if (StringUtils.isEmpty(parentPlatform.getServerGBId())
109 109 ){
... ... @@ -138,7 +138,7 @@ public class PlatformController {
138 138 public ResponseEntity<String> exitPlatform(@PathVariable String deviceGbId){
139 139  
140 140 if (logger.isDebugEnabled()) {
141   - logger.debug("查询所有上级设备API调用");
  141 + logger.debug("查询上级平台是否存在API调用:" + deviceGbId);
142 142 }
143 143 ParentPlatform parentPlatform = storager.queryParentPlatById(deviceGbId);
144 144 return new ResponseEntity<>(String.valueOf(parentPlatform != null), HttpStatus.OK);
... ... @@ -184,7 +184,7 @@ public class PlatformController {
184 184 public ResponseEntity<String> delChannelForGB(@RequestBody UpdateChannelParam param){
185 185  
186 186 if (logger.isDebugEnabled()) {
187   - logger.debug("给上级平台添加国标通道API调用");
  187 + logger.debug("给上级平台删除国标通道API调用");
188 188 }
189 189 int result = storager.delChannelForGB(param.getPlatformId(), param.getChannelReduces());
190 190  
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
... ... @@ -86,6 +86,9 @@ public class PlayServiceImpl implements IPlayService {
86 86 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
87 87 msg.setData(JSON.toJSONString(streamInfo));
88 88 resultHolder.invokeResult(msg);
  89 + if (hookEvent != null) {
  90 + hookEvent.response(JSONObject.parseObject(JSON.toJSONString(streamInfo)));
  91 + }
89 92 } else {
90 93 redisCatchStorage.stopPlay(streamInfo);
91 94 storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
... ...