优化国标级联发流并发能力

pull/673/head
648540858 2022-11-16 09:39:27 +08:00
parent 2466a24860
commit 694076dc8c
26 changed files with 277 additions and 166 deletions

View File

@ -281,7 +281,6 @@ CREATE TABLE `media_server` (
`secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`rtpEnable` int 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,
`defaultServer` int NOT NULL,
`createTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

View File

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

View File

@ -37,6 +37,8 @@ public class UserSetting {
private Boolean pushAuthority = Boolean.TRUE;
private Boolean gbSendStreamStrict = Boolean.FALSE;
private String serverId = "000000";
private String thirdPartyGBIdReg = "[\\s\\S]*";
@ -166,4 +168,12 @@ public class UserSetting {
public void setPushAuthority(Boolean 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.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.message.Response;
import java.text.ParseException;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -29,6 +31,7 @@ public class SipSubscribe {
private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>();
private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>();
private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>();
// @Scheduled(cron="*/5 * * * * ?") //每五秒执行一次

View File

@ -1,18 +1,18 @@
package com.genersoft.iot.vmp.gb28181.event.record;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* @description:
* @description:
* @author: pan
* @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.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.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
@ -12,45 +11,31 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
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.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
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.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.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.GitUtil;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import com.genersoft.iot.vmp.utils.DateUtil;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import gov.nist.javax.sip.stack.SIPClientTransaction;
import gov.nist.javax.sip.stack.SIPClientTransactionImpl;
import gov.nist.javax.sip.stack.SIPDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.Message;
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.HashSet;
/**
* @description:
@ -183,7 +168,7 @@ public class SIPCommander implements ISIPCommander {
public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
StringBuffer ptzXml = new StringBuffer(200);
StringBuilder ptzXml = new StringBuilder(200);
String charset = device.getCharset();
ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
ptzXml.append("<Control>\r\n");

View File

@ -1,7 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@ -15,15 +14,12 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
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.SIPRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
@ -638,7 +634,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem != null) {
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
zlmrtpServerFactory.closeRTPServer(mediaServerItem, sendRtpItem.getStreamId());
zlmrtpServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId());
}
SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
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.event.request.ISIPRequestProcessor;
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.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
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.ToHeader;
import java.text.ParseException;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
/**
* SIP ACK
@ -62,6 +63,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired
private ZLMRTPServerFactory zlmrtpServerFactory;
@Autowired
private ZlmHttpHookSubscribe hookSubscribe;
@Autowired
private IMediaServerService mediaServerService;
@ -130,8 +134,18 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
});
}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,

View File

@ -45,6 +45,7 @@ import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import java.text.ParseException;
import java.time.Instant;
import java.util.Random;
import java.util.Vector;
/**
@ -157,11 +158,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
StreamProxyItem proxyByAppAndStream =null;
// 不是通道可能是直播流
if (channel != null && gbStream == null) {
// if (channel.getStatus() == 0) {
// logger.info("通道离线返回400");
// responseAck(request, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline");
// return;
// }
// 通道存在发100TRYING
try {
responseAck(request, Response.TRYING);
@ -385,7 +381,12 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
} else {
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=rtpmap:96 PS/90000\r\n");
content.append("y=" + sendRtpItem.getSsrc() + "\r\n");
@ -476,9 +477,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// 写入redis 超时时回复
redisCatchStorage.updateSendRTPSever(sendRtpItem);
MediaServerItem finalMediaServerItem = mediaServerItem;
playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> {
logger.info("[上级点播]超时, 用户:{} 通道:{}", username, channelId);
redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
zlmrtpServerFactory.releasePort(finalMediaServerItem, sendRtpItem.getSsrc());
}, null);
} else {
sendRtpItem.setStreamId(playTransaction.getStream());

View File

@ -626,6 +626,32 @@ public class ZLMHttpHookListener {
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) {
HashMap<String, String> map = new HashMap<>();
if (ObjectUtils.isEmpty(params)) {

View File

@ -1,15 +1,15 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
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.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.*;
@ -24,6 +24,9 @@ public class ZLMRTPServerFactory {
@Autowired
private UserSetting userSetting;
@Autowired
private ZlmHttpHookSubscribe hookSubscribe;
private int[] portRangeArray = new int[2];
public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) {
@ -141,7 +144,7 @@ public class ZLMRTPServerFactory {
return result;
}
public boolean closeRTPServer(MediaServerItem serverItem, String streamId) {
public boolean closeRtpServer(MediaServerItem serverItem, String streamId) {
boolean result = false;
if (serverItem !=null){
Map<String, Object> param = new HashMap<>();
@ -161,32 +164,6 @@ public class ZLMRTPServerFactory {
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;
// }
// }
/**
*
@ -200,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){
// 使用RTPServer 功能找一个可用的端口
String sendRtpPortRange = serverItem.getSendRtpPortRange();
if (ObjectUtils.isEmpty(sendRtpPortRange)) {
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;
// 默认为随机端口
int localPort = 0;
if (userSetting.getGbSendStreamStrict()) {
if (userSetting.getGbSendStreamStrict()) {
localPort = keepPort(serverItem, ssrc);
if (localPort == 0) {
return null;
}
}
}
SendRtpItem sendRtpItem = new SendRtpItem();
sendRtpItem.setIp(ip);
@ -242,21 +213,13 @@ public class ZLMRTPServerFactory {
* @return SendRtpItem
*/
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();
if (ObjectUtils.isEmpty(sendRtpPortRange)) {
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;
// 默认为随机端口
int localPort = 0;
if (userSetting.getGbSendStreamStrict()) {
localPort = keepPort(serverItem, ssrc);
if (localPort == 0) {
return null;
}
}
SendRtpItem sendRtpItem = new SendRtpItem();
sendRtpItem.setIp(ip);
@ -273,6 +236,42 @@ public class ZLMRTPServerFactory {
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
*/
@ -333,7 +332,7 @@ public class ZLMRTPServerFactory {
result= true;
logger.info("[停止RTP推流] 成功");
} 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;
}

View File

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

View File

@ -24,6 +24,17 @@ public class HookSubscribeFactory {
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() {
HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted();
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;
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private Instant expires;
@Override

View File

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

View File

@ -65,9 +65,6 @@ public class MediaServerItem{
@Schema(description = "多端口RTP收流端口范围")
private String rtpPortRange;
@Schema(description = "RTP发流端口范围")
private String sendRtpPortRange;
@Schema(description = "assist服务端口")
private int recordAssistPort;
@ -118,7 +115,6 @@ public class MediaServerItem{
hookAliveInterval = zlmServerConfig.getHookAliveInterval();
rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
rtpPortRange = zlmServerConfig.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号
sendRtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
recordAssistPort = 0; // 默认关闭
}
@ -323,14 +319,6 @@ public class MediaServerItem{
this.lastKeepaliveTime = lastKeepaliveTime;
}
public String getSendRtpPortRange() {
return sendRtpPortRange;
}
public void setSendRtpPortRange(String sendRtpPortRange) {
this.sendRtpPortRange = sendRtpPortRange;
}
public Float getHookAliveInterval() {
return 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

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

View File

@ -168,7 +168,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
if (mediaServerItem == null) {
return;
}
zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId);
zlmrtpServerFactory.closeRtpServer(mediaServerItem, streamId);
releaseSsrc(mediaServerItem.getId(), streamId);
}
@ -535,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_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_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrex));
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()));
}else {
@ -545,8 +546,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
// 置0关闭此特性(推流断开会导致立即断开播放器)
// 此参数不应大于播放器超时时间
// 优化此消息以更快的收到流注销事件
param.put("general.continue_push_ms", "3000" );
param.put("general.publishToHls", "0" );
param.put("protocol.continue_push_ms", "3000" );
// 最多等待未初始化的Track时间单位毫秒超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流,
// 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项
// param.put("general.wait_track_ready_ms", "3000" );

View File

@ -283,7 +283,7 @@ public class PlayServiceImpl implements IPlayService {
try {
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString());
System.out.println("停止超时任务: " + timeOutTaskKey);
dynamicTask.stop(timeOutTaskKey);
// hook响应
onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid);

View File

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

View File

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

View File

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

View File

@ -192,6 +192,9 @@ user-settings:
stream-on-demand: true
# 推流鉴权, 默认开启
push-authority: true
# 国标级联发流严格模式严格模式会使用与sdp信息中一致的端口发流端口共享media.rtp.port-range这会损失一些性能
# 非严格模式使用随机端口发流,性能更好, 默认关闭
gb-send-stream-strict: false
# 关闭在线文档(生产环境建议关闭)
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-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-input v-model.number="mediaServerForm.recordAssistPort" :disabled="mediaServerForm.defaultServer">
<!-- <el-button v-if="mediaServerForm.recordAssistPort > 0" slot="append" type="primary" @click="checkRecordServer"></el-button>-->
@ -177,15 +172,12 @@ export default {
rtmpSSlPort: "",
rtpEnable: false,
rtpPortRange: "",
sendRtpPortRange: "",
rtpProxyPort: "",
rtspPort: "",
rtspSSLPort: "",
},
rtpPortRange1:30000,
rtpPortRange2:30500,
sendRtpPortRange1:30000,
sendRtpPortRange2:30500,
rules: {
ip: [{ required: true, validator: isValidIp, message: '请输入有效的IP地址', trigger: 'blur' }],
@ -196,8 +188,6 @@ export default {
rtmpSSlPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
rtpPortRange1: [{ 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' }],
rtspPort: [{ 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]
}
}
let sendRtpPortRange = this.mediaServerForm.sendRtpPortRange.split(",");
this.sendRtpPortRange1 = sendRtpPortRange[0]
this.sendRtpPortRange2 = sendRtpPortRange[1]
}
},
checkServer: function() {
@ -251,8 +238,6 @@ export default {
that.mediaServerForm = data.data;
that.mediaServerForm.httpPort = httpPort;
that.mediaServerForm.autoConfig = true;
that.sendRtpPortRange1 = 30000
that.sendRtpPortRange2 = 30500
that.rtpPortRange1 = 30000
that.rtpPortRange2 = 30500
that.serverCheck = 1;
@ -336,13 +321,10 @@ export default {
rtmpSSlPort: "",
rtpEnable: false,
rtpPortRange: "",
sendRtpPortRange: "",
rtpProxyPort: "",
rtspPort: "",
rtspSSLPort: "",
};
this.sendRtpPortRange1 = 30000;
this.sendRtpPortRange2 = 30500;
this.rtpPortRange1 = 30500;
this.rtpPortRange2 = 30500;
this.listChangeCallback = null
@ -367,9 +349,7 @@ export default {
}
},
portRangeChange: function() {
this.mediaServerForm.sendRtpPortRange = this.sendRtpPortRange1 + "," + this.sendRtpPortRange2
this.mediaServerForm.rtpPortRange = this.rtpPortRange1 + "," + this.rtpPortRange2
console.log(this.mediaServerForm.sendRtpPortRange)
console.log(this.mediaServerForm.rtpPortRange)
}
},