Commit c014a90cc6a294dfc2aac740be87e75f44193a29

Authored by 648540858
2 parents 9bd58895 e8b76617

Merge branch 'wvp-28181-2.0' into main-dev

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.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/media/zlm/ZLMHttpHookListener.java
Showing 23 changed files with 497 additions and 237 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
... ... @@ -101,6 +101,21 @@ public class VideoManagerConstants {
101 101 */
102 102 public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED";
103 103  
  104 + /**
  105 + * redis 消息通知上级平台开始观看流
  106 + */
  107 + public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY";
  108 +
  109 + /**
  110 + * redis 消息通知上级平台停止观看流
  111 + */
  112 + public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY";
  113 +
  114 + /**
  115 + * redis 消息接收关闭一个推流
  116 + */
  117 + public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED";
  118 +
104 119  
105 120 /**
106 121 * 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
... ... @@ -284,7 +284,7 @@ public class SIPRequestHeaderPlarformProvider {
284 284 viaHeader.setRPort();
285 285 viaHeaders.add(viaHeader);
286 286 // from
287   - SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(),
  287 + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sendRtpItem.getChannelId(),
288 288 platform.getDeviceIp() + ":" + platform.getDevicePort());
289 289 Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
290 290 FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag());
... ... @@ -297,13 +297,10 @@ public class SIPRequestHeaderPlarformProvider {
297 297 MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
298 298 // ceq
299 299 CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
300   - MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory();
301   - // 设置编码, 防止中文乱码
302   - messageFactory.setDefaultContentEncodingCharset("gb2312");
303 300  
304 301 CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
305 302  
306   - request = (SIPRequest) messageFactory.createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader,
  303 + request = (SIPRequest) SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader,
307 304 toHeader, viaHeaders, maxForwards);
308 305  
309 306 request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
... ... @@ -311,6 +308,7 @@ public class SIPRequestHeaderPlarformProvider {
311 308 String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
312 309 Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory()
313 310 .createSipURI(platform.getDeviceGBId(), sipAddress));
  311 +
314 312 request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
315 313  
316 314 return request;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -378,7 +378,6 @@ public class SIPCommander implements ISIPCommander {
378 378 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
379 379 errorEvent.response(e);
380 380 }), e -> {
381   - // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
382 381 ResponseEvent responseEvent = (ResponseEvent) e.event;
383 382 SIPResponse response = (SIPResponse) responseEvent.getResponse();
384 383 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
... ... @@ -237,6 +237,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
237 237 }else {
238 238 if (channel.getChannelId().length() != 20) {
239 239 catalogXml.append("</Item>\r\n");
  240 + logger.warn("[编号长度异常] {} 长度错误,请使用20位长度的国标编号,当前长度:{}", channel.getChannelId(), channel.getChannelId().length());
  241 + catalogXml.append("</Item>\r\n");
240 242 continue;
241 243 }
242 244 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.JSONObject;
4 4 import com.genersoft.iot.vmp.conf.DynamicTask;
5 5 import com.genersoft.iot.vmp.conf.UserSetting;
  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.dto.HookSubscribeFactory;
14 16 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout;
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.IPlayService;
18 21 import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
19 22 import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
... ... @@ -57,6 +60,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
57 60 private IRedisCatchStorage redisCatchStorage;
58 61  
59 62 @Autowired
  63 + private UserSetting userSetting;
  64 +
  65 + @Autowired
60 66 private IVideoManagerStorage storager;
61 67  
62 68 @Autowired
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
... ... @@ -2,9 +2,11 @@ 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.conf.UserSetting;
5 6 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
6 7 import com.genersoft.iot.vmp.gb28181.bean.*;
7 8 import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
  9 +import com.genersoft.iot.vmp.gb28181.bean.*;
8 10 import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
9 11 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
10 12 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
... ... @@ -18,6 +20,7 @@ import com.genersoft.iot.vmp.service.IDeviceService;
18 20 import com.genersoft.iot.vmp.service.IInviteStreamService;
19 21 import com.genersoft.iot.vmp.service.IMediaServerService;
20 22 import com.genersoft.iot.vmp.service.IPlayService;
  23 +import com.genersoft.iot.vmp.service.*;
21 24 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
22 25 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
23 26 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
... ... @@ -31,11 +34,7 @@ import org.springframework.stereotype.Component;
31 34 import javax.sip.InvalidArgumentException;
32 35 import javax.sip.RequestEvent;
33 36 import javax.sip.SipException;
34   -import javax.sip.address.SipURI;
35 37 import javax.sip.header.CallIdHeader;
36   -import javax.sip.header.FromHeader;
37   -import javax.sip.header.HeaderAddress;
38   -import javax.sip.header.ToHeader;
39 38 import javax.sip.message.Response;
40 39 import java.text.ParseException;
41 40 import java.util.HashMap;
... ... @@ -64,12 +63,18 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
64 63 private IInviteStreamService inviteStreamService;
65 64  
66 65 @Autowired
  66 + private IPlatformService platformService;
  67 +
  68 + @Autowired
67 69 private IDeviceService deviceService;
68 70  
69 71 @Autowired
70 72 private AudioBroadcastManager audioBroadcastManager;
71 73  
72 74 @Autowired
  75 + private IDeviceChannelService channelService;
  76 +
  77 + @Autowired
73 78 private IVideoManagerStorage storager;
74 79  
75 80 @Autowired
... ... @@ -90,6 +95,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
90 95 @Autowired
91 96 private IPlayService playService;
92 97  
  98 + @Autowired
  99 + private UserSetting userSetting;
  100 +
93 101 @Override
94 102 public void afterPropertiesSet() throws Exception {
95 103 // 添加消息处理的订阅
... ... @@ -102,201 +110,107 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
102 110 */
103 111 @Override
104 112 public void process(RequestEvent evt) {
105   -
106   - // TODO 此处需要重构
107   - SIPRequest request =(SIPRequest) evt.getRequest();
  113 + SIPRequest request = (SIPRequest) evt.getRequest();
108 114 try {
109 115 responseAck(request, Response.OK);
110 116 } catch (SipException | InvalidArgumentException | ParseException e) {
111 117 logger.error("[回复BYE信息失败],{}", e.getMessage());
112 118 }
113 119 CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
114   - String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
115   - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
116   - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
117   - logger.info("[收到bye] {}/{}", platformGbId, channelId);
118   - if (sendRtpItem != null){
119   - String streamId = sendRtpItem.getStream();
120   - Map<String, Object> param = new HashMap<>();
121   - param.put("vhost","__defaultVhost__");
122   - param.put("app",sendRtpItem.getApp());
123   - param.put("stream",streamId);
124   - param.put("ssrc",sendRtpItem.getSsrc());
125   - logger.info("[收到bye] 停止向上级推流:{}", streamId);
126   - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
127   - redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
128   - ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
129   - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
130   - int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
131   - if (totalReaderCount <= 0) {
132   - logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
133   - if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
134   - Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
135   - if (device == null) {
136   - logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
137   - }
138   - try {
139   - logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId);
140   - cmder.streamByeCmd(device, channelId, streamId, null);
141   - } catch (InvalidArgumentException | ParseException | SipException |
142   - SsrcTransactionNotFoundException e) {
143   - logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
144   - }
145   - }
146   - if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
147   - MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
148   - sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
149   - sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
150   - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
151   - }
  120 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
  121 +
  122 + if (sendRtpItem != null){
  123 + logger.info("[收到bye] 来自平台{}, 停止通道:{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId());
  124 + String streamId = sendRtpItem.getStream();
  125 + Map<String, Object> param = new HashMap<>();
  126 + param.put("vhost","__defaultVhost__");
  127 + param.put("app",sendRtpItem.getApp());
  128 + param.put("stream",streamId);
  129 + param.put("ssrc",sendRtpItem.getSsrc());
  130 + logger.info("[收到bye] 停止向上级推流:{}", streamId);
  131 + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  132 + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
  133 + callIdHeader.getCallId(), null);
  134 + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
  135 + if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
  136 + ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
  137 + if (platform != null) {
  138 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
  139 + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
  140 + sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
  141 + messageForPushChannel.setPlatFormIndex(platform.getId());
  142 + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
  143 + }else {
  144 + logger.info("[上级平台停止观看] 未找到平台{}的信息,发送redis消息失败", sendRtpItem.getPlatformId());
152 145 }
153 146 }
154   - // 可能是设备主动停止
155   - Device device = storager.queryVideoDeviceByChannelId(platformGbId);
156   - if (device != null) {
157   - storager.stopPlay(device.getDeviceId(), channelId);
158   - SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
159   - if (ssrcTransactionForPlay != null){
160   - if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
161   - // 释放ssrc
162   - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId());
163   - if (mediaServerItem != null) {
164   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc());
165   - }
166   - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
  147 +
  148 + int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
  149 + if (totalReaderCount <= 0) {
  150 + logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
  151 + if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
  152 + Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
  153 + if (device == null) {
  154 + logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
167 155 }
168   - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
169   - inviteStreamService.removeInviteInfo(inviteInfo);
170   - if (inviteInfo != null) {
171   - if (inviteInfo.getStreamInfo() != null) {
172   - mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
173   - }
  156 + try {
  157 + logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
  158 + cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
  159 + } catch (InvalidArgumentException | ParseException | SipException |
  160 + SsrcTransactionNotFoundException e) {
  161 + logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
174 162 }
175 163 }
176   - SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null);
177   - if (ssrcTransactionForPlayBack != null) {
178   - // 释放ssrc
179   - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId());
180   - if (mediaServerItem != null) {
181   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc());
182   - }
183   - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream());
184   - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, device.getDeviceId(), channelId);
  164 + }
  165 + }
