Commit f62bf7b2c6239f2c67f5d9019f8302c8d441f870

Authored by 648540858
2 parents 1294081a bfae9780

Merge branch '2.6.8' into wvp-28181-2.0

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/Gb28181Sdp.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
Showing 21 changed files with 489 additions and 122 deletions
doc/_sidebar.md
1 1 <!-- 侧边栏 -->
2 2  
3 3 * **编译与部署**
  4 + * [测试](_content/introduction/test.md)
4 5 * [编译](_content/introduction/compile.md)
5 6 * [配置](_content/introduction/config.md)
6 7 * [部署](_content/introduction/deployment.md)
... ...
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
... ... @@ -100,6 +100,21 @@ public class VideoManagerConstants {
100 100 */
101 101 public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED";
102 102  
  103 + /**
  104 + * redis 消息通知上级平台开始观看流
  105 + */
  106 + public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY";
  107 +
  108 + /**
  109 + * redis 消息通知上级平台停止观看流
  110 + */
  111 + public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY";
  112 +
  113 + /**
  114 + * redis 消息接收关闭一个推流
  115 + */
  116 + public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED";
  117 +
103 118  
104 119 /**
105 120 * redis 消息通知平台通知设备推流结果
... ...
src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java
... ... @@ -46,6 +46,9 @@ public class RedisMsgListenConfig {
46 46 @Autowired
47 47 private RedisCloseStreamMsgListener redisCloseStreamMsgListener;
48 48  
  49 + @Autowired
  50 + private RedisPushStreamCloseResponseListener redisPushStreamCloseResponseListener;
  51 +
49 52  
50 53 /**
51 54 * redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
... ... @@ -67,6 +70,7 @@ public class RedisMsgListenConfig {
67 70 container.addMessageListener(redisPushStreamListMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_PUSH_STREAM_LIST_CHANGE));
68 71 container.addMessageListener(redisPushStreamResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_RESPONSE));
69 72 container.addMessageListener(redisCloseStreamMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE));
  73 + container.addMessageListener(redisPushStreamCloseResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED));
70 74 return container;
71 75 }
72 76 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
... ... @@ -64,7 +64,7 @@ public class SipLayer implements CommandLineRunner {
64 64 try {
65 65 sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
66 66 } catch (PeerUnavailableException e) {
67   - logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
  67 + logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
68 68 return;
69 69 }
70 70  
... ... @@ -76,12 +76,12 @@ public class SipLayer implements CommandLineRunner {
76 76 tcpSipProvider.addSipListener(sipProcessorObserver);
77 77 tcpSipProviderMap.put(monitorIp, tcpSipProvider);
78 78  
79   - logger.info("[Sip Server] tcp://{}:{} 启动成功", monitorIp, port);
  79 + logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port);
80 80 } catch (TransportNotSupportedException
81 81 | TooManyListenersException
82 82 | ObjectInUseException
83 83 | InvalidArgumentException e) {
84   - logger.error("[Sip Server] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
  84 + logger.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
85 85 , monitorIp, port);
86 86 }
87 87  
... ... @@ -93,12 +93,12 @@ public class SipLayer implements CommandLineRunner {
93 93  
94 94 udpSipProviderMap.put(monitorIp, udpSipProvider);
95 95  
96   - logger.info("[Sip Server] udp://{}:{} 启动成功", monitorIp, port);
  96 + logger.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port);
97 97 } catch (TransportNotSupportedException
98 98 | TooManyListenersException
99 99 | ObjectInUseException
100 100 | InvalidArgumentException e) {
101   - logger.error("[Sip Server] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
  101 + logger.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
102 102 , monitorIp, port);
103 103 }
104 104 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
... ... @@ -283,7 +283,7 @@ public class SIPRequestHeaderPlarformProvider {
283 283 viaHeader.setRPort();
284 284 viaHeaders.add(viaHeader);
285 285 // from
286   - SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(),
  286 + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sendRtpItem.getChannelId(),
287 287 platform.getDeviceIp() + ":" + platform.getDevicePort());
288 288 Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
289 289 FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag());
... ... @@ -296,13 +296,10 @@ public class SIPRequestHeaderPlarformProvider {
296 296 MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
297 297 // ceq
298 298 CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
299   - MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory();
300   - // 设置编码, 防止中文乱码
301   - messageFactory.setDefaultContentEncodingCharset("gb2312");
302 299  
303 300 CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
304 301  
305   - request = (SIPRequest) messageFactory.createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader,
  302 + request = (SIPRequest) SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader,
306 303 toHeader, viaHeaders, maxForwards);
307 304  
308 305 request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
... ... @@ -310,6 +307,7 @@ public class SIPRequestHeaderPlarformProvider {
310 307 String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
311 308 Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory()
312 309 .createSipURI(platform.getDeviceGBId(), sipAddress));
  310 +
313 311 request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
314 312  
315 313 return request;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -371,7 +371,6 @@ public class SIPCommander implements ISIPCommander {
371 371 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
372 372 errorEvent.response(e);
373 373 }), e -> {
374   - // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
375 374 ResponseEvent responseEvent = (ResponseEvent) e.event;
376 375 SIPResponse response = (SIPResponse) responseEvent.getResponse();
377 376 streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
... ... @@ -215,6 +215,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
215 215 }else {
216 216 if (channel.getChannelId().length() != 20) {
217 217 catalogXml.append("</Item>\r\n");
  218 + logger.warn("[编号长度异常] {} 长度错误,请使用20位长度的国标编号,当前长度:{}", channel.getChannelId(), channel.getChannelId().length());
  219 + catalogXml.append("</Item>\r\n");
218 220 continue;
219 221 }
220 222 switch (Integer.parseInt(channel.getChannelId().substring(10, 13))){
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
... ... @@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
3 3 import com.alibaba.fastjson2.JSON;
4 4 import com.alibaba.fastjson2.JSONObject;
5 5 import com.genersoft.iot.vmp.conf.DynamicTask;
  6 +import com.genersoft.iot.vmp.conf.UserSetting;
  7 +import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
6 8 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
7 9 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
8 10 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
... ... @@ -14,6 +16,7 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
14 16 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
15 17 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
16 18 import com.genersoft.iot.vmp.service.IMediaServerService;
  19 +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
17 20 import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
18 21 import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
19 22 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
... ... @@ -58,6 +61,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
58 61 private IRedisCatchStorage redisCatchStorage;
59 62  
60 63 @Autowired
  64 + private UserSetting userSetting;
  65 +
  66 + @Autowired
61 67 private IVideoManagerStorage storager;
62 68  
63 69 @Autowired
... ... @@ -155,6 +161,13 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
155 161 } else if (jsonObject.getInteger("code") == 0) {
156 162 logger.info("调用ZLM推流接口, 结果: {}", jsonObject);
157 163 logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
  164 + if (sendRtpItem.getPlayType() == InviteStreamType.PUSH) {
  165 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStreamId(),
  166 + sendRtpItem.getChannelId(), parentPlatform.getServerGBId(), parentPlatform.getName(), userSetting.getServerId(),
  167 + sendRtpItem.getMediaServerId());
  168 + messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
  169 + redisCatchStorage.sendPlatformStartPlayMsg(messageForPushChannel);
  170 + }
158 171 } else {
159 172 logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
160 173 if (sendRtpItem.isOnlyAudio()) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
... ... @@ -2,12 +2,15 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
2 2  
3 3 import com.genersoft.iot.vmp.common.InviteInfo;
4 4 import com.genersoft.iot.vmp.common.InviteSessionType;
  5 +import com.genersoft.iot.vmp.common.StreamInfo;
  6 +import com.genersoft.iot.vmp.conf.UserSetting;
5 7 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
6 8 import com.genersoft.iot.vmp.gb28181.bean.Device;
7 9 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
8 10 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
9 11 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
10 12 import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
  13 +import com.genersoft.iot.vmp.gb28181.bean.*;
11 14 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
12 15 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
13 16 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
... ... @@ -15,9 +18,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor
15 18 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
16 19 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
17 20 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  21 +import com.genersoft.iot.vmp.service.IDeviceChannelService;
18 22 import com.genersoft.iot.vmp.service.IDeviceService;
19 23 import com.genersoft.iot.vmp.service.IInviteStreamService;
20 24 import com.genersoft.iot.vmp.service.IMediaServerService;
  25 +import com.genersoft.iot.vmp.service.IPlatformService;
21 26 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
22 27 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
23 28 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
... ... @@ -32,10 +37,10 @@ import javax.sip.InvalidArgumentException;
32 37 import javax.sip.RequestEvent;
33 38 import javax.sip.SipException;
34 39 import javax.sip.address.SipURI;
  40 +import javax.sip.InvalidArgumentException;
  41 +import javax.sip.RequestEvent;
  42 +import javax.sip.SipException;
35 43 import javax.sip.header.CallIdHeader;
36   -import javax.sip.header.FromHeader;
37   -import javax.sip.header.HeaderAddress;
38   -import javax.sip.header.ToHeader;
39 44 import javax.sip.message.Response;
40 45 import java.text.ParseException;
41 46 import java.util.HashMap;
... ... @@ -60,9 +65,15 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
60 65 private IInviteStreamService inviteStreamService;
61 66  
62 67 @Autowired
  68 + private IPlatformService platformService;
  69 +
  70 + @Autowired
63 71 private IDeviceService deviceService;
64 72  
65 73 @Autowired
  74 + private IDeviceChannelService channelService;
  75 +
  76 + @Autowired
66 77 private IVideoManagerStorage storager;
67 78  
68 79 @Autowired
... ... @@ -80,6 +91,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
80 91 @Autowired
81 92 private VideoStreamSessionManager streamSession;
82 93  
  94 + @Autowired
  95 + private UserSetting userSetting;
  96 +
83 97 @Override
84 98 public void afterPropertiesSet() throws Exception {
85 99 // 添加消息处理的订阅
... ... @@ -92,93 +106,91 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
92 106 */
93 107 @Override
94 108 public void process(RequestEvent evt) {
95   -
  109 + SIPRequest request = (SIPRequest) evt.getRequest();
96 110 try {
97   - responseAck((SIPRequest) evt.getRequest(), Response.OK);
  111 + responseAck(request, Response.OK);
98 112 } catch (SipException | InvalidArgumentException | ParseException e) {
99 113 logger.error("[回复BYE信息失败],{}", e.getMessage());
100 114 }
101 115 CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
102   - String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
103   - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
104   - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
105   - logger.info("[收到bye] {}/{}", platformGbId, channelId);
106   - if (sendRtpItem != null){
107   - String streamId = sendRtpItem.getStreamId();
108   - Map<String, Object> param = new HashMap<>();
109   - param.put("vhost","__defaultVhost__");
110   - param.put("app",sendRtpItem.getApp());
111   - param.put("stream",streamId);
112   - param.put("ssrc",sendRtpItem.getSsrc());
113   - logger.info("[收到bye] 停止向上级推流:{}", streamId);
114   - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
115   - redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
116   - ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
117   - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
118   - int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
119   - if (totalReaderCount <= 0) {
120   - logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
121   - if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
122   - Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
123   - if (device == null) {
124   - logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
125   - }
126   - try {
127   - logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId);
128   - cmder.streamByeCmd(device, channelId, streamId, null);
129   - } catch (InvalidArgumentException | ParseException | SipException |
130   - SsrcTransactionNotFoundException e) {
131   - logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
132   - }
133   - }
134   - if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
135   - MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
136   - sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
137   - sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
138   - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
139   - }
  116 +
  117 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
  118 +
  119 + if (sendRtpItem != null){
  120 + logger.info("[收到bye] 来自平台{}, 停止通道:{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId());
  121 + String streamId = sendRtpItem.getStreamId();
  122 + Map<String, Object> param = new HashMap<>();
  123 + param.put("vhost","__defaultVhost__");
  124 + param.put("app",sendRtpItem.getApp());
  125 + param.put("stream",streamId);
  126 + param.put("ssrc",sendRtpItem.getSsrc());
  127 + logger.info("[收到bye] 停止向上级推流:{}", streamId);
  128 + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  129 + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
  130 + callIdHeader.getCallId(), null);
  131 + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
  132 + if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
  133 + ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
  134 + if (platform != null) {
  135 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
  136 + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
  137 + sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
  138 + messageForPushChannel.setPlatFormIndex(platform.getId());
  139 + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
  140 + }else {
  141 + logger.info("[上级平台停止观看] 未找到平台{}的信息,发送redis消息失败", sendRtpItem.getPlatformId());
140 142 }
141 143 }
142   - // 可能是设备主动停止
143   - Device device = storager.queryVideoDeviceByChannelId(platformGbId);
144   - if (device != null) {
145   - storager.stopPlay(device.getDeviceId(), channelId);
146   - SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
147   - if (ssrcTransactionForPlay != null){
148   - if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
149   - // 释放ssrc
150   - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
151   - if (mediaServerItem != null) {
152   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc());
153   - }
154   - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
155   - }
156   - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
157   - inviteStreamService.removeInviteInfo(inviteInfo);
158   - if (inviteInfo != null) {
159   - if (inviteInfo.getStreamInfo() != null) {
160   - mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
161   - }
162   - }
163   - }
164   - SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null);
165   - if (ssrcTransactionForPlayBack != null) {
166   - // 释放ssrc
167   - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId());
168   - if (mediaServerItem != null) {
169   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc());
  144 +
  145 + int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
  146 + if (totalReaderCount <= 0) {
  147 + logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
  148 + if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
  149 + Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
  150 + if (device == null) {
  151 + logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
170 152 }
171   - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
172   - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, device.getDeviceId(), channelId);
173   -
174   - if (inviteInfo != null) {
175   - inviteStreamService.removeInviteInfo(inviteInfo);
176   - if (inviteInfo.getStreamInfo() != null) {
177   - mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
178   - }
  153 + try {
  154 + logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
  155 + cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
  156 + } catch (InvalidArgumentException | ParseException | SipException |
  157 + SsrcTransactionNotFoundException e) {
  158 + logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
179 159 }
180 160 }
181 161 }
  162 + }else {
  163 + // 可能是设备发送的停止
  164 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
  165 + if (ssrcTransaction == null) {
  166 + logger.info("[收到bye] 但是无法获取推流信息和发流信息,忽略此请求");
  167 + logger.info(request.toString());
  168 + return;
  169 + }
  170 + logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
182 171  
  172 + Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
  173 + if (device == null) {
  174 + logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
  175 + return;
  176 + }
  177 + DeviceChannel channel = channelService.getOne(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
  178 + if (channel == null) {
  179 + logger.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
  180 + return;
  181 + }
  182 + storager.stopPlay(device.getDeviceId(), channel.getChannelId());
  183 + StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channel.getChannelId());
  184 + if (streamInfo != null) {
  185 + redisCatchStorage.stopPlay(streamInfo);
  186 + mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream());
  187 + }
  188 + // 释放ssrc
  189 + MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
  190 + if (mediaServerItem != null) {
  191 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
  192 + }
  193 + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getStream());
  194 + }
