Commit e898c344aaa515b4fe16ae7ce3d979160d1e962b

Authored by 648540858
2 parents 7079c73b 0aada74b

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/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
Showing 27 changed files with 369 additions and 464 deletions
doc/_content/introduction/config.md
... ... @@ -160,4 +160,4 @@ user-settings:
160 160  
161 161  
162 162 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。
163   -接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。
  163 +接下来[部署到服务器](./_content/introduction/deployment.md), 如果你只是本地运行直接在本地运行即可。
... ...
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
1 1 package com.genersoft.iot.vmp.conf;
2 2  
  3 +import org.apache.commons.lang3.ObjectUtils;
3 4 import org.slf4j.Logger;
4 5 import org.slf4j.LoggerFactory;
5 6 import org.springframework.scheduling.annotation.Scheduled;
... ... @@ -45,6 +46,9 @@ public class DynamicTask {
45 46 * @return
46 47 */
47 48 public void startCron(String key, Runnable task, int cycleForCatalog) {
  49 + if(ObjectUtils.isEmpty(key)) {
  50 + return;
  51 + }
48 52 ScheduledFuture<?> future = futureMap.get(key);
49 53 if (future != null) {
50 54 if (future.isCancelled()) {
... ... @@ -73,6 +77,9 @@ public class DynamicTask {
73 77 * @return
74 78 */
75 79 public void startDelay(String key, Runnable task, int delay) {
  80 + if(ObjectUtils.isEmpty(key)) {
  81 + return;
  82 + }
76 83 stop(key);
77 84  
78 85 // 获取执行的时刻
... ... @@ -99,9 +106,12 @@ public class DynamicTask {
99 106 }
100 107  
101 108 public boolean stop(String key) {
  109 + if(ObjectUtils.isEmpty(key)) {
  110 + return false;
  111 + }
102 112 boolean result = false;
103   - if (futureMap.get(key) != null && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) {
104   - result = futureMap.get(key).cancel(false);
  113 + if (!ObjectUtils.isEmpty(futureMap.get(key)) && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) {
  114 + result = futureMap.get(key).cancel(true);
105 115 futureMap.remove(key);
106 116 runnableMap.remove(key);
107 117 }
... ... @@ -109,6 +119,9 @@ public class DynamicTask {
109 119 }
110 120  
111 121 public boolean contains(String key) {
  122 + if(ObjectUtils.isEmpty(key)) {
  123 + return false;
  124 + }
112 125 return futureMap.get(key) != null;
113 126 }
114 127  
... ... @@ -117,6 +130,9 @@ public class DynamicTask {
117 130 }
118 131  
119 132 public Runnable get(String key) {
  133 + if(ObjectUtils.isEmpty(key)) {
  134 + return null;
  135 + }
120 136 return runnableMap.get(key);
121 137 }
122 138  
... ...
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
... ... @@ -43,8 +43,6 @@ public class UserSetting {
43 43  
44 44 private Boolean pushAuthority = Boolean.TRUE;
45 45  
46   - private Boolean gbSendStreamStrict = Boolean.FALSE;
47   -
48 46 private Boolean syncChannelOnDeviceOnline = Boolean.FALSE;
49 47  
50 48 private Boolean sipLog = Boolean.FALSE;
... ... @@ -208,14 +206,6 @@ public class UserSetting {
208 206 this.pushAuthority = pushAuthority;
209 207 }
210 208  
211   - public Boolean getGbSendStreamStrict() {
212   - return gbSendStreamStrict;
213   - }
214   -
215   - public void setGbSendStreamStrict(Boolean gbSendStreamStrict) {
216   - this.gbSendStreamStrict = gbSendStreamStrict;
217   - }
218   -
219 209 public Boolean getSyncChannelOnDeviceOnline() {
220 210 return syncChannelOnDeviceOnline;
221 211 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
... ... @@ -165,7 +165,7 @@ public class Device {
165 165 * 是否开启ssrc校验,默认关闭,开启可以防止串流
166 166 */
167 167 @Schema(description = "是否开启ssrc校验,默认关闭,开启可以防止串流")
168   - private boolean ssrcCheck = true;
  168 + private boolean ssrcCheck = false;
169 169  
170 170 /**
171 171 * 地理坐标系, 目前支持 WGS84,GCJ02
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
... ... @@ -333,7 +333,7 @@ public interface ISIPCommander {
333 333 * @param endTime 报警发生终止时间(可选)
334 334 * @return true = 命令发送成功
335 335 */
336   - void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException;
  336 + void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException;
337 337  
338 338 /**
339 339 * 订阅、取消订阅目录信息
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
... ... @@ -167,6 +167,7 @@ public class SIPRequestHeaderPlarformProvider {
167 167  
168 168 public Request createMessageRequest(ParentPlatform parentPlatform, String content, SendRtpItem sendRtpItem) throws PeerUnavailableException, ParseException, InvalidArgumentException {
169 169 CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId());
  170 + callIdHeader.setCallId(sendRtpItem.getCallId());
170 171 return createMessageRequest(parentPlatform, content, sendRtpItem.getToTag(), SipUtils.getNewViaTag(), sendRtpItem.getFromTag(), callIdHeader);
171 172 }
172 173  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -1275,7 +1275,7 @@ public class SIPCommander implements ISIPCommander {
1275 1275 * @return true = 命令发送成功
1276 1276 */
1277 1277 @Override
1278   - public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
  1278 + public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
1279 1279  
1280 1280 StringBuffer cmdXml = new StringBuffer(200);
1281 1281 String charset = device.getCharset();
... ... @@ -1293,9 +1293,6 @@ public class SIPCommander implements ISIPCommander {
1293 1293 if (!ObjectUtils.isEmpty(alarmMethod)) {
1294 1294 cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
1295 1295 }
1296   - if (!ObjectUtils.isEmpty(alarmType)) {
1297   - cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
1298   - }
1299 1296 if (!ObjectUtils.isEmpty(startTime)) {
1300 1297 cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
1301 1298 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
... ... @@ -98,15 +98,20 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
98 98 logger.warn("[收到ACK]:未找到来自{},目标为({})的推流信息",fromUserId, toUserId);
99 99 return;
100 100 }
101   - // tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤
102   - if (sendRtpItem.isTcpActive()) {
103   - return;
104   - }
105   - logger.info("[收到ACK]:rtp/{}开始级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStream(),
106   - sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp());
107   - // 取消设置的超时任务
108   - dynamicTask.stop(callIdHeader.getCallId());
  101 + // tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤
  102 + if (sendRtpItem.isTcpActive()) {
  103 + logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStreamId());
  104 + return;
  105 + }
  106 + String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
109 107 MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  108 + logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}",
  109 + sendRtpItem.getStream(),
  110 + sendRtpItem.getIp(),
  111 + sendRtpItem.getPort(),
  112 + sendRtpItem.getSsrc(),
  113 + sendRtpItem.isTcp()?(sendRtpItem.isTcpActive()?"TCP主动":"TCP被动"):"UDP"
  114 + );
110 115 ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(fromUserId);
111 116  
112 117 if (parentPlatform != null) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
... ... @@ -148,7 +148,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
148 148 logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
149 149 }
150 150 try {
151   - logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
  151 + logger.info("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
152 152 cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
153 153 } catch (InvalidArgumentException | ParseException | SipException |
154 154 SsrcTransactionNotFoundException e) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -469,8 +469,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
469 469 sendRtpItem.setApp("rtp");
470 470 if ("Playback".equalsIgnoreCase(sessionName)) {
471 471 sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
472   - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
473   - sendRtpItem.setStream(ssrcInfo.getStream());
  472 + String startTimeStr = DateUtil.urlFormatter.format(start);
  473 + String endTimeStr = DateUtil.urlFormatter.format(end);
  474 + String stream = device.getDeviceId() + "_" + channelId + "_" + startTimeStr + "_" + endTimeStr;
  475 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
474 476 // 写入redis, 超时时回复
475 477 redisCatchStorage.updateSendRTPSever(sendRtpItem);
476 478 playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
... ... @@ -530,12 +532,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
530 532 }
531 533 }));
532 534 sendRtpItem.setPlayType(InviteStreamType.PLAY);
533   - String streamId = null;
534   - if (mediaServerItem.isRtpEnable()) {
535   - streamId = String.format("%s_%s", device.getDeviceId(), channelId);
536   - }else {
537   - streamId = String.format("%08x", Integer.parseInt(ssrcInfo.getSsrc())).toUpperCase();
538   - }
  535 + String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
539 536 sendRtpItem.setStream(streamId);
540 537 sendRtpItem.setSsrc(ssrcInfo.getSsrc());
541 538 redisCatchStorage.updateSendRTPSever(sendRtpItem);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
... ... @@ -33,7 +33,7 @@ import java.text.ParseException;
33 33 public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
34 34  
35 35  
36   - private Logger logger = LoggerFactory.getLogger(KeepaliveNotifyMessageHandler.class);
  36 + private final Logger logger = LoggerFactory.getLogger(KeepaliveNotifyMessageHandler.class);
37 37 private final static String cmdType = "Keepalive";
38 38  
39 39 @Autowired
... ... @@ -59,14 +59,19 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
59 59 // 未注册的设备不做处理
60 60 return;
61 61 }
62   - logger.info("[收到心跳], device: {}", device.getDeviceId());
63 62 SIPRequest request = (SIPRequest) evt.getRequest();
  63 + logger.info("[收到心跳], device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId());
  64 +
64 65 // 回复200 OK
65 66 try {
66 67 responseAck(request, Response.OK);
67 68 } catch (SipException | InvalidArgumentException | ParseException e) {
68 69 logger.error("[命令发送失败] 心跳回复: {}", e.getMessage());
69 70 }
  71 + if (DateUtil.getDifferenceForNow(device.getKeepaliveTime()) <= 3000L){
  72 + logger.info("[收到心跳] 心跳发送过于频繁,已忽略 device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId());
  73 + return;
  74 + }
70 75  
71 76 RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
72 77 if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
... ... @@ -80,7 +85,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
80 85 }else {
81 86 long lastTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(device.getKeepaliveTime());
82 87 if (System.currentTimeMillis()/1000-lastTime > 10) {
83   - device.setKeepaliveIntervalTime(new Long(System.currentTimeMillis()/1000-lastTime).intValue());
  88 + device.setKeepaliveIntervalTime(Long.valueOf(System.currentTimeMillis()/1000-lastTime).intValue());
84 89 }
85 90 }
86 91  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
... ... @@ -281,6 +281,6 @@ public class SipUtils {
281 281 return null;
282 282 }
283 283 }
284   - return localDateTime.format(DateUtil.formatterISO8601);
  284 + return localDateTime.format(DateUtil.formatter);
285 285 }
286 286 }
287 287 \ No newline at end of file
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -200,7 +200,10 @@ public class ZLMHttpHookListener {
200 200  
201 201 String mediaServerId = json.getString("mediaServerId");
202 202 MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
203   -
  203 + if (mediaInfo == null) {
  204 + return new HookResultForOnPublish(200, "success");
  205 + }
  206 + // 推流鉴权的处理
204 207 if (!"rtp".equals(param.getApp())) {
205 208 if (userSetting.getPushAuthority()) {
206 209 // 推流鉴权
... ... @@ -252,11 +255,21 @@ public class ZLMHttpHookListener {
252 255 }
253 256 });
254 257  
  258 + // 是否录像
255 259 if ("rtp".equals(param.getApp())) {
256 260 result.setEnable_mp4(userSetting.getRecordSip());
257 261 } else {
258 262 result.setEnable_mp4(userSetting.isRecordPushLive());
259 263 }
  264 + // 替换流地址
  265 + if ("rtp".equals(param.getApp()) && !mediaInfo.isRtpEnable()) {
  266 + String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16));;
  267 + InviteInfo inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
  268 + if (inviteInfo != null) {
  269 + result.setStream_replace(inviteInfo.getStream());
  270 + logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream());
  271 + }
  272 + }
260 273 List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
261 274 if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
262 275 String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
... ... @@ -569,6 +582,7 @@ public class ZLMHttpHookListener {
569 582 Device device = deviceService.getDevice(inviteInfo.getDeviceId());
570 583 if (device != null) {
571 584 try {
  585 + // 多查询一次防止已经被处理了
572 586 InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(),
573 587 inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
574 588 if (info != null) {
... ... @@ -643,7 +657,7 @@ public class ZLMHttpHookListener {
643 657  
644 658 if ("rtp".equals(param.getApp())) {
645 659 String[] s = param.getStream().split("_");
646   - if (!mediaInfo.isRtpEnable() || (s.length != 2 && s.length != 4)) {
  660 + if ((s.length != 2 && s.length != 4)) {
647 661 defaultResult.setResult(HookResult.SUCCESS());
648 662 return defaultResult;
649 663 }
... ... @@ -672,7 +686,6 @@ public class ZLMHttpHookListener {
672 686  
673 687 result.onTimeout(() -> {
674 688 logger.info("[ZLM HOOK] 预览流自动点播, 等待超时");
675   - // 释放rtpserver
676 689 msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
677 690 resultHolder.invokeResult(msg);
678 691 });
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java
... ... @@ -168,13 +168,9 @@ public class ZLMServerFactory {
168 168 public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
169 169 String deviceId, String channelId, boolean tcp, boolean rtcp){
170 170  
171   - // 默认为随机端口
172   - int localPort = 0;
173   - if (userSetting.getGbSendStreamStrict()) {
174   - localPort = sendRtpPortManager.getNextPort(serverItem);
175   - if (localPort == 0) {
176   - return null;
177   - }
  171 + int localPort = sendRtpPortManager.getNextPort(serverItem);
  172 + if (localPort == 0) {
  173 + return null;
178 174 }
179 175 SendRtpItem sendRtpItem = new SendRtpItem();
180 176 sendRtpItem.setIp(ip);
... ... @@ -204,13 +200,10 @@ public class ZLMServerFactory {
204 200 */
205 201 public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
206 202 String app, String stream, String channelId, boolean tcp, boolean rtcp){
207   - // 默认为随机端口
208   - int localPort = 0;
209   - if (userSetting.getGbSendStreamStrict()) {
210   - localPort = sendRtpPortManager.getNextPort(serverItem);
211   - if (localPort == 0) {
212   - return null;
213   - }
  203 +
  204 + int localPort = sendRtpPortManager.getNextPort(serverItem);
  205 + if (localPort == 0) {
  206 + return null;
214 207 }
215 208 SendRtpItem sendRtpItem = new SendRtpItem();
216 209 sendRtpItem.setIp(ip);
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
... ... @@ -6,6 +6,7 @@ public class HookResultForOnPublish extends HookResult{
6 6 private boolean enable_mp4;
7 7 private int mp4_max_second;
8 8 private String mp4_save_path;
  9 + private String stream_replace;
9 10  
10 11 public HookResultForOnPublish() {
11 12 }
... ... @@ -51,12 +52,21 @@ public class HookResultForOnPublish extends HookResult{
51 52 this.mp4_save_path = mp4_save_path;
52 53 }
53 54  
  55 + public String getStream_replace() {
  56 + return stream_replace;
  57 + }
  58 +
  59 + public void setStream_replace(String stream_replace) {
  60 + this.stream_replace = stream_replace;
  61 + }
  62 +
54 63 @Override
55 64 public String toString() {
56 65 return "HookResultForOnPublish{" +
57 66 "enable_audio=" + enable_audio +
58 67 ", enable_mp4=" + enable_mp4 +
59 68 ", mp4_max_second=" + mp4_max_second +
  69 + ", stream_replace=" + stream_replace +
60 70 ", mp4_save_path='" + mp4_save_path + '\'' +
61 71 '}';
62 72 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java
... ... @@ -74,5 +74,13 @@ public interface IInviteStreamService {
74 74 int getStreamInfoCount(String mediaServerId);
75 75  
76 76  
  77 + /**
  78 + * 获取MediaServer下的流信息
  79 + */
  80 + InviteInfo getInviteInfoBySSRC(String ssrc);
77 81  
  82 + /**
  83 + * 更新ssrc
  84 + */
  85 + InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrcInResponse);
78 86 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
... ... @@ -190,6 +190,17 @@ public class DeviceServiceImpl implements IDeviceService {
190 190 redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true);
191 191 }
192 192  
  193 +//
  194 +// try {
  195 +// cmder.alarmSubscribe(device, 600, "0", "4", "0", "2023-7-27T00:00:00", "2023-7-28T00:00:00");
  196 +// } catch (InvalidArgumentException e) {
  197 +// throw new RuntimeException(e);
  198 +// } catch (SipException e) {
  199 +// throw new RuntimeException(e);
  200 +// } catch (ParseException e) {
  201 +// throw new RuntimeException(e);
  202 +// }
  203 +
193 204 }
194 205  
195 206 @Override
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java
... ... @@ -77,10 +77,11 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
77 77  
78 78 }
79 79 String key = VideoManagerConstants.INVITE_PREFIX +
80   - "_" + inviteInfoForUpdate.getType() +
81   - "_" + inviteInfoForUpdate.getDeviceId() +
82   - "_" + inviteInfoForUpdate.getChannelId() +
83   - "_" + inviteInfoForUpdate.getStream();
  80 + ":" + inviteInfoForUpdate.getType() +
  81 + ":" + inviteInfoForUpdate.getDeviceId() +
  82 + ":" + inviteInfoForUpdate.getChannelId() +
  83 + ":" + inviteInfoForUpdate.getStream()+
  84 + ":" + inviteInfoForUpdate.getSsrcInfo().getSsrc();
84 85 redisTemplate.opsForValue().set(key, inviteInfoForUpdate);
85 86 }
86 87  
... ... @@ -93,11 +94,15 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
93 94 }
94 95 removeInviteInfo(inviteInfoInDb);
95 96 String key = VideoManagerConstants.INVITE_PREFIX +
96   - "_" + inviteInfo.getType() +
97   - "_" + inviteInfo.getDeviceId() +
98   - "_" + inviteInfo.getChannelId() +
99   - "_" + stream;
  97 + ":" + inviteInfo.getType() +
  98 + ":" + inviteInfo.getDeviceId() +
  99 + ":" + inviteInfo.getChannelId() +
  100 + ":" + stream +
  101 + ":" + inviteInfo.getSsrcInfo().getSsrc();
100 102 inviteInfoInDb.setStream(stream);
  103 + if (inviteInfoInDb.getSsrcInfo() != null) {
  104 + inviteInfoInDb.getSsrcInfo().setStream(stream);
  105 + }
101 106 redisTemplate.opsForValue().set(key, inviteInfoInDb);
102 107 return inviteInfoInDb;
103 108 }
... ... @@ -105,10 +110,11 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
105 110 @Override
106 111 public InviteInfo getInviteInfo(InviteSessionType type, String deviceId, String channelId, String stream) {
107 112 String key = VideoManagerConstants.INVITE_PREFIX +
108   - "_" + (type != null ? type : "*") +
109   - "_" + (deviceId != null ? deviceId : "*") +
110   - "_" + (channelId != null ? channelId : "*") +
111   - "_" + (stream != null ? stream : "*");
  113 + ":" + (type != null ? type : "*") +
  114 + ":" + (deviceId != null ? deviceId : "*") +
  115 + ":" + (channelId != null ? channelId : "*") +
  116 + ":" + (stream != null ? stream : "*")
  117 + + ":*";
112 118 List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
113 119 if (scanResult.size() != 1) {
114 120 return null;
... ... @@ -130,10 +136,11 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
130 136 @Override
131 137 public void removeInviteInfo(InviteSessionType type, String deviceId, String channelId, String stream) {
132 138 String scanKey = VideoManagerConstants.INVITE_PREFIX +
133   - "_" + (type != null ? type : "*") +
134   - "_" + (deviceId != null ? deviceId : "*") +
135   - "_" + (channelId != null ? channelId : "*") +
136   - "_" + (stream != null ? stream : "*");
  139 + ":" + (type != null ? type : "*") +
  140 + ":" + (deviceId != null ? deviceId : "*") +
  141 + ":" + (channelId != null ? channelId : "*") +
  142 + ":" + (stream != null ? stream : "*") +
  143 + ":*";
137 144 List<Object> scanResult = RedisUtil.scan(redisTemplate, scanKey);
138 145 if (scanResult.size() > 0) {
139 146 for (Object keyObj : scanResult) {
... ... @@ -171,10 +178,10 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
171 178 }
172 179  
173 180 private String buildKey(InviteSessionType type, String deviceId, String channelId, String stream) {
174   - String key = type + "_" + deviceId + "_" + channelId;
  181 + String key = type + ":" + deviceId + ":" + channelId;
175 182 // 如果ssrc未null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite
176 183 if (stream != null) {
177   - key += ("_" + stream);
  184 + key += (":" + stream);
178 185 }
179 186 return key;
180 187 }
... ... @@ -188,7 +195,7 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
188 195 @Override
189 196 public int getStreamInfoCount(String mediaServerId) {
190 197 int count = 0;
191   - String key = VideoManagerConstants.INVITE_PREFIX + "_*_*_*_*";
  198 + String key = VideoManagerConstants.INVITE_PREFIX + ":*:*:*:*:*";
192 199 List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
193 200 if (scanResult.size() == 0) {
194 201 return 0;
... ... @@ -219,11 +226,42 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
219 226  
220 227  
221 228 private String buildSubStreamKey(InviteSessionType type, String deviceId, String channelId, String stream) {
222   - String key = type + "_" + "_" + deviceId + "_" + channelId;
  229 + String key = type + ":" + ":" + deviceId + ":" + channelId;
223 230 // 如果ssrc为null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite
224 231 if (stream != null) {
225   - key += ("_" + stream);
  232 + key += (":" + stream);
226 233 }
227 234 return key;
228 235 }
  236 +
  237 + @Override
  238 + public InviteInfo getInviteInfoBySSRC(String ssrc) {
  239 + String key = VideoManagerConstants.INVITE_PREFIX + ":*:*:*:*:" + ssrc;
  240 + List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
  241 + if (scanResult.size() != 1) {
  242 + return null;
  243 + }
  244 +
  245 + return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0));
  246 + }
  247 +
  248 + @Override
  249 + public InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrc) {
  250 + InviteInfo inviteInfoInDb = getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
  251 + if (inviteInfoInDb == null) {
  252 + return null;
  253 + }
  254 + removeInviteInfo(inviteInfoInDb);
  255 + String key = VideoManagerConstants.INVITE_PREFIX +
  256 + ":" + inviteInfo.getType() +
  257 + ":" + inviteInfo.getDeviceId() +
  258 + ":" + inviteInfo.getChannelId() +
  259 + ":" + inviteInfo.getStream() +
  260 + ":" + inviteInfo.getSsrcInfo().getSsrc();
  261 + if (inviteInfoInDb.getSsrcInfo() != null) {
  262 + inviteInfoInDb.getSsrcInfo().setSsrc(ssrc);
  263 + }
  264 + redisTemplate.opsForValue().set(key, inviteInfoInDb);
  265 + return inviteInfoInDb;
  266 + }
229 267 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
... ... @@ -153,9 +153,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
153 153 if (streamId == null) {
154 154 streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
155 155 }
  156 + int ssrcCheckParam = 0;
  157 + if (ssrcCheck && tcpMode > 1) {
  158 + // 目前zlm不支持 tcp模式更新ssrc,暂时关闭ssrc校验
  159 + logger.warn("[openRTPServer] TCP被动/TCP主动收流时,默认关闭ssrc检验");
  160 + }
156 161 int rtpServerPort;
157 162 if (mediaServerItem.isRtpEnable()) {
158   - rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port,onlyAuto, reUsePort, tcpMode);
  163 + rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, (ssrcCheck && tcpMode == 0)?Integer.parseInt(ssrc):0, port, onlyAuto, reUsePort, tcpMode);
159 164 } else {
160 165 rtpServerPort = mediaServerItem.getRtpProxyPort();
161 166 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
... ... @@ -18,6 +18,8 @@ import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
18 18 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
19 19 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
20 20 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  21 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
  22 +import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
21 23 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
22 24 import com.genersoft.iot.vmp.media.zlm.*;
23 25 import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
... ... @@ -43,6 +45,7 @@ import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
43 45 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
44 46 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
45 47 import gov.nist.javax.sip.message.SIPResponse;
  48 +import gov.nist.javax.sip.message.SIPResponse;
46 49 import org.slf4j.Logger;
47 50 import org.slf4j.LoggerFactory;
48 51 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -147,15 +150,21 @@ public class PlayServiceImpl implements IPlayService {
147 150 @Override
148 151 public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
149 152 if (mediaServerItem == null) {
  153 + logger.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", deviceId, channelId);
150 154 throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
151 155 }
152 156  
153 157 Device device = redisCatchStorage.getDevice(deviceId);
  158 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && !mediaServerItem.isRtpEnable()) {
  159 + logger.warn("[点播] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId);
  160 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流");
  161 + }
154 162 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
155 163 if (inviteInfo != null ) {
156 164 if (inviteInfo.getStreamInfo() == null) {
157 165 // 点播发起了但是尚未成功, 仅注册回调等待结果即可
158 166 inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
  167 + logger.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
159 168 return inviteInfo.getSsrcInfo();
160 169 }else {
161 170 StreamInfo streamInfo = inviteInfo.getStreamInfo();
... ... @@ -178,6 +187,7 @@ public class PlayServiceImpl implements IPlayService {
178 187 InviteErrorCode.SUCCESS.getCode(),
179 188 InviteErrorCode.SUCCESS.getMsg(),
180 189 streamInfo);
  190 + logger.info("[点播已存在] 直接返回, deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
181 191 return inviteInfo.getSsrcInfo();
182 192 }else {
183 193 // 点播发起了但是尚未成功, 仅注册回调等待结果即可
... ... @@ -187,11 +197,8 @@ public class PlayServiceImpl implements IPlayService {
187 197 }
188 198 }
189 199 }
190   - String streamId = null;
191   - if (mediaServerItem.isRtpEnable()) {
192   - streamId = String.format("%s_%s", device.getDeviceId(), channelId);
193   - }
194   - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, false,device.getStreamModeForParam());
  200 + String streamId = String.format("%s_%s", device.getDeviceId(), channelId);;
  201 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam());
195 202 if (ssrcInfo == null) {
196 203 callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
197 204 inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
... ... @@ -200,7 +207,6 @@ public class PlayServiceImpl implements IPlayService {
200 207 null);
201 208 return null;
202 209 }
203   - // TODO 记录点播的状态
204 210 play(mediaServerItem, ssrcInfo, device, channelId, callback);
205 211 return ssrcInfo;
206 212 }
... ... @@ -357,8 +363,8 @@ public class PlayServiceImpl implements IPlayService {
357 363 null);
358 364 return;
359 365 }
360   - logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
361   - device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(),
  366 + logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, STREAM:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
  367 + device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(), ssrcInfo.getStream(),
362 368 device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
363 369 //端口获取失败的ssrcInfo 没有必要发送点播指令
364 370 if (ssrcInfo.getPort() <= 0) {
... ... @@ -389,16 +395,6 @@ public class PlayServiceImpl implements IPlayService {
389 395 device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流",
390 396 ssrcInfo.getPort(), ssrcInfo.getSsrc());
391 397  
392   - // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
393   -// InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId);
394   -// if (inviteInfoForTimeout == null) {
395   -// return;
396   -// }
397   -// if (InviteSessionStatus.ok == inviteInfoForTimeout.getStatus() ) {
398   -// // TODO 发送bye
399   -// }else {
400   -// // TODO 发送cancel
401   -// }
402 398 callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
403 399 inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
404 400 InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
... ... @@ -443,128 +439,10 @@ public class PlayServiceImpl implements IPlayService {
443 439 logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(), channelId,
444 440 device.isSwitchPrimarySubStream() ? "辅码流" : "主码流");
445 441 snapOnPlay(mediaServerItemInuse, device.getDeviceId(), channelId, ssrcInfo.getStream());
446   - }, (event) -> {
447   - inviteInfo.setStatus(InviteSessionStatus.ok);
448   -
449   - ResponseEvent responseEvent = (ResponseEvent) event.event;
450   - String contentString = new String(responseEvent.getResponse().getRawContent());
451   - // 获取ssrc
452   - int ssrcIndex = contentString.indexOf("y=");
453   - // 检查是否有y字段
454   - if (ssrcIndex >= 0) {
455   - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
456   - String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim();
457   - // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
458   - if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
459   - if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
460   - String substring = contentString.substring(0, contentString.indexOf("y="));
461   - try {
462   - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
463   - int port = -1;
464   - Vector mediaDescriptions = sdp.getMediaDescriptions(true);
465   - for (Object description : mediaDescriptions) {
466   - MediaDescription mediaDescription = (MediaDescription) description;
467   - Media media = mediaDescription.getMedia();
468   -
469   - Vector mediaFormats = media.getMediaFormats(false);
470   - if (mediaFormats.contains("96")) {
471   - port = media.getMediaPort();
472   - break;
473   - }
474   - }
475   - logger.info("[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
476   - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
477   - logger.info("[点播-TCP主动连接对方] 结果: {}", jsonObject);
478   - } catch (SdpException e) {
479   - logger.error("[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
480   - dynamicTask.stop(timeOutTaskKey);
481   - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
482   - // 释放ssrc
483   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
484   -
485   - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
486   -
487   - callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
488   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
489   - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
490   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
491   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
492   - }
493   - }
494   - return;
495   - }
496   - logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
497   - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
498   - logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
499   - // 释放ssrc
500   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
501   - // 单端口模式streamId也有变化,重新设置监听即可
502   - if (!mediaServerItem.isRtpEnable()) {
503   - // 添加订阅
504   - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
505   - subscribe.removeSubscribe(hookSubscribe);
506   - String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase();
507   - hookSubscribe.getContent().put("stream", stream);
508   - inviteStreamService.updateInviteInfoForStream(inviteInfo, stream);
509   - subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
510   - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
511   - dynamicTask.stop(timeOutTaskKey);
512   - // hook响应
513   - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, hookParam, device.getDeviceId(), channelId);
514   - if (streamInfo == null){
515   - callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
516   - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
517   - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
518   - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
519   - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
520   - return;
521   - }
522   - callback.run(InviteErrorCode.SUCCESS.getCode(),
523   - InviteErrorCode.SUCCESS.getMsg(), streamInfo);
524   - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
525   - InviteErrorCode.SUCCESS.getCode(),
526   - InviteErrorCode.SUCCESS.getMsg(),
527   - streamInfo);
528   - snapOnPlay(mediaServerItemInUse, device.getDeviceId(), channelId, stream);
529   - });
530   - return;
531   - }
532   -
533   - // 更新ssrc
534   - Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
535   - if (!result) {
536   - try {
537   - logger.warn("[点播] 更新ssrc失败,停止点播 {}/{}", device.getDeviceId(), channelId);
538   - cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
539   - } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
540   - logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
541   - }
542   -
543   - dynamicTask.stop(timeOutTaskKey);
544   - // 释放ssrc
545   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
546   -
547   - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
548   -
549   - callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
550   - "下级自定义了ssrc,重新设置收流信息失败", null);
551   - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
552   - InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
553   - "下级自定义了ssrc,重新设置收流信息失败", null);
554   -
555   - }else {
556   - if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) {
557   - inviteStreamService.removeInviteInfo(inviteInfo);
558   - }
559   - ssrcInfo.setSsrc(ssrcInResponse);
560   - inviteInfo.setSsrcInfo(ssrcInfo);
561   - inviteInfo.setStream(ssrcInfo.getStream());
562   - }
563   - }else {
564   - logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
565   - }
566   - }
567   - inviteStreamService.updateInviteInfo(inviteInfo);
  442 + }, (eventResult) -> {
  443 + // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
  444 + InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
  445 + timeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAY);