185 166  
186   - if (inviteInfo != null) {
187   - inviteStreamService.removeInviteInfo(inviteInfo);
188   - if (inviteInfo.getStreamInfo() != null) {
189   - mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
190   - }
191   - }
192   - }
  167 +
  168 +
  169 + // 可能是设备发送的停止
  170 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
  171 + if (ssrcTransaction == null) {
  172 + logger.info("[收到bye] 但是无法获取推流信息和发流信息,忽略此请求");
  173 + logger.info(request.toString());
  174 + return;
193 175 }
  176 + logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
194 177  
195   - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, request.getCallIdHeader().getCallId(), null);
196   - if (ssrcTransaction != null) {
  178 + Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
  179 + if (device == null) {
  180 + logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
  181 + return;
  182 + }
  183 + DeviceChannel channel = channelService.getOne(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
  184 + if (channel == null) {
  185 + logger.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
  186 + return;
  187 + }
  188 + storager.stopPlay(device.getDeviceId(), channel.getChannelId());
  189 + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId());
  190 + if (inviteInfo != null) {
  191 + inviteStreamService.removeInviteInfo(inviteInfo);
  192 + if (inviteInfo.getStreamInfo() != null) {
  193 + mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStreamInfo().getStream());
  194 + }
  195 + }
197 196 // 释放ssrc
198 197 MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
199 198 if (mediaServerItem != null) {
200 199 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
201 200 }
202   -
203   - switch (ssrcTransaction.getType()) {
204   -// case play:
205   -// break;
206   -// case talk:
207   -// break;
208   -// case playback:
209   -// break;
210   -// case download:
211   -// break;
212   - case BROADCAST:
213   - String channelId1 = ssrcTransaction.getChannelId();
214   -
215   - Device deviceFromTransaction = storager.queryVideoDevice(ssrcTransaction.getDeviceId());
216   - if (deviceFromTransaction == null) {
217   - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(ssrcTransaction.getDeviceId());
218   - if (parentPlatform != null) {
219   - // 来自上级平台的停止对讲
220   - logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channelId1);
221   - // 释放ssrc
222   - streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
223   - if (mediaServerItem != null) {
224   - zlmrtpServerFactory.closeRtpServer(mediaServerItem, ssrcTransaction.getStream());
225   - }
226   - // 查找来源的对讲设备,发送停止
227   - Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
228   - if (sourceDevice != null) {
229   - playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channelId);
230   - }
231   - }
232   - }else {
233   - // 来自设备的停止对讲
234   -
235   - // 如果是来自设备,则听停止推流。 来自上级则停止收流
236   - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channelId1);
237   - if (audioBroadcastCatch != null) {
238   - //
239   - SendRtpItem sendRtpItemForBroadcast = redisCatchStorage.querySendRTPServer(ssrcTransaction.getDeviceId(), channelId1,
240   - audioBroadcastCatch.getStream(), audioBroadcastCatch.getSipTransactionInfo().getCallId());
241   - if (sendRtpItemForBroadcast != null) {
242   - MediaServerItem mediaServerItemForBroadcast = mediaServerService.getOne(sendRtpItem.getMediaServerId());
243   - if (mediaServerItemForBroadcast == null) {
244   - return;
245   - }
246   -
247   - Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), audioBroadcastCatch.getStream());
248   - if (ready) {
249   - Map<String, Object> param = new HashMap<>();
250   - param.put("vhost","__defaultVhost__");
251   - param.put("app",sendRtpItem.getApp());
252   - param.put("stream",audioBroadcastCatch.getStream());
253   - param.put("ssrc",sendRtpItem.getSsrc());
254   - logger.info("[收到bye] 停止推流:{}", audioBroadcastCatch.getStream());
255   - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
256   - redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), null);
257   - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
258   - }
259   - if (audioBroadcastCatch.isFromPlatform()) {
260   - // 上级也正在点播。 向上级回复bye
261   - List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(null, channelId1, null, null);
262   - if (ssrcTransactions.size() > 0) {
263   - for (SsrcTransaction transaction : ssrcTransactions) {
264   - if (transaction.getType().equals(InviteSessionType.BROADCAST)) {
265   - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(transaction.getDeviceId());
266   - if (parentPlatform != null) {
267   - try {
268   - commanderForPlatform.streamByeCmd(parentPlatform, channelId1, transaction.getStream(), transaction.getCallId(), eventResult -> {
269   - streamSession.remove(transaction.getDeviceId(), transaction.getChannelId(), transaction.getStream());
270   - });
271   - audioBroadcastManager.del(transaction.getDeviceId(), channelId1);
272   - } catch (InvalidArgumentException | SipException | ParseException |
273   - SsrcTransactionNotFoundException e) {
274   - logger.error("[命令发送失败] 向{}发送bye失败", transaction.getDeviceId());
275   - }
276   - // 释放ssrc
277   - MediaServerItem mediaServerItemFromTransaction = mediaServerService.getOne(transaction.getMediaServerId());
278   - if (mediaServerItemFromTransaction != null) {
279   - mediaServerService.releaseSsrc(mediaServerItemFromTransaction.getId(), transaction.getSsrc());
280   - }
281   - streamSession.remove(transaction.getDeviceId(), transaction.getChannelId(), transaction.getStream());
282   - }
283   - }
284   - }
285   - }
286   -
287   - }
288   - redisCatchStorage.deleteSendRTPServer(ssrcTransaction.getDeviceId(), channelId1,
289   - audioBroadcastCatch.getStream(), audioBroadcastCatch.getSipTransactionInfo().getCallId());
290   -
291   - }
292   - }
293   - }
294   - audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channelId1);
295   - break;
296   - default:
297   - break;
  201 + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getStream());
  202 + if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) {
  203 + // 查找来源的对讲设备,发送停止
  204 + Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
  205 + if (sourceDevice != null) {
  206 + playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId());
  207 + }
  208 + }
  209 + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId());
  210 + if (audioBroadcastCatch != null) {
  211 + // 来自上级平台的停止对讲
  212 + logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId());
  213 + audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId());