183 195 }
184 196 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -181,16 +181,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
181 181 return;
182 182 } else {
183 183 streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream());
184   - if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) {
185   - logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
186   - try {
187   - responseAck(request, Response.GONE);
188   - } catch (SipException | InvalidArgumentException | ParseException e) {
189   - logger.error("[命令发送失败] invite GONE: {}", e.getMessage());
190   - }
191   - return;
192   - }else {
193   - // TODO 可能漏回复消息
  184 + if (streamPushItem != null) {
  185 + mediaServerItem = mediaServerService.getOne(streamPushItem.getMediaServerId());
  186 + }
  187 + if (mediaServerItem == null) {
  188 + mediaServerItem = mediaServerService.getDefaultMediaServer();
194 189 }
195 190 }
196 191 } else {
... ... @@ -351,7 +346,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
351 346 }
352 347 logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc);
353 348 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
354   - device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp());
  349 + device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> {
  350 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  351 + });
355 352  
356 353 if (tcpActive != null) {
357 354 sendRtpItem.setTcpActive(tcpActive);
... ... @@ -557,7 +554,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
557 554 if (streamReady != null && streamReady) {
558 555 // 自平台内容
559 556 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
560   - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
  557 + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{
  558 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  559 + });
561 560  
562 561 if (sendRtpItem == null) {
563 562 logger.warn("服务器端口资源不足");
... ... @@ -596,7 +595,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
596 595 if (streamReady != null && streamReady) {
597 596 // 自平台内容
598 597 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
599   - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
  598 + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{
  599 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  600 + });
600 601  
601 602 if (sendRtpItem == null) {
602 603 logger.warn("服务器端口资源不足");
... ... @@ -712,7 +713,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
712 713 dynamicTask.stop(callIdHeader.getCallId());
713 714 if (serverId.equals(userSetting.getServerId())) {
714 715 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
715   - app, stream, channelId, mediaTransmissionTCP, platform.isRtcp());
  716 + app, stream, channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> {
  717 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  718 + });
716 719  
717 720 if (sendRtpItem == null) {
718 721 logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足");
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
... ... @@ -125,7 +125,7 @@ public class SipUtils {
125 125 strTmp = String.format("%02X", moveSpeed);
126 126 builder.append(strTmp, 0, 2);
127 127 builder.append(strTmp, 0, 2);
128   -
  128 +
129 129 //优化zoom低倍速下的变倍速率
130 130 if ((zoomSpeed > 0) && (zoomSpeed <16))
131 131 {
... ... @@ -263,4 +263,4 @@ public class SipUtils {
263 263 }
264 264 return localDateTime.format(DateUtil.formatterISO8601);
265 265 }
266 266 -}
  267 +}
267 268 \ No newline at end of file
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -22,6 +22,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
22 22 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
23 23 import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
24 24 import com.genersoft.iot.vmp.service.*;
  25 +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
25 26 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
26 27 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
27 28 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
... ... @@ -468,6 +469,13 @@ public class ZLMHttpHookListener {
468 469 }
469 470 redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
470 471 sendRtpItem.getCallId(), sendRtpItem.getStreamId());
  472 + if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
  473 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
  474 + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
  475 + sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
  476 + messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
  477 + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
  478 + }