568 446 }, (event) -> {
569 447 dynamicTask.stop(timeOutTaskKey);
570 448 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
... ... @@ -601,6 +479,47 @@ public class PlayServiceImpl implements IPlayService {
601 479 }
602 480 }
603 481  
  482 + private void tcpActiveHandler(Device device, String channelId, String contentString,
  483 + MediaServerItem mediaServerItem,
  484 + String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback<Object> callback){
  485 + if (!device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
  486 + return;
  487 + }
  488 + String substring = contentString.substring(0, contentString.indexOf("y="));
  489 + try {
  490 + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
  491 + int port = -1;
  492 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  493 + for (Object description : mediaDescriptions) {
  494 + MediaDescription mediaDescription = (MediaDescription) description;
  495 + Media media = mediaDescription.getMedia();
  496 +
  497 + Vector mediaFormats = media.getMediaFormats(false);
  498 + if (mediaFormats.contains("96")) {
  499 + port = media.getMediaPort();
  500 + break;
  501 + }
  502 + }
  503 + logger.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
  504 + JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
  505 + logger.info("[TCP主动连接对方] 结果: {}", jsonObject);
  506 + } catch (SdpException e) {
  507 + logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
  508 + dynamicTask.stop(timeOutTaskKey);
  509 + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  510 + // 释放ssrc
  511 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  512 +
  513 + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  514 +
  515 + callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
  516 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
  517 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  518 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
  519 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
  520 + }
  521 + }
  522 +
