Merge branch 'wvp-28181-2.0' into wvp-28181-2.0-multi-network

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
pull/673/head
648540858 2022-11-17 11:31:30 +08:00
commit d340a37a00
33 changed files with 377 additions and 267 deletions

View File

@ -11,7 +11,7 @@
<groupId>com.genersoft</groupId> <groupId>com.genersoft</groupId>
<artifactId>wvp-pro</artifactId> <artifactId>wvp-pro</artifactId>
<version>2.3.2</version> <version>2.6.6</version>
<name>web video platform</name> <name>web video platform</name>
<description>国标28181视频平台</description> <description>国标28181视频平台</description>

View File

@ -281,7 +281,6 @@ CREATE TABLE `media_server` (
`secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`rtpEnable` int NOT NULL, `rtpEnable` int NOT NULL,
`rtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `rtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`sendRtpPortRange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`recordAssistPort` int NOT NULL, `recordAssistPort` int NOT NULL,
`defaultServer` int NOT NULL, `defaultServer` int NOT NULL,
`createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

View File

@ -1,6 +1,9 @@
alter table media_server alter table media_server
drop column streamNoneReaderDelayMS; drop column streamNoneReaderDelayMS;
alter table media_server
drop column sendRtpPortRange;
alter table stream_proxy alter table stream_proxy
add enable_disable_none_reader bit(1) default null; add enable_disable_none_reader bit(1) default null;

View File

@ -1,10 +1,7 @@
package com.genersoft.iot.vmp.conf; package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -101,12 +98,14 @@ public class DynamicTask {
} }
} }
public void stop(String key) { public boolean stop(String key) {
if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) { boolean result = false;
futureMap.get(key).cancel(false); if (futureMap.get(key) != null && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) {
result = futureMap.get(key).cancel(false);
futureMap.remove(key); futureMap.remove(key);
runnableMap.remove(key); runnableMap.remove(key);
} }
return result;
} }
public boolean contains(String key) { public boolean contains(String key) {

View File

@ -2,13 +2,11 @@ package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.gb28181.device.DeviceQuery;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -75,10 +73,6 @@ public class MediaConfig{
@Value("${media.rtp.port-range}") @Value("${media.rtp.port-range}")
private String rtpPortRange; private String rtpPortRange;
@Value("${media.rtp.send-port-range}")
private String sendRtpPortRange;
@Value("${media.record-assist-port:0}") @Value("${media.record-assist-port:0}")
private Integer recordAssistPort = 0; private Integer recordAssistPort = 0;
@ -191,10 +185,6 @@ public class MediaConfig{
return sipDomain; return sipDomain;
} }
public String getSendRtpPortRange() {
return sendRtpPortRange;
}
public MediaServerItem getMediaSerItem(){ public MediaServerItem getMediaSerItem(){
MediaServerItem mediaServerItem = new MediaServerItem(); MediaServerItem mediaServerItem = new MediaServerItem();
mediaServerItem.setId(id); mediaServerItem.setId(id);
@ -214,9 +204,8 @@ public class MediaConfig{
mediaServerItem.setSecret(secret); mediaServerItem.setSecret(secret);
mediaServerItem.setRtpEnable(rtpEnable); mediaServerItem.setRtpEnable(rtpEnable);
mediaServerItem.setRtpPortRange(rtpPortRange); mediaServerItem.setRtpPortRange(rtpPortRange);
mediaServerItem.setSendRtpPortRange(sendRtpPortRange);
mediaServerItem.setRecordAssistPort(recordAssistPort); mediaServerItem.setRecordAssistPort(recordAssistPort);
mediaServerItem.setHookAliveInterval(120); mediaServerItem.setHookAliveInterval(30.00f);
mediaServerItem.setCreateTime(DateUtil.getNow()); mediaServerItem.setCreateTime(DateUtil.getNow());
mediaServerItem.setUpdateTime(DateUtil.getNow()); mediaServerItem.setUpdateTime(DateUtil.getNow());

View File

@ -39,6 +39,8 @@ public class UserSetting {
private Boolean pushAuthority = Boolean.TRUE; private Boolean pushAuthority = Boolean.TRUE;
private Boolean gbSendStreamStrict = Boolean.FALSE;
private String serverId = "000000"; private String serverId = "000000";
private String thirdPartyGBIdReg = "[\\s\\S]*"; private String thirdPartyGBIdReg = "[\\s\\S]*";
@ -176,4 +178,12 @@ public class UserSetting {
public void setPushAuthority(Boolean pushAuthority) { public void setPushAuthority(Boolean pushAuthority) {
this.pushAuthority = pushAuthority; this.pushAuthority = pushAuthority;
} }
public Boolean getGbSendStreamStrict() {
return gbSendStreamStrict;
}
public void setGbSendStreamStrict(Boolean gbSendStreamStrict) {
this.gbSendStreamStrict = gbSendStreamStrict;
}
} }

View File

@ -7,10 +7,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.sip.*; import javax.sip.DialogTerminatedEvent;
import javax.sip.ResponseEvent;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.header.CallIdHeader; import javax.sip.header.CallIdHeader;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.text.ParseException;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -29,6 +31,7 @@ public class SipSubscribe {
private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>(); private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>();
private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>(); private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>();
private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>(); private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>();
// @Scheduled(cron="*/5 * * * * ?") //每五秒执行一次 // @Scheduled(cron="*/5 * * * * ?") //每五秒执行一次

View File

@ -1,18 +1,18 @@
package com.genersoft.iot.vmp.gb28181.event.record; package com.genersoft.iot.vmp.gb28181.event.record;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException; import java.util.HashMap;
import java.util.*; import java.util.Hashtable;
import java.util.Map;
/** /**
* @description: * @description:
* @author: pan * @author: pan
* @data: 2022-02-23 * @data: 2022-02-23
*/ */

View File

@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
@ -13,15 +12,15 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse; import gov.nist.javax.sip.message.SIPResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -31,16 +30,13 @@ import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import javax.sip.*; import javax.sip.InvalidArgumentException;
import javax.sip.address.Address; import javax.sip.ResponseEvent;
import javax.sip.address.SipURI; import javax.sip.SipException;
import javax.sip.header.*; import javax.sip.SipFactory;
import javax.sip.message.Message; import javax.sip.header.CallIdHeader;
import javax.sip.message.Request; import javax.sip.message.Request;
import javax.sip.message.Response;
import java.lang.reflect.Field;
import java.text.ParseException; import java.text.ParseException;
import java.util.HashSet;
/** /**
* @description: * @description:
@ -173,7 +169,7 @@ public class SIPCommander implements ISIPCommander {
public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed, public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
int zoomSpeed) throws InvalidArgumentException, SipException, ParseException { int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed); String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
StringBuffer ptzXml = new StringBuffer(200); StringBuilder ptzXml = new StringBuilder(200);
String charset = device.getCharset(); String charset = device.getCharset();
ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n"); ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
ptzXml.append("<Control>\r\n"); ptzXml.append("<Control>\r\n");

View File

@ -16,15 +16,12 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.message.MessageFactoryImpl; import gov.nist.javax.sip.message.MessageFactoryImpl;
import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -639,7 +636,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem != null) { if (mediaServerItem != null) {
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
zlmrtpServerFactory.closeRTPServer(mediaServerItem, sendRtpItem.getStreamId()); zlmrtpServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId());
} }
SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(parentPlatform, sendRtpItem); SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(parentPlatform, sendRtpItem);
if (byeRequest == null) { if (byeRequest == null) {

View File

@ -10,8 +10,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
@ -33,7 +33,8 @@ import javax.sip.header.FromHeader;
import javax.sip.header.HeaderAddress; import javax.sip.header.HeaderAddress;
import javax.sip.header.ToHeader; import javax.sip.header.ToHeader;
import java.text.ParseException; import java.text.ParseException;
import java.util.*; import java.util.HashMap;
import java.util.Map;
/** /**
* SIP ACK * SIP ACK
@ -62,6 +63,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired @Autowired
private ZLMRTPServerFactory zlmrtpServerFactory; private ZLMRTPServerFactory zlmrtpServerFactory;
@Autowired
private ZlmHttpHookSubscribe hookSubscribe;
@Autowired @Autowired
private IMediaServerService mediaServerService; private IMediaServerService mediaServerService;
@ -130,8 +134,18 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
}); });
}else { }else {
JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); // 如果是非严格模式,需要关闭端口占用
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); JSONObject startSendRtpStreamResult = null;
if (sendRtpItem.getLocalPort() != 0) {
if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) {
startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
}
}else {
startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
}
if (startSendRtpStreamResult != null) {
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
}
} }
} }
private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,

View File

@ -45,6 +45,7 @@ import javax.sip.header.CallIdHeader;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.text.ParseException; import java.text.ParseException;
import java.time.Instant; import java.time.Instant;
import java.util.Random;
import java.util.Vector; import java.util.Vector;
/** /**
@ -157,11 +158,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
StreamProxyItem proxyByAppAndStream =null; StreamProxyItem proxyByAppAndStream =null;
// 不是通道可能是直播流 // 不是通道可能是直播流
if (channel != null && gbStream == null) { if (channel != null && gbStream == null) {
// if (channel.getStatus() == 0) {
// logger.info("通道离线返回400");
// responseAck(request, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline");
// return;
// }
// 通道存在发100TRYING // 通道存在发100TRYING
try { try {
responseAck(request, Response.TRYING); responseAck(request, Response.TRYING);
@ -385,7 +381,12 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
} else { } else {
content.append("t=0 0\r\n"); content.append("t=0 0\r\n");
} }
content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n"); int localPort = sendRtpItem.getLocalPort();
if (localPort == 0) {
// 非严格模式端口不统一, 增加兼容性修改为一个不为0的端口
localPort = new Random().nextInt(65535) + 1;
}
content.append("m=video " + localPort + " RTP/AVP 96\r\n");
content.append("a=sendonly\r\n"); content.append("a=sendonly\r\n");
content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=rtpmap:96 PS/90000\r\n");
content.append("y=" + sendRtpItem.getSsrc() + "\r\n"); content.append("y=" + sendRtpItem.getSsrc() + "\r\n");
@ -476,6 +477,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// 写入redis 超时时回复 // 写入redis 超时时回复
redisCatchStorage.updateSendRTPSever(sendRtpItem); redisCatchStorage.updateSendRTPSever(sendRtpItem);
MediaServerItem finalMediaServerItem = mediaServerItem;
playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> { playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> {
logger.info("[上级点播]超时, 用户:{} 通道:{}", username, channelId); logger.info("[上级点播]超时, 用户:{} 通道:{}", username, channelId);
redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
@ -656,6 +658,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
if (!platform.isStartOfflinePush()) { if (!platform.isStartOfflinePush()) {
// 平台设置中关闭了拉起离线的推流则直接回复 // 平台设置中关闭了拉起离线的推流则直接回复
try { try {
logger.info("[上级点播] 失败推流设备未推流channel: {}, app: {}, stream: {}", gbStream.getGbId(), gbStream.getApp(), gbStream.getStream());
responseAck(request, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing"); responseAck(request, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing");
} catch (SipException | InvalidArgumentException | ParseException e) { } catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage()); logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());

View File

@ -85,7 +85,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword(); String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword();
AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
if (authHead == null && !ObjectUtils.isEmpty(password)) { if (authHead == null && !ObjectUtils.isEmpty(password)) {
logger.info("[注册请求] 未携带授权头 回复401: {}", requestAddress); logger.info("[注册请求] 回复401: {}", requestAddress);
response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request); response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain()); new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);

View File

@ -1,19 +1,19 @@
package com.genersoft.iot.vmp.media.zlm; package com.genersoft.iot.vmp.media.zlm;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.media.zlm.dto.HookType;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.*; import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
import com.genersoft.iot.vmp.service.*; import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@ -24,18 +24,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.SipException; import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* @description: ZLMediaServerhook * @description: ZLMediaServerhook
@ -186,7 +183,7 @@ public class ZLMHttpHookListener {
if (!"rtp".equals(param.getApp())) { if (!"rtp".equals(param.getApp())) {
if (userSetting.getPushAuthority()) { if (userSetting.getPushAuthority()) {
// 推流鉴权 // 推流鉴权
if (param.getParams() == null) { if (param.getParams() == null) {
logger.info("推流鉴权失败: 缺少不要参数sign=md5(user表的pushKey)"); logger.info("推流鉴权失败: 缺少不要参数sign=md5(user表的pushKey)");
ret.put("code", 401); ret.put("code", 401);
@ -571,6 +568,8 @@ public class ZLMHttpHookListener {
public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){ public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
jsonObject.put("ip", request.getRemoteAddr()); jsonObject.put("ip", request.getRemoteAddr());
System.out.println(jsonObject.toJSONString()
);
ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject); ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject);
zlmServerConfig.setIp(request.getRemoteAddr()); zlmServerConfig.setIp(request.getRemoteAddr());
logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId()); logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId());
@ -627,6 +626,32 @@ public class ZLMHttpHookListener {
return ret; return ret;
} }
/**
* rtpServer
*/
@ResponseBody
@PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
public JSONObject onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param){
System.out.println(param);
logger.info("[ZLM HOOK] rtpServer收流超时{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("msg", "success");
taskExecutor.execute(()->{
JSONObject json = (JSONObject) JSON.toJSON(param);
List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
if (subscribes != null && subscribes.size() > 0) {
for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
subscribe.response(null, json);
}
}
});
return ret;
}
private Map<String, String> urlParamToMap(String params) { private Map<String, String> urlParamToMap(String params) {
HashMap<String, String> map = new HashMap<>(); HashMap<String, String> map = new HashMap<>();
if (ObjectUtils.isEmpty(params)) { if (ObjectUtils.isEmpty(params)) {

View File

@ -1,16 +1,15 @@
package com.genersoft.iot.vmp.media.zlm; package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.*; import java.util.*;
@ -25,6 +24,9 @@ public class ZLMRTPServerFactory {
@Autowired @Autowired
private UserSetting userSetting; private UserSetting userSetting;
@Autowired
private ZlmHttpHookSubscribe hookSubscribe;
private int[] portRangeArray = new int[2]; private int[] portRangeArray = new int[2];
public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) { public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) {
@ -142,7 +144,7 @@ public class ZLMRTPServerFactory {
return result; return result;
} }
public boolean closeRTPServer(MediaServerItem serverItem, String streamId) { public boolean closeRtpServer(MediaServerItem serverItem, String streamId) {
boolean result = false; boolean result = false;
if (serverItem !=null){ if (serverItem !=null){
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
@ -162,32 +164,6 @@ public class ZLMRTPServerFactory {
return result; return result;
} }
// private int getPortFromportRange(MediaServerItem mediaServerItem) {
// int currentPort = mediaServerItem.getCurrentPort();
// if (currentPort == 0) {
// String[] portRangeStrArray = mediaServerItem.getSendRtpPortRange().split(",");
// if (portRangeStrArray.length != 2) {
// portRangeArray[0] = 30000;
// portRangeArray[1] = 30500;
// }else {
// portRangeArray[0] = Integer.parseInt(portRangeStrArray[0]);
// portRangeArray[1] = Integer.parseInt(portRangeStrArray[1]);
// }
// }
//
// if (currentPort == 0 || currentPort++ > portRangeArray[1]) {
// currentPort = portRangeArray[0];
// mediaServerItem.setCurrentPort(currentPort);
// return portRangeArray[0];
// } else {
// if (currentPort % 2 == 1) {
// currentPort++;
// }
// currentPort++;
// mediaServerItem.setCurrentPort(currentPort);
// return currentPort;
// }
// }
/** /**
* *
@ -201,21 +177,15 @@ public class ZLMRTPServerFactory {
*/ */
public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){ public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){
// 使用RTPServer 功能找一个可用的端口 // 默认为随机端口
String sendRtpPortRange = serverItem.getSendRtpPortRange(); int localPort = 0;
if (ObjectUtils.isEmpty(sendRtpPortRange)) { if (userSetting.getGbSendStreamStrict()) {
if (userSetting.getGbSendStreamStrict()) {
localPort = keepPort(serverItem, ssrc);
if (localPort == 0) {
return null; return null;
} }
String[] portRangeStrArray = serverItem.getSendRtpPortRange().split(",");
int localPort = -1;
if (portRangeStrArray.length != 2) {
localPort = getFreePort(serverItem, 30000, 30500, null);
}else {
localPort = getFreePort(serverItem, Integer.parseInt(portRangeStrArray[0]), Integer.parseInt(portRangeStrArray[1]), null);
} }
if (localPort == -1) {
logger.error("没有可用的端口");
return null;
} }
SendRtpItem sendRtpItem = new SendRtpItem(); SendRtpItem sendRtpItem = new SendRtpItem();
sendRtpItem.setIp(ip); sendRtpItem.setIp(ip);
@ -243,21 +213,13 @@ public class ZLMRTPServerFactory {
* @return SendRtpItem * @return SendRtpItem
*/ */
public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){ public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){
// 使用RTPServer 功能找一个可用的端口 // 默认为随机端口
String sendRtpPortRange = serverItem.getSendRtpPortRange(); int localPort = 0;
if (ObjectUtils.isEmpty(sendRtpPortRange)) { if (userSetting.getGbSendStreamStrict()) {
localPort = keepPort(serverItem, ssrc);
if (localPort == 0) {
return null; return null;
} }
String[] portRangeStrArray = serverItem.getSendRtpPortRange().split(",");
int localPort = -1;
if (portRangeStrArray.length != 2) {
localPort = getFreePort(serverItem, 30000, 30500, null);
}else {
localPort = getFreePort(serverItem, Integer.parseInt(portRangeStrArray[0]), Integer.parseInt(portRangeStrArray[1]), null);
}
if (localPort == -1) {
logger.error("没有可用的端口");
return null;
} }
SendRtpItem sendRtpItem = new SendRtpItem(); SendRtpItem sendRtpItem = new SendRtpItem();
sendRtpItem.setIp(ip); sendRtpItem.setIp(ip);
@ -274,6 +236,42 @@ public class ZLMRTPServerFactory {
return sendRtpItem; return sendRtpItem;
} }
/**
*
*/
public int keepPort(MediaServerItem serverItem, String ssrc) {
int localPort = 0;
Map<String, Object> param = new HashMap<>(3);
param.put("port", 0);
param.put("enable_tcp", 1);
param.put("stream_id", ssrc);
JSONObject jsonObject = zlmresTfulUtils.openRtpServer(serverItem, param);
if (jsonObject.getInteger("code") == 0) {
localPort = jsonObject.getInteger("port");
HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId());
// 订阅 zlm启动事件, 新的zlm也会从这里进入系统
hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout,
(MediaServerItem mediaServerItem, JSONObject response)->{
logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc);
keepPort(serverItem, ssrc);
});
}
logger.info("[上级点播] {}->监听端口: {}", ssrc, localPort);
return localPort;
}
/**
*
*/
public boolean releasePort(MediaServerItem serverItem, String ssrc) {
logger.info("[上级点播] {}->释放监听端口", ssrc);
boolean closeRTPServerResult = closeRtpServer(serverItem, ssrc);
HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId());
// 订阅 zlm启动事件, 新的zlm也会从这里进入系统
hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
return closeRTPServerResult;
}
/** /**
* zlm RESTFUL API startSendRtp * zlm RESTFUL API startSendRtp
*/ */
@ -294,7 +292,8 @@ public class ZLMRTPServerFactory {
*/ */
public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) { public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) {
JSONObject mediaInfo = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId); JSONObject mediaInfo = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId);
return (mediaInfo.getInteger("code") == 0 return mediaInfo != null && (mediaInfo.getInteger("code") == 0
&& mediaInfo.getJSONArray("data") != null && mediaInfo.getJSONArray("data") != null
&& mediaInfo.getJSONArray("data").size() > 0); && mediaInfo.getJSONArray("data").size() > 0);
} }
@ -333,7 +332,7 @@ public class ZLMRTPServerFactory {
result= true; result= true;
logger.info("[停止RTP推流] 成功"); logger.info("[停止RTP推流] 成功");
} else { } else {
logger.error("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"),jsonObject.toJSONString(param)); logger.error("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject);
} }
return result; return result;
} }

View File

@ -18,7 +18,11 @@ import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.*; import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component @Component
@Order(value=2) @Order(value=2)
@ -73,8 +77,6 @@ public class ZLMRunner implements CommandLineRunner {
} }
}); });
// 获取zlm信息 // 获取zlm信息
logger.info("[zlm] 等待默认zlm中..."); logger.info("[zlm] 等待默认zlm中...");
@ -87,7 +89,7 @@ public class ZLMRunner implements CommandLineRunner {
} }
for (MediaServerItem mediaServerItem : all) { for (MediaServerItem mediaServerItem : all) {
if (startGetMedia == null) { if (startGetMedia == null) {
startGetMedia = new HashMap<>(); startGetMedia = new ConcurrentHashMap<>();
} }
startGetMedia.put(mediaServerItem.getId(), true); startGetMedia.put(mediaServerItem.getId(), true);
connectZlmServer(mediaServerItem); connectZlmServer(mediaServerItem);
@ -95,7 +97,7 @@ public class ZLMRunner implements CommandLineRunner {
} }
String taskKey = "zlm-connect-timeout"; String taskKey = "zlm-connect-timeout";
dynamicTask.startDelay(taskKey, ()->{ dynamicTask.startDelay(taskKey, ()->{
if (startGetMedia != null) { if (startGetMedia != null && startGetMedia.size() > 0) {
Set<String> allZlmId = startGetMedia.keySet(); Set<String> allZlmId = startGetMedia.keySet();
for (String id : allZlmId) { for (String id : allZlmId) {
logger.error("[ {} ]]主动连接失败,不再尝试连接", id); logger.error("[ {} ]]主动连接失败,不再尝试连接", id);

View File

@ -66,7 +66,7 @@ public class ZLMServerConfig {
private String hookAdminParams; private String hookAdminParams;
@JSONField(name = "hook.alive_interval") @JSONField(name = "hook.alive_interval")
private int hookAliveInterval; private Float hookAliveInterval;
@JSONField(name = "hook.enable") @JSONField(name = "hook.enable")
private String hookEnable; private String hookEnable;
@ -798,11 +798,11 @@ public class ZLMServerConfig {
this.shellPhell = shellPhell; this.shellPhell = shellPhell;
} }
public int getHookAliveInterval() { public Float getHookAliveInterval() {
return hookAliveInterval; return hookAliveInterval;
} }
public void setHookAliveInterval(int hookAliveInterval) { public void setHookAliveInterval(Float hookAliveInterval) {
this.hookAliveInterval = hookAliveInterval; this.hookAliveInterval = hookAliveInterval;
} }

View File

@ -24,6 +24,17 @@ public class HookSubscribeFactory {
return hookSubscribe; return hookSubscribe;
} }
public static HookSubscribeForRtpServerTimeout on_rtp_server_timeout(String stream, String ssrc, String mediaServerId) {
HookSubscribeForRtpServerTimeout hookSubscribe = new HookSubscribeForRtpServerTimeout();
JSONObject subscribeKey = new com.alibaba.fastjson2.JSONObject();
subscribeKey.put("stream_id", stream);
subscribeKey.put("ssrc", ssrc);
subscribeKey.put("mediaServerId", mediaServerId);
hookSubscribe.setContent(subscribeKey);
return hookSubscribe;
}
public static HookSubscribeForServerStarted on_server_started() { public static HookSubscribeForServerStarted on_server_started() {
HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted(); HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted();
hookSubscribe.setContent(new JSONObject()); hookSubscribe.setContent(new JSONObject());

View File

@ -0,0 +1,44 @@
package com.genersoft.iot.vmp.media.zlm.dto;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.annotation.JSONField;
import java.time.Instant;
/**
* hook-
* @author lin
*/
public class HookSubscribeForRtpServerTimeout implements IHookSubscribe{
private HookType hookType = HookType.on_rtp_server_timeout;
private JSONObject content;
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private Instant expires;
@Override
public HookType getHookType() {
return hookType;
}
@Override
public JSONObject getContent() {
return content;
}
public void setContent(JSONObject content) {
this.content = content;
}
@Override
public Instant getExpires() {
return expires;
}
@Override
public void setExpires(Instant expires) {
this.expires = expires;
}
}

View File

@ -15,6 +15,7 @@ public class HookSubscribeForStreamChange implements IHookSubscribe{
private JSONObject content; private JSONObject content;
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private Instant expires; private Instant expires;
@Override @Override

View File

@ -19,5 +19,7 @@ public enum HookType {
on_stream_none_reader, on_stream_none_reader,
on_stream_not_found, on_stream_not_found,
on_server_started, on_server_started,
on_rtp_server_timeout,
on_server_keepalive on_server_keepalive
} }

View File

@ -5,7 +5,6 @@ import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig; import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.HashMap; import java.util.HashMap;
@ -55,7 +54,7 @@ public class MediaServerItem{
private String secret; private String secret;
@Schema(description = "keepalive hook触发间隔,单位秒") @Schema(description = "keepalive hook触发间隔,单位秒")
private int hookAliveInterval; private Float hookAliveInterval;
@Schema(description = "是否使用多端口模式") @Schema(description = "是否使用多端口模式")
private boolean rtpEnable; private boolean rtpEnable;
@ -66,9 +65,6 @@ public class MediaServerItem{
@Schema(description = "多端口RTP收流端口范围") @Schema(description = "多端口RTP收流端口范围")
private String rtpPortRange; private String rtpPortRange;
@Schema(description = "RTP发流端口范围")
private String sendRtpPortRange;
@Schema(description = "assist服务端口") @Schema(description = "assist服务端口")
private int recordAssistPort; private int recordAssistPort;
@ -119,7 +115,6 @@ public class MediaServerItem{
hookAliveInterval = zlmServerConfig.getHookAliveInterval(); hookAliveInterval = zlmServerConfig.getHookAliveInterval();
rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口 rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
rtpPortRange = zlmServerConfig.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号 rtpPortRange = zlmServerConfig.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号
sendRtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
recordAssistPort = 0; // 默认关闭 recordAssistPort = 0; // 默认关闭
} }
@ -324,19 +319,11 @@ public class MediaServerItem{
this.lastKeepaliveTime = lastKeepaliveTime; this.lastKeepaliveTime = lastKeepaliveTime;
} }
public String getSendRtpPortRange() { public Float getHookAliveInterval() {
return sendRtpPortRange;
}
public void setSendRtpPortRange(String sendRtpPortRange) {
this.sendRtpPortRange = sendRtpPortRange;
}
public int getHookAliveInterval() {
return hookAliveInterval; return hookAliveInterval;
} }
public void setHookAliveInterval(int hookAliveInterval) { public void setHookAliveInterval(Float hookAliveInterval) {
this.hookAliveInterval = hookAliveInterval; this.hookAliveInterval = hookAliveInterval;
} }
} }

View File

@ -0,0 +1,53 @@
package com.genersoft.iot.vmp.media.zlm.dto.hook;
/**
* zlm hookon_rtp_server_timeout
* @author lin
*/
public class OnRtpServerTimeoutHookParam extends HookParam{
private int local_port;
private String stream_id;
private int tcpMode;
private boolean re_use_port;
private String ssrc;
public int getLocal_port() {
return local_port;
}
public void setLocal_port(int local_port) {
this.local_port = local_port;
}
public String getStream_id() {
return stream_id;
}
public void setStream_id(String stream_id) {
this.stream_id = stream_id;
}
public int getTcpMode() {
return tcpMode;
}
public void setTcpMode(int tcpMode) {
this.tcpMode = tcpMode;
}
public boolean isRe_use_port() {
return re_use_port;
}
public void setRe_use_port(boolean re_use_port) {
this.re_use_port = re_use_port;
}
public String getSsrc() {
return ssrc;
}
public void setSsrc(String ssrc) {
this.ssrc = ssrc;
}
}

View File

@ -11,6 +11,9 @@ import com.github.pagehelper.PageInfo;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/**
* @author lin
*/
public interface IStreamPushService { public interface IStreamPushService {
List<StreamPushItem> handleJSON(String json, MediaServerItem mediaServerItem); List<StreamPushItem> handleJSON(String json, MediaServerItem mediaServerItem);

View File

@ -4,13 +4,12 @@ import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
import com.genersoft.iot.vmp.service.IDeviceChannelService;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.service.IDeviceChannelService;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
@ -24,12 +23,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.support.incrementer.AbstractIdentityColumnMaxValueIncrementer;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.SipException; import javax.sip.SipException;
@ -171,7 +168,7 @@ public class DeviceServiceImpl implements IDeviceService {
redisCatchStorage.updateDevice(device); redisCatchStorage.updateDevice(device);
deviceMapper.update(device); deviceMapper.update(device);
//进行通道离线 //进行通道离线
deviceChannelMapper.offlineByDeviceId(deviceId); // deviceChannelMapper.offlineByDeviceId(deviceId);
// 离线释放所有ssrc // 离线释放所有ssrc
List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(deviceId, null, null, null); List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(deviceId, null, null, null);
if (ssrcTransactions != null && ssrcTransactions.size() > 0) { if (ssrcTransactions != null && ssrcTransactions.size() > 0) {

View File

@ -1,19 +1,32 @@
package com.genersoft.iot.vmp.service.impl; package com.genersoft.iot.vmp.service.impl;
import java.time.LocalDateTime; import com.alibaba.fastjson2.JSON;
import java.util.ArrayList; import com.alibaba.fastjson2.JSONArray;
import java.util.Collections; import com.alibaba.fastjson2.JSONObject;
import java.util.HashMap; import com.genersoft.iot.vmp.common.VideoManagerConstants;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData; import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad; import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -24,28 +37,8 @@ import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import com.alibaba.fastjson2.JSON; import java.time.LocalDateTime;
import com.alibaba.fastjson2.JSONArray; import java.util.*;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/** /**
* *
@ -129,6 +122,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
@Override @Override
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port) { public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port) {
if (mediaServerItem == null || mediaServerItem.getId() == null) { if (mediaServerItem == null || mediaServerItem.getId() == null) {
logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
return null; return null;
} }
// 获取mediaServer可用的ssrc // 获取mediaServer可用的ssrc
@ -174,7 +168,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
if (mediaServerItem == null) { if (mediaServerItem == null) {
return; return;
} }
zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId); zlmrtpServerFactory.closeRtpServer(mediaServerItem, streamId);
releaseSsrc(mediaServerItem.getId(), streamId); releaseSsrc(mediaServerItem.getId(), streamId);
} }
@ -306,7 +300,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
public void add(MediaServerItem mediaServerItem) { public void add(MediaServerItem mediaServerItem) {
mediaServerItem.setCreateTime(DateUtil.getNow()); mediaServerItem.setCreateTime(DateUtil.getNow());
mediaServerItem.setUpdateTime(DateUtil.getNow()); mediaServerItem.setUpdateTime(DateUtil.getNow());
mediaServerItem.setHookAliveInterval(120); mediaServerItem.setHookAliveInterval(30f);
JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem); JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
if (responseJSON != null) { if (responseJSON != null) {
JSONArray data = responseJSON.getJSONArray("data"); JSONArray data = responseJSON.getJSONArray("data");
@ -413,7 +407,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
} }
final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId(); final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId();
dynamicTask.stop(zlmKeepaliveKey); dynamicTask.stop(zlmKeepaliveKey);
dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (serverItem.getHookAliveInterval() + 5) * 1000); dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (Math.getExponent(serverItem.getHookAliveInterval()) + 5) * 1000);
publisher.zlmOnlineEventPublish(serverItem.getId()); publisher.zlmOnlineEventPublish(serverItem.getId());
logger.info("[ZLM] 连接成功 {} - {}:{} ", logger.info("[ZLM] 连接成功 {} - {}:{} ",
zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort()); zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
@ -541,6 +535,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)); param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex)); param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex)); param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex));
param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrex));
if (mediaServerItem.getRecordAssistPort() > 0) { if (mediaServerItem.getRecordAssistPort() > 0) {
param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort())); param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort()));
}else { }else {
@ -551,8 +546,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
// 置0关闭此特性(推流断开会导致立即断开播放器) // 置0关闭此特性(推流断开会导致立即断开播放器)
// 此参数不应大于播放器超时时间 // 此参数不应大于播放器超时时间
// 优化此消息以更快的收到流注销事件 // 优化此消息以更快的收到流注销事件
param.put("general.continue_push_ms", "3000" ); param.put("protocol.continue_push_ms", "3000" );
param.put("general.publishToHls", "0" );
// 最多等待未初始化的Track时间单位毫秒超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流, // 最多等待未初始化的Track时间单位毫秒超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流,
// 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项 // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项
// param.put("general.wait_track_ready_ms", "3000" ); // param.put("general.wait_track_ready_ms", "3000" );
@ -666,7 +660,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
} }
final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerItem.getId(); final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerItem.getId();
dynamicTask.stop(zlmKeepaliveKey); dynamicTask.stop(zlmKeepaliveKey);
dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(mediaServerItem), (mediaServerItem.getHookAliveInterval() + 5) * 1000); dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(mediaServerItem), (mediaServerItem.getHookAliveInterval().intValue() + 5) * 1000);
} }
private MediaServerItem getOneFromDatabase(String mediaServerId) { private MediaServerItem getOneFromDatabase(String mediaServerId) {

View File

@ -181,6 +181,16 @@ public class PlayServiceImpl implements IPlayService {
streamId = String.format("%s_%s", device.getDeviceId(), channelId); streamId = String.format("%s_%s", device.getDeviceId(), channelId);
} }
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false); SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
logger.info(JSONObject.toJSONString(ssrcInfo));
if (ssrcInfo == null) {
WVPResult wvpResult = new WVPResult();
wvpResult.setCode(ErrorCode.ERROR100.getCode());
wvpResult.setMsg("开启收流失败");
msg.setData(wvpResult);
resultHolder.invokeAllResult(msg);
return playResult;
}
play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response) -> { play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response) -> {
if (hookEvent != null) { if (hookEvent != null) {
hookEvent.response(mediaServerItem, response); hookEvent.response(mediaServerItem, response);
@ -217,31 +227,25 @@ public class PlayServiceImpl implements IPlayService {
ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent, ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
InviteTimeOutCallback timeoutCallback) { InviteTimeOutCallback timeoutCallback) {
String streamId = null;
if (mediaServerItem.isRtpEnable()) {
streamId = String.format("%s_%s", device.getDeviceId(), channelId);
}
if (ssrcInfo == null) {
ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
}
logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
// 超时处理 // 超时处理
String timeOutTaskKey = UUID.randomUUID().toString(); String timeOutTaskKey = UUID.randomUUID().toString();
SSRCInfo finalSsrcInfo = ssrcInfo;
dynamicTask.startDelay(timeOutTaskKey, () -> { dynamicTask.startDelay(timeOutTaskKey, () -> {
// 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, finalSsrcInfo.getPort(), finalSsrcInfo.getSsrc()); if (redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId) == null) {
timeoutCallback.run(1, "收流超时"); logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
// 点播超时回复BYE 同时释放ssrc以及此次点播的资源 // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
try { try {
cmder.streamByeCmd(device, channelId, finalSsrcInfo.getStream(), null); cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
} catch (InvalidArgumentException | ParseException | SipException e) { } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
logger.error("[点播超时] 发送BYE失败 {}", e.getMessage()); logger.error("[点播超时] 发送BYE失败 {}", e.getMessage());
} catch (SsrcTransactionNotFoundException e) { } finally {
timeoutCallback.run(0, "点播超时"); timeoutCallback.run(1, "收流超时");
mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
}
} }
}, userSetting.getPlayTimeout()); }, userSetting.getPlayTimeout());
final String ssrc = ssrcInfo.getSsrc(); final String ssrc = ssrcInfo.getSsrc();
@ -264,8 +268,8 @@ public class PlayServiceImpl implements IPlayService {
try { try {
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
System.out.println("停止超时任务: " + timeOutTaskKey);
dynamicTask.stop(timeOutTaskKey); dynamicTask.stop(timeOutTaskKey);
// hook响应 // hook响应
onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId); onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId);
hookEvent.response(mediaServerItemInuse, response); hookEvent.response(mediaServerItemInuse, response);
@ -287,18 +291,18 @@ public class PlayServiceImpl implements IPlayService {
//ssrc规定长度为10字节不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 //ssrc规定长度为10字节不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
// 查询到ssrc不一致且开启了ssrc校验则需要针对处理 // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
if (ssrc.equals(ssrcInResponse)) { if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
return; return;
} }
logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
// ssrc 不可用 // ssrc 不可用
// 释放ssrc // 释放ssrc
mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
event.msg = "下级自定义了ssrc,但是此ssrc不可用"; event.msg = "下级自定义了ssrc,但是此ssrc不可用";
event.statusCode = 400; event.statusCode = 400;
errorEvent.response(event); errorEvent.response(event);
@ -308,7 +312,7 @@ public class PlayServiceImpl implements IPlayService {
// 单端口模式streamId也有变化需要重新设置监听 // 单端口模式streamId也有变化需要重新设置监听
if (!mediaServerItem.isRtpEnable()) { if (!mediaServerItem.isRtpEnable()) {
// 添加订阅 // 添加订阅
HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
subscribe.removeSubscribe(hookSubscribe); subscribe.removeSubscribe(hookSubscribe);
hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase()); hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
@ -320,30 +324,30 @@ public class PlayServiceImpl implements IPlayService {
}); });
} }
// 关闭rtp server // 关闭rtp server
mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
// 重新开启ssrc server // 重新开启ssrc server
mediaServerService.openRTPServer(mediaServerItem, finalSsrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, finalSsrcInfo.getPort()); mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort());
} }
} }
}, (event) -> { }, (event) -> {
dynamicTask.stop(timeOutTaskKey); dynamicTask.stop(timeOutTaskKey);
mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
// 释放ssrc // 释放ssrc
mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
errorEvent.response(event); errorEvent.response(event);
}); });
} catch (InvalidArgumentException | SipException | ParseException e) { } catch (InvalidArgumentException | SipException | ParseException e) {
logger.error("[命令发送失败] 点播消息: {}", e.getMessage()); logger.error("[命令发送失败] 点播消息: {}", e.getMessage());
dynamicTask.stop(timeOutTaskKey); dynamicTask.stop(timeOutTaskKey);
mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
// 释放ssrc // 释放ssrc
mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null)); SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
eventResult.msg = "命令发送失败"; eventResult.msg = "命令发送失败";
errorEvent.response(eventResult); errorEvent.response(eventResult);

View File

@ -28,7 +28,6 @@ public interface MediaServerMapper {
"secret, " + "secret, " +
"rtpEnable, " + "rtpEnable, " +
"rtpPortRange, " + "rtpPortRange, " +
"sendRtpPortRange, " +
"recordAssistPort, " + "recordAssistPort, " +
"defaultServer, " + "defaultServer, " +
"createTime, " + "createTime, " +
@ -52,7 +51,6 @@ public interface MediaServerMapper {
"'${secret}', " + "'${secret}', " +
"${rtpEnable}, " + "${rtpEnable}, " +
"'${rtpPortRange}', " + "'${rtpPortRange}', " +
"'${sendRtpPortRange}', " +
"${recordAssistPort}, " + "${recordAssistPort}, " +
"${defaultServer}, " + "${defaultServer}, " +
"'${createTime}', " + "'${createTime}', " +
@ -77,7 +75,6 @@ public interface MediaServerMapper {
"<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" + "<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" +
"<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" + "<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" +
"<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" + "<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" +
"<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" +
"<if test=\"secret != null\">, secret='${secret}'</if>" + "<if test=\"secret != null\">, secret='${secret}'</if>" +
"<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" + "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
"<if test=\"hookAliveInterval != null\">, hookAliveInterval=${hookAliveInterval}</if>" + "<if test=\"hookAliveInterval != null\">, hookAliveInterval=${hookAliveInterval}</if>" +
@ -101,7 +98,6 @@ public interface MediaServerMapper {
"<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" + "<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" +
"<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" + "<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" +
"<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" + "<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" +
"<if test=\"sendRtpPortRange != null\">, sendRtpPortRange='${sendRtpPortRange}'</if>" +
"<if test=\"secret != null\">, secret='${secret}'</if>" + "<if test=\"secret != null\">, secret='${secret}'</if>" +
"<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" + "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
"<if test=\"hookAliveInterval != null\">, hookAliveInterval=${hookAliveInterval}</if>" + "<if test=\"hookAliveInterval != null\">, hookAliveInterval=${hookAliveInterval}</if>" +

View File

@ -23,10 +23,10 @@ public interface PlatformGbStreamMapper {
@Insert("<script> " + @Insert("<script> " +
"INSERT into platform_gb_stream " + "INSERT into platform_gb_stream " +
"(gbStreamId, platformId, catalogId,status) " + "(gbStreamId, platformId, catalogId) " +
"values " + "values " +
"<foreach collection='streamPushItems' index='index' item='item' separator=','> " + "<foreach collection='streamPushItems' index='index' item='item' separator=','> " +
"(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}'), '${item.status}')" + "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}')" +
"</foreach> " + "</foreach> " +
"</script>") "</script>")
int batchAdd(List<StreamPushItem> streamPushItems); int batchAdd(List<StreamPushItem> streamPushItems);

View File

@ -62,7 +62,9 @@ public class MediaController {
if (callId != null) { if (callId != null) {
// 权限校验 // 权限校验
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream); StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
if (streamAuthorityInfo.getCallId().equals(callId)) { if (streamAuthorityInfo != null
&& streamAuthorityInfo.getCallId() != null
&& streamAuthorityInfo.getCallId().equals(callId)) {
authority = true; authority = true;
}else { }else {
throw new ControllerException(ErrorCode.ERROR400); throw new ControllerException(ErrorCode.ERROR400);

View File

@ -135,14 +135,8 @@ public class ServerController {
MediaServerItem mediaServerItemInDatabase = mediaServerService.getOne(mediaServerItem.getId()); MediaServerItem mediaServerItemInDatabase = mediaServerService.getOne(mediaServerItem.getId());
if (mediaServerItemInDatabase != null) { if (mediaServerItemInDatabase != null) {
if (ObjectUtils.isEmpty(mediaServerItemInDatabase.getSendRtpPortRange()) && ObjectUtils.isEmpty(mediaServerItem.getSendRtpPortRange())) {
mediaServerItem.setSendRtpPortRange("30000,30500");
}
mediaServerService.update(mediaServerItem); mediaServerService.update(mediaServerItem);
} else { } else {
if (ObjectUtils.isEmpty(mediaServerItem.getSendRtpPortRange())) {
mediaServerItem.setSendRtpPortRange("30000,30500");
}
mediaServerService.add(mediaServerItem); mediaServerService.add(mediaServerItem);
} }
} }

View File

@ -192,6 +192,9 @@ user-settings:
stream-on-demand: true stream-on-demand: true
# 推流鉴权, 默认开启 # 推流鉴权, 默认开启
push-authority: true push-authority: true
# 国标级联发流严格模式严格模式会使用与sdp信息中一致的端口发流端口共享media.rtp.port-range这会损失一些性能
# 非严格模式使用随机端口发流,性能更好, 默认关闭
gb-send-stream-strict: false
# 关闭在线文档(生产环境建议关闭) # 关闭在线文档(生产环境建议关闭)
springdoc: springdoc:

View File

@ -89,11 +89,6 @@
- -
<el-input v-model="rtpPortRange2" placeholder="终止" @change="portRangeChange" clearable style="width: 100px" prop="rtpPortRange2" :disabled="mediaServerForm.defaultServer"></el-input> <el-input v-model="rtpPortRange2" placeholder="终止" @change="portRangeChange" clearable style="width: 100px" prop="rtpPortRange2" :disabled="mediaServerForm.defaultServer"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="推流端口" prop="sendRtpPortRange1">
<el-input v-model="sendRtpPortRange1" placeholder="起始" @change="portRangeChange" clearable style="width: 100px" prop="sendRtpPortRange1" :disabled="mediaServerForm.defaultServer"></el-input>
-
<el-input v-model="sendRtpPortRange2" placeholder="终止" @change="portRangeChange" clearable style="width: 100px" prop="sendRtpPortRange2" :disabled="mediaServerForm.defaultServer"></el-input>
</el-form-item>
<el-form-item label="录像管理服务端口" prop="recordAssistPort"> <el-form-item label="录像管理服务端口" prop="recordAssistPort">
<el-input v-model.number="mediaServerForm.recordAssistPort" :disabled="mediaServerForm.defaultServer"> <el-input v-model.number="mediaServerForm.recordAssistPort" :disabled="mediaServerForm.defaultServer">
<!-- <el-button v-if="mediaServerForm.recordAssistPort > 0" slot="append" type="primary" @click="checkRecordServer"></el-button>--> <!-- <el-button v-if="mediaServerForm.recordAssistPort > 0" slot="append" type="primary" @click="checkRecordServer"></el-button>-->
@ -177,15 +172,12 @@ export default {
rtmpSSlPort: "", rtmpSSlPort: "",
rtpEnable: false, rtpEnable: false,
rtpPortRange: "", rtpPortRange: "",
sendRtpPortRange: "",
rtpProxyPort: "", rtpProxyPort: "",
rtspPort: "", rtspPort: "",
rtspSSLPort: "", rtspSSLPort: "",
}, },
rtpPortRange1:30000, rtpPortRange1:30000,
rtpPortRange2:30500, rtpPortRange2:30500,
sendRtpPortRange1:30000,
sendRtpPortRange2:30500,
rules: { rules: {
ip: [{ required: true, validator: isValidIp, message: '请输入有效的IP地址', trigger: 'blur' }], ip: [{ required: true, validator: isValidIp, message: '请输入有效的IP地址', trigger: 'blur' }],
@ -196,8 +188,6 @@ export default {
rtmpSSlPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }], rtmpSSlPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
rtpPortRange1: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }], rtpPortRange1: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
rtpPortRange2: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }], rtpPortRange2: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
sendRtpPortRange1: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
sendRtpPortRange2: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
rtpProxyPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }], rtpProxyPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
rtspPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }], rtspPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
rtspSSLPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }], rtspSSLPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
@ -229,9 +219,6 @@ export default {
this.rtpPortRange2 = rtpPortRange[1] this.rtpPortRange2 = rtpPortRange[1]
} }
} }
let sendRtpPortRange = this.mediaServerForm.sendRtpPortRange.split(",");
this.sendRtpPortRange1 = sendRtpPortRange[0]
this.sendRtpPortRange2 = sendRtpPortRange[1]
} }
}, },
checkServer: function() { checkServer: function() {
@ -251,8 +238,6 @@ export default {
that.mediaServerForm = data.data; that.mediaServerForm = data.data;
that.mediaServerForm.httpPort = httpPort; that.mediaServerForm.httpPort = httpPort;
that.mediaServerForm.autoConfig = true; that.mediaServerForm.autoConfig = true;
that.sendRtpPortRange1 = 30000
that.sendRtpPortRange2 = 30500
that.rtpPortRange1 = 30000 that.rtpPortRange1 = 30000
that.rtpPortRange2 = 30500 that.rtpPortRange2 = 30500
that.serverCheck = 1; that.serverCheck = 1;
@ -336,13 +321,10 @@ export default {
rtmpSSlPort: "", rtmpSSlPort: "",
rtpEnable: false, rtpEnable: false,
rtpPortRange: "", rtpPortRange: "",
sendRtpPortRange: "",
rtpProxyPort: "", rtpProxyPort: "",
rtspPort: "", rtspPort: "",
rtspSSLPort: "", rtspSSLPort: "",
}; };
this.sendRtpPortRange1 = 30000;
this.sendRtpPortRange2 = 30500;
this.rtpPortRange1 = 30500; this.rtpPortRange1 = 30500;
this.rtpPortRange2 = 30500; this.rtpPortRange2 = 30500;
this.listChangeCallback = null this.listChangeCallback = null
@ -367,9 +349,7 @@ export default {
} }
}, },
portRangeChange: function() { portRangeChange: function() {
this.mediaServerForm.sendRtpPortRange = this.sendRtpPortRange1 + "," + this.sendRtpPortRange2
this.mediaServerForm.rtpPortRange = this.rtpPortRange1 + "," + this.rtpPortRange2 this.mediaServerForm.rtpPortRange = this.rtpPortRange1 + "," + this.rtpPortRange2
console.log(this.mediaServerForm.sendRtpPortRange)
console.log(this.mediaServerForm.rtpPortRange) console.log(this.mediaServerForm.rtpPortRange)
} }
}, },