471 479 }
472 480 }
473 481 }
... ... @@ -513,7 +521,7 @@ public class ZLMHttpHookListener {
513 521 }
514 522 return ret;
515 523 }
516   - // 推流具有主动性,暂时不做处理
  524 + // TODO 推流具有主动性,暂时不做处理
517 525 // StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
518 526 // if (streamPushItem != null) {
519 527 // // TODO 发送停止
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -221,13 +221,14 @@ public class ZLMRTPServerFactory {
221 221 * @param tcp 是否为tcp
222 222 * @return SendRtpItem
223 223 */
224   - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp, boolean rtcp){
  224 + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
  225 + String deviceId, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){
225 226  
226 227 // 默认为随机端口
227 228 int localPort = 0;
228 229 if (userSetting.getGbSendStreamStrict()) {
229 230 if (userSetting.getGbSendStreamStrict()) {
230   - localPort = keepPort(serverItem, ssrc, localPort);
  231 + localPort = keepPort(serverItem, ssrc, localPort, callback);
231 232 if (localPort == 0) {
232 233 return null;
233 234 }
... ... @@ -259,11 +260,12 @@ public class ZLMRTPServerFactory {
259 260 * @param tcp 是否为tcp
260 261 * @return SendRtpItem
261 262 */
262   - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp, boolean rtcp){
  263 + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
  264 + String app, String stream, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){
263 265 // 默认为随机端口
264 266 int localPort = 0;
265 267 if (userSetting.getGbSendStreamStrict()) {
266   - localPort = keepPort(serverItem, ssrc, localPort);
  268 + localPort = keepPort(serverItem, ssrc, localPort, callback);
267 269 if (localPort == 0) {
268 270 return null;
269 271 }
... ... @@ -284,10 +286,14 @@ public class ZLMRTPServerFactory {
284 286 return sendRtpItem;
285 287 }
286 288  
  289 + public interface KeepPortCallback{
  290 + Boolean keep(String ssrc);
  291 + }
  292 +
287 293 /**
288 294 * 保持端口,直到需要需要发流时再释放
289 295 */
290   - public int keepPort(MediaServerItem serverItem, String ssrc, Integer localPort) {
  296 + public int keepPort(MediaServerItem serverItem, String ssrc, int localPort, KeepPortCallback keepPortCallback) {
291 297 Map<String, Object> param = new HashMap<>(3);
292 298 param.put("port", localPort);
293 299 param.put("enable_tcp", 1);
... ... @@ -296,18 +302,20 @@ public class ZLMRTPServerFactory {
296 302 if (jsonObject.getInteger("code") == 0) {
297 303 localPort = jsonObject.getInteger("port");
298 304 HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId());
  305 + // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
299 306 Integer finalLocalPort = localPort;
300 307 hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout,
301 308 (MediaServerItem mediaServerItem, HookParam hookParam)->{
302 309 logger.info("[上级点播] {}->监听端口到期继续保持监听: {}", ssrc, finalLocalPort);
303 310 OnRtpServerTimeoutHookParam rtpServerTimeoutHookParam = (OnRtpServerTimeoutHookParam) hookParam;
304   - if (!ssrc.equals(rtpServerTimeoutHookParam.getSsrc())) {
305   - return;
306   - }
307   - int port = keepPort(serverItem, ssrc, finalLocalPort);
308   - if (port == 0) {
309   - logger.info("[上级点播] {}->监听端口失败,移除监听", ssrc);
310   - hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
  311 + if (ssrc.equals(rtpServerTimeoutHookParam.getSsrc())) {
  312 + if (keepPortCallback.keep(ssrc)) {
  313 + logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc);
  314 + keepPort(serverItem, ssrc, finalLocalPort, keepPortCallback);
  315 + }else {
  316 + logger.info("[上级点播] {}->发送取消,无需继续监听", ssrc);
  317 + releasePort(serverItem, ssrc);
  318 + }
311 319 }
312 320 });
313 321 logger.info("[上级点播] {}->监听端口: {}", ssrc, localPort);
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java
1 1 package com.genersoft.iot.vmp.service.bean;
2 2  
3   -import java.util.stream.Stream;
4   -
5 3 /**
6 4 * 当上级平台
7 5 * @author lin
... ... @@ -29,11 +27,16 @@ public class MessageForPushChannel {
29 27 private String gbId;
30 28  
31 29 /**
32   - * 请求的平台ID
  30 + * 请求的平台国标编号
33 31 */
34 32 private String platFormId;
35 33  
36 34 /**
  35 + * 请求的平台自增ID
  36 + */
  37 + private int platFormIndex;
  38 +
  39 + /**
37 40 * 请求平台名称
38 41 */
39 42 private String platFormName;
... ... @@ -128,4 +131,12 @@ public class MessageForPushChannel {
128 131 public void setMediaServerId(String mediaServerId) {
129 132 this.mediaServerId = mediaServerId;
130 133 }
  134 +
  135 + public int getPlatFormIndex() {
  136 + return platFormIndex;
  137 + }
  138 +
  139 + public void setPlatFormIndex(int platFormIndex) {
  140 + this.platFormIndex = platFormIndex;
  141 + }
131 142 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
... ... @@ -105,6 +105,7 @@ public class PlatformServiceImpl implements IPlatformService {
105 105 // 行政区划默认去编号的前6位
106 106 parentPlatform.setAdministrativeDivision(parentPlatform.getServerGBId().substring(0,6));
107 107 }
  108 + parentPlatform.setTreeType("CivilCode");
108 109 parentPlatform.setCatalogId(parentPlatform.getDeviceGBId());
109 110 int result = platformMapper.addParentPlatform(parentPlatform);
110 111 // 添加缓存
... ...
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java
... ... @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
13 13 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
14 14 import com.genersoft.iot.vmp.service.IMediaServerService;
15 15 import com.genersoft.iot.vmp.service.bean.*;
  16 +import com.genersoft.iot.vmp.utils.redis.RedisUtil;
16 17 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
17 18 import org.slf4j.Logger;
18 19 import org.slf4j.LoggerFactory;
... ... @@ -26,6 +27,7 @@ import org.springframework.stereotype.Component;
26 27  
27 28 import java.text.ParseException;
28 29 import java.util.HashMap;
  30 +import java.util.List;
29 31 import java.util.Map;
30 32 import java.util.UUID;
31 33 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -127,6 +129,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
127 129 case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
128 130 RequestPushStreamMsg param = JSON.to(RequestPushStreamMsg.class, wvpRedisMsg.getContent());
129 131 requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
  132 +
130 133 break;
131 134 default:
132 135 break;
... ... @@ -311,7 +314,9 @@ public class RedisGbPlayMsgListener implements MessageListener {
311 314 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
312 315 content.getPort(), content.getSsrc(), content.getPlatformId(),
313 316 content.getApp(), content.getStream(), content.getChannelId(),
314   - content.getTcp(), content.getRtcp());
  317 + content.getTcp(), content.getRtcp(), ssrcFromCallback -> {
  318 + return querySendRTPServer(content.getPlatformId(), content.getChannelId(), content.getStream(), null) != null;
  319 + });
315 320  
316 321 WVPResult<ResponseSendItemMsg> result = new WVPResult<>();
317 322 result.setCode(0);
... ... @@ -388,4 +393,31 @@ public class RedisGbPlayMsgListener implements MessageListener {
388 393 });
389 394 redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
390 395 }
  396 +
  397 + private SendRtpItem querySendRTPServer(String platformGbId, String channelId, String streamId, String callId) {
  398 + if (platformGbId == null) {
  399 + platformGbId = "*";
  400 + }
  401 + if (channelId == null) {
  402 + channelId = "*";
  403 + }
  404 + if (streamId == null) {
  405 + streamId = "*";
  406 + }
  407 + if (callId == null) {
  408 + callId = "*";
  409 + }
  410 + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
  411 + + userSetting.getServerId() + "_*_"
  412 + + platformGbId + "_"
  413 + + channelId + "_"
  414 + + streamId + "_"
  415 + + callId;
  416 + List<Object> scan = RedisUtil.scan(redisTemplate, key);
  417 + if (scan.size() > 0) {
  418 + return (SendRtpItem)redisTemplate.opsForValue().get(scan.get(0));
  419 + }else {
  420 + return null;
  421 + }
  422 + }
391 423 }
... ...
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.redisMsg;
  2 +
  3 +import com.alibaba.fastjson2.JSON;
  4 +import com.genersoft.iot.vmp.conf.UserSetting;
  5 +import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
  6 +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
  7 +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
  8 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
  9 +import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
  10 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  11 +import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
  12 +import com.genersoft.iot.vmp.service.IMediaServerService;
  13 +import com.genersoft.iot.vmp.service.IStreamPushService;
  14 +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
  15 +import com.genersoft.iot.vmp.service.bean.MessageForPushChannelResponse;
  16 +import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  17 +import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  18 +import org.slf4j.Logger;
  19 +import org.slf4j.LoggerFactory;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.data.redis.connection.Message;
  22 +import org.springframework.data.redis.connection.MessageListener;
  23 +import org.springframework.stereotype.Component;
  24 +
  25 +import javax.sip.InvalidArgumentException;
  26 +import javax.sip.SipException;
  27 +import java.text.ParseException;
  28 +import java.util.HashMap;
  29 +import java.util.List;
  30 +import java.util.Map;
  31 +import java.util.concurrent.ConcurrentHashMap;
  32 +
  33 +/**
  34 + * 接收redis发送的结束推流请求
  35 + * @author lin
  36 + */
  37 +@Component
  38 +public class RedisPushStreamCloseResponseListener implements MessageListener {
  39 +
  40 + private final static Logger logger = LoggerFactory.getLogger(RedisPushStreamCloseResponseListener.class);
  41 +
  42 + @Autowired
  43 + private IStreamPushService streamPushService;
  44 +
  45 + @Autowired
  46 + private IRedisCatchStorage redisCatchStorage;
  47 +
  48 + @Autowired
  49 + private IVideoManagerStorage storager;
  50 +
  51 + @Autowired
  52 + private ISIPCommanderForPlatform commanderFroPlatform;
  53 +
  54 + @Autowired
  55 + private UserSetting userSetting;
  56 +
  57 + @Autowired
  58 + private IMediaServerService mediaServerService;
  59 +
  60 + @Autowired
  61 + private ZLMRTPServerFactory zlmrtpServerFactory;
  62 +
  63 +
  64 + private Map<String, PushStreamResponseEvent> responseEvents = new ConcurrentHashMap<>();
  65 +
  66 + public interface PushStreamResponseEvent{
  67 + void run(MessageForPushChannelResponse response);
  68 + }
  69 +
  70 + @Override
  71 + public void onMessage(Message message, byte[] bytes) {
  72 + logger.info("[REDIS消息-推流结束]: {}", new String(message.getBody()));
  73 + MessageForPushChannel pushChannel = JSON.parseObject(message.getBody(), MessageForPushChannel.class);
  74 + StreamPushItem push = streamPushService.getPush(pushChannel.getApp(), pushChannel.getStream());
  75 + if (push != null) {
  76 + if (redisCatchStorage.isChannelSendingRTP(push.getGbId())) {
  77 + List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(
  78 + push.getGbId());
  79 + if (sendRtpItems.size() > 0) {
  80 + for (SendRtpItem sendRtpItem : sendRtpItems) {
  81 + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
  82 + // 停止向上级推流
  83 + String streamId = sendRtpItem.getStreamId();
  84 + Map<String, Object> param = new HashMap<>();
  85 + param.put("vhost","__defaultVhost__");
  86 + param.put("app",sendRtpItem.getApp());
  87 + param.put("stream",streamId);
  88 + param.put("ssrc",sendRtpItem.getSsrc());
  89 + logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId);
  90 + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  91 + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId());
  92 + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
  93 +
  94 + try {
  95 + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem);
  96 + } catch (SipException | InvalidArgumentException | ParseException e) {
  97 + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
  98 + }
  99 + if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
  100 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
  101 + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
  102 + sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
  103 + messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
  104 + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
  105 + }
  106 + }
  107 + }
  108 + }
  109 + }
  110 +
  111 + }
  112 +
  113 + public void addEvent(String app, String stream, PushStreamResponseEvent callback) {
  114 + responseEvents.put(app + stream, callback);
  115 + }
  116 +
  117 + public void removeEvent(String app, String stream) {
  118 + responseEvents.remove(app + stream);
  119 + }
  120 +}