604 523 /**
605 524 * 点播成功时调用截图.
606 525 *
... ... @@ -707,22 +626,23 @@ public class PlayServiceImpl implements IPlayService {
707 626 String endTime, ErrorCallback<Object> callback) {
708 627 Device device = storager.queryVideoDevice(deviceId);
709 628 if (device == null) {
710   - return;
  629 + logger.warn("[录像回放] 未找到设备 deviceId: {},channelId:{}", deviceId, channelId);
  630 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId);
711 631 }
  632 +
712 633 MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
713   - String stream = null;
714   - if (newMediaServerItem.isRtpEnable()) {
715   - String startTimeStr = startTime.replace("-", "")
716   - .replace(":", "")
717   - .replace(" ", "");
718   - System.out.println(startTimeStr);
719   - String endTimeTimeStr = endTime.replace("-", "")
720   - .replace(":", "")
721   - .replace(" ", "");
722   - System.out.println(endTimeTimeStr);
723   - stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
724   - }
725   - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam());
  634 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && ! newMediaServerItem.isRtpEnable()) {
  635 + logger.warn("[录像回放] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId);
  636 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流");
  637 + }
  638 + String startTimeStr = startTime.replace("-", "")
  639 + .replace(":", "")
  640 + .replace(" ", "");
  641 + String endTimeTimeStr = endTime.replace("-", "")
  642 + .replace(":", "")
  643 + .replace(" ", "");
  644 + String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
  645 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
726 646 playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
727 647 }
728 648  
... ... @@ -795,113 +715,13 @@ public class PlayServiceImpl implements IPlayService {
795 715 try {
796 716 cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime,
797 717 hookEvent, eventResult -> {
798   - inviteInfo.setStatus(InviteSessionStatus.ok);
799   - ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
800   - String contentString = new String(responseEvent.getResponse().getRawContent());
801   - // 获取ssrc
802   - int ssrcIndex = contentString.indexOf("y=");
803   - // 检查是否有y字段
804   - if (ssrcIndex >= 0) {
805   - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
806   - String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
807   - // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
808   - if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
809   - if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
810   - String substring = contentString.substring(0, contentString.indexOf("y="));
811   - try {
812   - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
813   - int port = -1;
814   - Vector mediaDescriptions = sdp.getMediaDescriptions(true);
815   - for (Object description : mediaDescriptions) {
816   - MediaDescription mediaDescription = (MediaDescription) description;
817   - Media media = mediaDescription.getMedia();
818   -
819   - Vector mediaFormats = media.getMediaFormats(false);
820   - if (mediaFormats.contains("96")) {
821   - port = media.getMediaPort();
822   - break;
823   - }
824   - }
825   - logger.info("[录像回放-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
826   - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
827   - logger.info("[录像回放-TCP主动连接对方] 结果: {}", jsonObject);
828   - } catch (SdpException e) {
829   - logger.error("[录像回放-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
830   - dynamicTask.stop(playBackTimeOutTaskKey);
831   - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
832   - // 释放ssrc
833   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
834   -
835   - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
836   -
837   - callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
838   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
839   - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
840   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
841   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
842   - }
843   - }
844   - return;
845   - }
846   - logger.info("[录像回放] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
847   - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
848   - logger.info("[录像回放] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
849   -
850   - // 释放ssrc
851   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
852   -
853   - // 单端口模式streamId也有变化,需要重新设置监听
854   - if (!mediaServerItem.isRtpEnable()) {
855   - // 添加订阅
856   - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
857   - subscribe.removeSubscribe(hookSubscribe);
858   - String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase();
859   - hookSubscribe.getContent().put("stream", stream);
860   - inviteStreamService.updateInviteInfoForStream(inviteInfo, stream);
861   - subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
862   - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
863   - dynamicTask.stop(playBackTimeOutTaskKey);
864   - // hook响应
865   - hookEvent.response(mediaServerItemInUse, hookParam);
866   - });
867   - }
868   - // 更新ssrc
869   - Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
870   - if (!result) {
871   - try {
872   - logger.warn("[录像回放] 更新ssrc失败,停止录像回放 {}/{}", device.getDeviceId(), channelId);
873   - cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
874   - } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
875   - logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
876   -
877   - }
878   -
879   - dynamicTask.stop(playBackTimeOutTaskKey);
880   - // 释放ssrc
881   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
882   -
883   - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
884   -
885   - callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
886   - "下级自定义了ssrc,重新设置收流信息失败", null);
887   -
888   - }else {
889   - if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) {
890   - inviteStreamService.removeInviteInfo(inviteInfo);
891   - }
892   -
893   - ssrcInfo.setSsrc(ssrcInResponse);
894   - inviteInfo.setSsrcInfo(ssrcInfo);
895   - inviteInfo.setStream(ssrcInfo.getStream());
896   - }
897   - }else {
898   - logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
899   - }
900   - }
901   - inviteStreamService.updateInviteInfo(inviteInfo);
  718 + // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
  719 + InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
  720 + playBackTimeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAYBACK);
  721 +
902 722 }, errorEvent);
903 723 } catch (InvalidArgumentException | SipException | ParseException e) {
904   - logger.error("[命令发送失败] 回放: {}", e.getMessage());
  724 + logger.error("[命令发送失败] 录像回放: {}", e.getMessage());
905 725  
906 726 SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
907 727 eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent;
... ... @@ -912,6 +732,90 @@ public class PlayServiceImpl implements IPlayService {
912 732 }
913 733  
914 734  
  735 + private void InviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, MediaServerItem mediaServerItem,
  736 + Device device, String channelId, String timeOutTaskKey, ErrorCallback<Object> callback,
  737 + InviteInfo inviteInfo, InviteSessionType inviteSessionType){
  738 + inviteInfo.setStatus(InviteSessionStatus.ok);
  739 + ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
  740 + String contentString = new String(responseEvent.getResponse().getRawContent());
  741 + String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
  742 + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  743 + // ssrc 一致
  744 + if (mediaServerItem.isRtpEnable()) {
  745 + // 多端口
  746 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
  747 + tcpActiveHandler(device, channelId, contentString, mediaServerItem, timeOutTaskKey, ssrcInfo, callback);
  748 + }
  749 + }else {
  750 + // 单端口
  751 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
  752 + logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
  753 + }
  754 +
  755 + }
  756 + }else {
  757 + logger.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  758 + // ssrc 不一致
  759 + if (mediaServerItem.isRtpEnable()) {
  760 + // 多端口
  761 + if (device.isSsrcCheck()) {
  762 + // ssrc检验
  763 + // 更新ssrc
  764 + logger.info("[Invite 200OK] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  765 + // 释放ssrc
  766 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  767 + Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
  768 + if (!result) {
  769 + try {
  770 + logger.warn("[Invite 200OK] 更新ssrc失败,停止点播 {}/{}", device.getDeviceId(), channelId);
  771 + cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
  772 + } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
  773 + logger.error("[命令发送失败] 停止播放, 发送BYE: {}", e.getMessage());
  774 + }
  775 +
  776 + dynamicTask.stop(timeOutTaskKey);
  777 + // 释放ssrc
  778 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  779 +
  780 + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  781 +
  782 + callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
  783 + "下级自定义了ssrc,重新设置收流信息失败", null);
  784 + inviteStreamService.call(inviteSessionType, device.getDeviceId(), channelId, null,
  785 + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
  786 + "下级自定义了ssrc,重新设置收流信息失败", null);
  787 +
  788 + }else {
  789 + ssrcInfo.setSsrc(ssrcInResponse);
  790 + inviteInfo.setSsrcInfo(ssrcInfo);
  791 + inviteInfo.setStream(ssrcInfo.getStream());
  792 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
  793 + if (mediaServerItem.isRtpEnable()) {
  794 + tcpActiveHandler(device, channelId, contentString, mediaServerItem, timeOutTaskKey, ssrcInfo, callback);
  795 + }else {
  796 + logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
  797 + }
  798 + }
  799 + inviteStreamService.updateInviteInfo(inviteInfo);
  800 + }
  801 + }
  802 + }else {
  803 + if (ssrcInResponse != null) {
  804 + // 单端口
  805 + // 重新订阅流上线
  806 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(inviteInfo.getDeviceId(),
  807 + inviteInfo.getChannelId(), null, inviteInfo.getStream());
  808 + streamSession.remove(inviteInfo.getDeviceId(),
  809 + inviteInfo.getChannelId(), inviteInfo.getStream());
  810 + inviteStreamService.updateInviteInfoForSSRC(inviteInfo, ssrcInResponse);
  811 + streamSession.put(device.getDeviceId(), channelId, ssrcTransaction.getCallId(),
  812 + inviteInfo.getStream(), ssrcInResponse, mediaServerItem.getId(), (SIPResponse) responseEvent.getResponse(), inviteSessionType);
  813 + }
  814 + }
  815 + }
  816 + }
  817 +
  818 +
915 819 @Override
916 820 public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) {
917 821 Device device = storager.queryVideoDevice(deviceId);
... ... @@ -925,6 +829,7 @@ public class PlayServiceImpl implements IPlayService {
925 829 null);
926 830 return;
927 831 }
  832 + // 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
928 833 SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam());
929 834 download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
930 835 }
... ... @@ -993,108 +898,9 @@ public class PlayServiceImpl implements IPlayService {
993 898 try {
994 899 cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed,
995 900 hookEvent, errorEvent, eventResult ->{
996   - inviteInfo.setStatus(InviteSessionStatus.ok);
997   - ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
998   - String contentString = new String(responseEvent.getResponse().getRawContent());
999   - // 获取ssrc
1000   - int ssrcIndex = contentString.indexOf("y=");
1001   - // 检查是否有y字段
1002   - if (ssrcIndex >= 0) {
1003   - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
1004   - String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
1005   - // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
1006   - if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
1007   - if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
1008   - String substring = contentString.substring(0, contentString.indexOf("y="));
1009   - try {
1010   - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
1011   - int port = -1;
1012   - Vector mediaDescriptions = sdp.getMediaDescriptions(true);
1013   - for (Object description : mediaDescriptions) {
1014   - MediaDescription mediaDescription = (MediaDescription) description;
1015   - Media media = mediaDescription.getMedia();
1016   -
1017   - Vector mediaFormats = media.getMediaFormats(false);
1018   - if (mediaFormats.contains("96")) {
1019   - port = media.getMediaPort();
1020   - break;
1021   - }
1022   - }
1023   - logger.info("[录像下载-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
1024   - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
1025   - logger.info("[录像下载-TCP主动连接对方] 结果: {}", jsonObject);
1026   - } catch (SdpException e) {
1027   - logger.error("[录像下载-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
1028   - dynamicTask.stop(downLoadTimeOutTaskKey);
1029   - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
1030   - // 释放ssrc
1031   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
1032   -
1033   - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
1034   -
1035   - callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
1036   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
1037   - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
1038   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
1039   - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
1040   - }
1041   - }
1042   - return;
1043   - }
1044   - logger.info("[录像下载] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
1045   - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
1046   - logger.info("[录像下载] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
1047   -
1048   - // 释放ssrc
1049   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
1050   -
1051   - // 单端口模式streamId也有变化,需要重新设置监听
1052   - if (!mediaServerItem.isRtpEnable()) {
1053   - // 添加订阅
1054   - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
1055   - subscribe.removeSubscribe(hookSubscribe);
1056   - String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase();
1057   - hookSubscribe.getContent().put("stream", stream);
1058   - inviteStreamService.updateInviteInfoForStream(inviteInfo, stream);
1059   - subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
1060   - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
1061   - dynamicTask.stop(downLoadTimeOutTaskKey);
1062   - hookEvent.response(mediaServerItemInUse, hookParam);
1063   - });
1064   - }
1065   -
1066   - // 更新ssrc
1067   - Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
1068   - if (!result) {
1069   - try {
1070   - logger.warn("[录像下载] 更新ssrc失败,停止录像回放 {}/{}", device.getDeviceId(), channelId);
1071   - cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
1072   - } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
1073   - logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
1074   - }
1075   -
1076   - dynamicTask.stop(downLoadTimeOutTaskKey);
1077   - // 释放ssrc
1078   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
1079   -
1080   - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
1081   -
1082   - callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
1083   - "下级自定义了ssrc,重新设置收流信息失败", null);
1084   -
1085   - }else {
1086   - if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) {
1087   - inviteStreamService.removeInviteInfo(inviteInfo);
1088   - }
1089   - ssrcInfo.setSsrc(ssrcInResponse);
1090   - inviteInfo.setSsrcInfo(ssrcInfo);
1091   - inviteInfo.setStream(ssrcInfo.getStream());
1092   - }
1093   - }else {
1094   - logger.info("[录像下载] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
1095   - }
1096   - }
1097   - inviteStreamService.updateInviteInfo(inviteInfo);
  901 + // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
  902 + InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
  903 + downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD);
1098 904 });
1099 905 } catch (InvalidArgumentException | SipException | ParseException e) {
1100 906 logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
... ...
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
... ... @@ -7,6 +7,7 @@ import java.time.LocalDateTime;
7 7 import java.time.ZoneId;
8 8 import java.time.format.DateTimeFormatter;
9 9 import java.time.format.DateTimeParseException;
  10 +import java.time.temporal.ChronoUnit;
10 11 import java.time.temporal.TemporalAccessor;
11 12  
12 13 import java.util.Locale;
... ... @@ -106,4 +107,9 @@ public class DateUtil {
106 107 LocalDateTime nowDateTime = LocalDateTime.now();
107 108 return formatterISO8601.format(nowDateTime);
108 109 }
  110 +
  111 + public static long getDifferenceForNow(String keepaliveTime) {
  112 + Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime));
  113 + return ChronoUnit.MILLIS.between(beforeInstant, Instant.now());
  114 + }
109 115 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java
... ... @@ -67,7 +67,7 @@ public class MediaController {
67 67 && streamAuthorityInfo.getCallId().equals(callId)) {
68 68 authority = true;
69 69 }else {
70   - throw new ControllerException(ErrorCode.ERROR400);
  70 + throw new ControllerException(ErrorCode.ERROR400.getCode(), "获取播放地址鉴权失败");
71 71 }
72 72 }else {
73 73 // 是否登陆用户, 登陆用户返回完整信息
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
... ... @@ -97,6 +97,7 @@ public class PlayController {
97 97 public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, @PathVariable String deviceId,
98 98 @PathVariable String channelId) {
99 99  
  100 + logger.info("[开始点播] deviceId:{}, channelId:{}, ", deviceId, channelId);
100 101 // 获取可用的zlm
101 102 Device device = storager.queryVideoDevice(deviceId);
102 103 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
... ... @@ -109,13 +110,15 @@ public class PlayController {
109 110 DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
110 111  
111 112 result.onTimeout(()->{
112   - logger.info("点播接口等待超时");
  113 + logger.info("[点播等待超时] deviceId:{}, channelId:{}, ", deviceId, channelId);
113 114 // 释放rtpserver
114 115 WVPResult<StreamInfo> wvpResult = new WVPResult<>();
115 116 wvpResult.setCode(ErrorCode.ERROR100.getCode());
116 117 wvpResult.setMsg("点播超时");
117 118 requestMessage.setData(wvpResult);
118   - resultHolder.invokeResult(requestMessage);
  119 + resultHolder.invokeAllResult(requestMessage);
  120 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
  121 + storager.stopPlay(deviceId, channelId);
119 122 });
120 123  
121 124 // 录像查询以channelId作为deviceId查询
... ... @@ -168,7 +171,7 @@ public class PlayController {
168 171 }
169 172 if (InviteSessionStatus.ok == inviteInfo.getStatus()) {
170 173 try {
171   - logger.warn("[停止点播] {}/{}", device.getDeviceId(), channelId);
  174 + logger.info("[停止点播] {}/{}", device.getDeviceId(), channelId);
172 175 cmder.streamByeCmd(device, channelId, inviteInfo.getStream(), null, null);
173 176 } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
174 177 logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
... ... @@ -338,7 +341,7 @@ public class PlayController {
338 341 message.setKey(key);
339 342 message.setId(uuid);
340 343  
341   - String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + "jpg";
  344 + String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + ".jpg";
342 345 playService.getSnap(deviceId, channelId, fileName, (code, msg, data) -> {
343 346 if (code == InviteErrorCode.SUCCESS.getCode()) {
344 347 message.setData(data);
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java
... ... @@ -42,7 +42,7 @@ public class LogController {
42 42 * @return
43 43 */
44 44 @GetMapping("/all")
45   - @Operation(summary = "分页查询报警")
  45 + @Operation(summary = "分页查询日志")