298 214 }
299   -
300   - }
301 215 }
302 216 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -210,16 +210,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
210 210 return;
211 211 } else {
212 212 streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream());
213   - if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) {
214   - logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
215   - try {
216   - responseAck(request, Response.GONE);
217   - } catch (SipException | InvalidArgumentException | ParseException e) {
218   - logger.error("[命令发送失败] invite GONE: {}", e.getMessage());
219   - }
220   - return;
221   - }else {
222   - // TODO 可能漏回复消息
  213 + if (streamPushItem != null) {
  214 + mediaServerItem = mediaServerService.getOne(streamPushItem.getMediaServerId());
  215 + }
  216 + if (mediaServerItem == null) {
  217 + mediaServerItem = mediaServerService.getDefaultMediaServer();
223 218 }
224 219 }
225 220 } else {
... ... @@ -380,7 +375,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
380 375 }
381 376 logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc);
382 377 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
383   - device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp());
  378 + device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> {
  379 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  380 + });
384 381  
385 382 if (tcpActive != null) {
386 383 sendRtpItem.setTcpActive(tcpActive);
... ... @@ -584,14 +581,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
584 581 * 安排推流
585 582 */
586 583 private void pushProxyStream(RequestEvent evt, SIPRequest request, GbStream gbStream, ParentPlatform platform,
587   - CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
588   - int port, Boolean tcpActive, boolean mediaTransmissionTCP,
589   - String channelId, String addressStr, String ssrc, String requesterId) {
590   - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
591   - if (streamReady != null && streamReady) {
592   - // 自平台内容
593   - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
594   - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
  584 + CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
  585 + int port, Boolean tcpActive, boolean mediaTransmissionTCP,
  586 + String channelId, String addressStr, String ssrc, String requesterId) {
  587 + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
  588 + if (streamReady != null && streamReady) {
  589 + // 自平台内容
  590 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
  591 + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{
  592 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  593 + });
595 594  
596 595 if (sendRtpItem == null) {
597 596 logger.warn("服务器端口资源不足");
... ... @@ -631,7 +630,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
631 630 if (streamReady != null && streamReady) {
632 631 // 自平台内容
633 632 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
634   - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
  633 + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{
  634 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  635 + });
635 636  
636 637 if (sendRtpItem == null) {
637 638 logger.warn("服务器端口资源不足");
... ... @@ -747,7 +748,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
747 748 dynamicTask.stop(callIdHeader.getCallId());
748 749 if (serverId.equals(userSetting.getServerId())) {
749 750 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
750   - app, stream, channelId, mediaTransmissionTCP, platform.isRtcp());
  751 + app, stream, channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> {
  752 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  753 + });
751 754  
752 755 if (sendRtpItem == null) {
753 756 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 {
... ... @@ -283,4 +283,4 @@ public class SipUtils {
283 283 }
284 284 return localDateTime.format(DateUtil.formatterISO8601);
285 285 }
286 286 -}
  287 +}
287 288 \ No newline at end of file
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -23,6 +23,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
23 23 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
24 24 import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
25 25 import com.genersoft.iot.vmp.service.*;
  26 +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
26 27 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
27 28 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
28 29 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
... ... @@ -543,6 +544,13 @@ public class ZLMHttpHookListener {
543 544 }
544 545 redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
545 546 sendRtpItem.getCallId(), sendRtpItem.getStream());
  547 + if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
  548 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
  549 + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
  550 + sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
  551 + messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
  552 + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
  553 + }
546 554 }
547 555 }
548 556 }
... ... @@ -595,7 +603,7 @@ public class ZLMHttpHookListener {
595 603 }
596 604 return ret;
597 605 }
598   - // 推流具有主动性,暂时不做处理
  606 + // TODO 推流具有主动性,暂时不做处理