... ...
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
... ... @@ -202,5 +202,10 @@ public interface IRedisCatchStorage {
202 202 void removeAllDevice();
203 203  
204 204 void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online);
  205 +
205 206 void sendChannelAddOrDelete(String deviceId, String channelId, boolean add);
  207 +
  208 + void sendPlatformStartPlayMsg(MessageForPushChannel messageForPushChannel);
  209 +
  210 + void sendPlatformStopPlayMsg(MessageForPushChannel messageForPushChannel);
206 211 }
... ...
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
... ... @@ -622,4 +622,18 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
622 622 // 使用 RedisTemplate<Object, Object> 发送字符串消息会导致发送的消息多带了双引号
623 623 stringRedisTemplate.convertAndSend(key, msg.toString());
624 624 }
  625 +
  626 + @Override
  627 + public void sendPlatformStartPlayMsg(MessageForPushChannel msg) {
  628 + String key = VideoManagerConstants.VM_MSG_STREAM_START_PLAY_NOTIFY;
  629 + logger.info("[redis发送通知] 推流被上级平台观看 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId());
  630 + redisTemplate.convertAndSend(key, JSON.toJSON(msg));
  631 + }
  632 +
  633 + @Override
  634 + public void sendPlatformStopPlayMsg(MessageForPushChannel msg) {
  635 + String key = VideoManagerConstants.VM_MSG_STREAM_STOP_PLAY_NOTIFY;
  636 + logger.info("[redis发送通知] 上级平台停止观看 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId());
  637 + redisTemplate.convertAndSend(key, JSON.toJSON(msg));
  638 + }
625 639 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
... ... @@ -97,6 +97,9 @@ public class PtzController {
97 97 cmdCode = 32;
98 98 break;
99 99 case "stop":
  100 + horizonSpeed = 0;
  101 + verticalSpeed = 0;
  102 + zoomSpeed = 0;
100 103 break;
101 104 default:
102 105 break;
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.rtp;
  2 +
  3 +import com.genersoft.iot.vmp.conf.SipConfig;
  4 +import com.genersoft.iot.vmp.conf.UserSetting;
  5 +import com.genersoft.iot.vmp.conf.VersionInfo;
  6 +import com.genersoft.iot.vmp.conf.exception.ControllerException;
  7 +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
  8 +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  9 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  10 +import com.genersoft.iot.vmp.service.*;
  11 +import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  12 +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
  13 +import io.swagger.v3.oas.annotations.Operation;
  14 +import io.swagger.v3.oas.annotations.Parameter;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import org.springframework.beans.factory.annotation.Autowired;
  17 +import org.springframework.beans.factory.annotation.Value;
  18 +import org.springframework.web.bind.annotation.GetMapping;
  19 +import org.springframework.web.bind.annotation.RequestMapping;
  20 +import org.springframework.web.bind.annotation.ResponseBody;
  21 +import org.springframework.web.bind.annotation.RestController;
  22 +
  23 +@SuppressWarnings("rawtypes")
  24 +@Tag(name = "第三方服务对接")
  25 +
  26 +@RestController
  27 +@RequestMapping("/api/rtp")
  28 +public class RtpController {
  29 +
  30 + @Autowired
  31 + private ZlmHttpHookSubscribe zlmHttpHookSubscribe;
  32 +
  33 + @Autowired
  34 + private IMediaServerService mediaServerService;
  35 +
  36 + @Autowired
  37 + private VersionInfo versionInfo;
  38 +
  39 + @Autowired
  40 + private SipConfig sipConfig;
  41 +
  42 + @Autowired
  43 + private UserSetting userSetting;
  44 +
  45 + @Autowired
  46 + private IDeviceService deviceService;
  47 +
  48 + @Autowired
  49 + private IDeviceChannelService channelService;
  50 +
  51 + @Autowired
  52 + private IStreamPushService pushService;
  53 +
  54 +
  55 + @Autowired
  56 + private IStreamProxyService proxyService;
  57 +
  58 +
  59 + @Value("${server.port}")
  60 + private int serverPort;
  61 +
  62 +
  63 + @Autowired
  64 + private IRedisCatchStorage redisCatchStorage;
  65 +
  66 +
  67 + @GetMapping(value = "/receive/open")
  68 + @ResponseBody
  69 + @Operation(summary = "开启收流和获取发流信息")
  70 + @Parameter(name = "isSend", description = "是否发送,false时只开启收流, true同时返回推流信息", required = true)
  71 + @Parameter(name = "callId", description = "整个过程的唯一标识,为了与后续接口关联", required = true)
  72 + @Parameter(name = "ssrc", description = "来源流的SSRC,不传则不校验来源ssrc", required = false)
  73 + @Parameter(name = "stream", description = "形成的流的ID", required = true)
  74 + @Parameter(name = "tcpMode", description = "收流模式, 0为UDP, 1为TCP被动", required = true)
  75 + @Parameter(name = "callBack", description = "回调地址,如果收流超时会通道回调通知,回调为get请求,参数为callId", required = true)
  76 + public SendRtpItem openRtpServer(Boolean isSend, String ssrc, String callId, String stream, Integer tcpMode, String callBack) {
  77 + MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
  78 + if (mediaServerItem == null) {
  79 + throw new ControllerException(ErrorCode.ERROR100.getCode(),"没有可用的MediaServer");
  80 + }
  81 + return null;
  82 + }
  83 +
  84 + @GetMapping(value = "/receive/close")
  85 + @ResponseBody
  86 + @Operation(summary = "关闭收流")
  87 + @Parameter(name = "stream", description = "流的ID", required = true)
  88 + public void closeRtpServer(String stream) {
  89 +
  90 + }
  91 +
  92 + @GetMapping(value = "/send/start")
  93 + @ResponseBody
  94 + @Operation(summary = "发送流")
  95 + @Parameter(name = "ssrc", description = "发送流的SSRC", required = true)
  96 + @Parameter(name = "ip", description = "目标IP", required = true)
  97 + @Parameter(name = "port", description = "目标端口", required = true)
  98 + @Parameter(name = "app", description = "待发送应用名", required = true)
  99 + @Parameter(name = "stream", description = "待发送流Id", required = true)
  100 + @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true)
  101 + @Parameter(name = "onlyAudio", description = "是否只有音频", required = true)
  102 + @Parameter(name = "streamType", description = "流类型,1为es流,2为ps流, 默认es流", required = false)
  103 + public void sendRTP(String ssrc, String ip, Integer port, String app, String stream, String callId, Boolean onlyAudio, Integer streamType) {
  104 +
  105 + }
  106 +
  107 +
  108 +
  109 + @GetMapping(value = "/send/stop")
  110 + @ResponseBody
  111 + @Operation(summary = "关闭发送流")
  112 + @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true)
  113 + public void closeSendRTP(String callId) {
  114 +
  115 + }
  116 +
  117 +}
... ...