46 46 @Parameter(name = "query", description = "查询内容", required = true)
47 47 @Parameter(name = "page", description = "当前页", required = true)
48 48 @Parameter(name = "count", description = "每页查询数量", required = true)
... ... @@ -84,7 +84,7 @@ public class LogController {
84 84 * 清空日志
85 85 *
86 86 */
87   - @Operation(summary = "停止视频回放")
  87 + @Operation(summary = "清空日志")
88 88 @DeleteMapping("/clear")
89 89 public void clear() {
90 90 logService.clear();
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
... ... @@ -266,12 +266,4 @@ public class ServerController {
266 266  
267 267 return result;
268 268 }
269   -
270   - @PostMapping(value = "/test/getPort")
271   - @ResponseBody
272   - public int getPort() {
273   - int result = sendRtpPortManager.getNextPort(mediaServerService.getDefaultMediaServer());
274   - System.out.println(result);
275   - return result;
276   - }
277 269 }
... ...
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java
... ... @@ -133,17 +133,29 @@ public class ApiStreamController {
133 133 result.put("ChannelName", deviceChannel.getName());
134 134 result.put("ChannelCustomName", "");
135 135 result.put("FLV", inviteInfo.getStreamInfo().getFlv().getUrl());
136   - result.put("HTTPS_FLV", inviteInfo.getStreamInfo().getHttps_flv().getUrl());
  136 + if(inviteInfo.getStreamInfo().getHttps_flv() != null) {
  137 + result.put("HTTPS_FLV", inviteInfo.getStreamInfo().getHttps_flv().getUrl());
  138 + }
137 139 result.put("WS_FLV", inviteInfo.getStreamInfo().getWs_flv().getUrl());
138   - result.put("WSS_FLV", inviteInfo.getStreamInfo().getWss_flv().getUrl());
  140 + if(inviteInfo.getStreamInfo().getWss_flv() != null) {
  141 + result.put("WSS_FLV", inviteInfo.getStreamInfo().getWss_flv().getUrl());
  142 + }
139 143 result.put("RTMP", inviteInfo.getStreamInfo().getRtmp().getUrl());
140   - result.put("RTMPS", inviteInfo.getStreamInfo().getRtmps().getUrl());
  144 + if (inviteInfo.getStreamInfo().getRtmps() != null) {
  145 + result.put("RTMPS", inviteInfo.getStreamInfo().getRtmps().getUrl());
  146 + }
141 147 result.put("HLS", inviteInfo.getStreamInfo().getHls().getUrl());
142   - result.put("HTTPS_HLS", inviteInfo.getStreamInfo().getHttps_hls().getUrl());
  148 + if (inviteInfo.getStreamInfo().getHttps_hls() != null) {
  149 + result.put("HTTPS_HLS", inviteInfo.getStreamInfo().getHttps_hls().getUrl());
  150 + }
143 151 result.put("RTSP", inviteInfo.getStreamInfo().getRtsp().getUrl());
144   - result.put("RTSPS", inviteInfo.getStreamInfo().getRtsps().getUrl());
  152 + if (inviteInfo.getStreamInfo().getRtsps() != null) {
  153 + result.put("RTSPS", inviteInfo.getStreamInfo().getRtsps().getUrl());
  154 + }
145 155 result.put("WEBRTC", inviteInfo.getStreamInfo().getRtc().getUrl());
146   - result.put("HTTPS_WEBRTC", inviteInfo.getStreamInfo().getRtcs().getUrl());
  156 + if (inviteInfo.getStreamInfo().getRtcs() != null) {
  157 + result.put("HTTPS_WEBRTC", inviteInfo.getStreamInfo().getRtcs().getUrl());
  158 + }
147 159 result.put("CDN", "");
148 160 result.put("SnapURL", "");
149 161 result.put("Transport", device.getTransport());
... ...
src/main/resources/all-application.yml
... ... @@ -187,9 +187,6 @@ user-settings:
187 187 stream-on-demand: true
188 188 # 推流鉴权, 默认开启
189 189 push-authority: true
190   - # 国标级联发流严格模式,严格模式会使用与sdp信息中一致的端口发流,端口共享media.rtp.port-range,这会损失一些性能,
191   - # 非严格模式使用随机端口发流,性能更好, 默认关闭
192   - gb-send-stream-strict: false
193 190 # 设备上线时是否自动同步通道
194 191 sync-channel-on-device-online: false
195 192 # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式
... ...