599 607 // StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
600 608 // if (streamPushItem != null) {
601 609 // // TODO 发送停止
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -227,13 +227,14 @@ public class ZLMRTPServerFactory {
227 227 * @param tcp 是否为tcp
228 228 * @return SendRtpItem
229 229 */
230   - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp, boolean rtcp){
  230 + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
  231 + String deviceId, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){
231 232  
232 233 // 默认为随机端口
233 234 int localPort = 0;
234 235 if (userSetting.getGbSendStreamStrict()) {
235 236 if (userSetting.getGbSendStreamStrict()) {
236   - localPort = keepPort(serverItem, ssrc, localPort);
  237 + localPort = keepPort(serverItem, ssrc, localPort, callback);
237 238 if (localPort == 0) {
238 239 return null;
239 240 }
... ... @@ -265,11 +266,12 @@ public class ZLMRTPServerFactory {
265 266 * @param tcp 是否为tcp
266 267 * @return SendRtpItem
267 268 */
268   - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp, boolean rtcp){
  269 + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
  270 + String app, String stream, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){
269 271 // 默认为随机端口
270 272 int localPort = 0;
271 273 if (userSetting.getGbSendStreamStrict()) {
272   - localPort = keepPort(serverItem, ssrc, localPort);
  274 + localPort = keepPort(serverItem, ssrc, localPort, callback);
273 275 if (localPort == 0) {
274 276 return null;
275 277 }
... ... @@ -290,10 +292,14 @@ public class ZLMRTPServerFactory {
290 292 return sendRtpItem;
291 293 }
292 294  
  295 + public interface KeepPortCallback{
  296 + Boolean keep(String ssrc);
  297 + }
  298 +
293 299 /**
294 300 * 保持端口,直到需要需要发流时再释放
295 301 */
296   - public int keepPort(MediaServerItem serverItem, String ssrc, Integer localPort) {
  302 + public int keepPort(MediaServerItem serverItem, String ssrc, int localPort, KeepPortCallback keepPortCallback) {
297 303 Map<String, Object> param = new HashMap<>(3);
298 304 param.put("port", localPort);
299 305 param.put("enable_tcp", 1);
... ... @@ -302,18 +308,20 @@ public class ZLMRTPServerFactory {
302 308 if (jsonObject.getInteger("code") == 0) {
303 309 localPort = jsonObject.getInteger("port");
304 310 HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId());
  311 + // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
305 312 Integer finalLocalPort = localPort;
306 313 hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout,
307 314 (MediaServerItem mediaServerItem, HookParam hookParam)->{
308 315 logger.info("[上级点播] {}->监听端口到期继续保持监听: {}", ssrc, finalLocalPort);
309 316 OnRtpServerTimeoutHookParam rtpServerTimeoutHookParam = (OnRtpServerTimeoutHookParam) hookParam;
310   - if (!ssrc.equals(rtpServerTimeoutHookParam.getSsrc())) {
311   - return;
312   - }
313   - int port = keepPort(serverItem, ssrc, finalLocalPort);
314   - if (port == 0) {
315   - logger.info("[上级点播] {}->监听端口失败,移除监听", ssrc);
316   - hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
  317 + if (ssrc.equals(rtpServerTimeoutHookParam.getSsrc())) {
  318 + if (keepPortCallback.keep(ssrc)) {
  319 + logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc);
  320 + keepPort(serverItem, ssrc, finalLocalPort, keepPortCallback);
  321 + }else {
  322 + logger.info("[上级点播] {}->发送取消,无需继续监听", ssrc);
  323 + releasePort(serverItem, ssrc);
  324 + }
317 325 }
318 326 });
319 327 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/DeviceServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
3   -import com.genersoft.iot.vmp.common.InviteSessionType;
4 3 import com.genersoft.iot.vmp.common.VideoManagerConstants;
5 4 import com.genersoft.iot.vmp.conf.DynamicTask;
6 5 import com.genersoft.iot.vmp.conf.UserSetting;
... ... @@ -415,8 +414,8 @@ public class DeviceServiceImpl implements IDeviceService {
415 414 if (device == null) {
416 415 return null;
417 416 }
418   - if (ObjectUtils.isEmpty(parentId) || parentId.equals(deviceId)) {
419   - parentId = null;
  417 + if (ObjectUtils.isEmpty(parentId) ) {
  418 + parentId = deviceId;
420 419 }
421 420 List<DeviceChannel> rootNodes = deviceChannelMapper.getSubChannelsByDeviceId(deviceId, parentId, onlyCatalog);
422 421 return transportChannelsToTree(rootNodes, "");
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
... ... @@ -362,7 +362,7 @@ public class PlayServiceImpl implements IPlayService {
362 362 null);
363 363 return;
364 364 }
365   - logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
  365 + logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
366 366 device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(),
367 367 device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
368 368 //端口获取失败的ssrcInfo 没有必要发送点播指令
... ... @@ -445,7 +445,7 @@ public class PlayServiceImpl implements IPlayService {
445 445 InviteErrorCode.SUCCESS.getCode(),
446 446 InviteErrorCode.SUCCESS.getMsg(),
447 447 streamInfo);
448   - logger.info("[点播成功] deviceId: {}, channelId: {},码流类型:{}", device.getDeviceId(),
  448 + logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(),
449 449 device.isSwitchPrimarySubStream() ? "辅码流" : "主码流");
450 450 String streamUrl;
451 451 if (mediaServerItemInuse.getRtspPort() != 0) {
... ...
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/dao/DeviceChannelMapper.java
... ... @@ -459,8 +459,8 @@ public interface DeviceChannelMapper {
459 459 "select * " +
460 460 "from wvp_device_channel " +
461 461 "where device_id=#{deviceId}" +
462   - " <if test='parentId != null '> and parent_id = #{parentId} </if>" +
463   - " <if test='parentId == null '> and parent_id is null </if>" +
  462 + " <if test='parentId != null and parentId != deviceId'> and parent_id = #{parentId} </if>" +
  463 + " <if test='parentId == null or parentId == deviceId'> and parent_id is null or parent_id = #{deviceId}</if>" +
464 464 " <if test='onlyCatalog == true '> and parental = 1 </if>" +
465 465 " </script>"})
466 466 List<DeviceChannel> getSubChannelsByDeviceId(String deviceId, String parentId, boolean onlyCatalog);
... ...
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 +}
... ...