优化国标规范,参考国标文档中-点播外域设备媒体流SSRC处理方式,上级点播时自定义ssrc,不适用上级携带的ssrc,也避免上级兼容性差,不携带ssrc的问题,可通过配置关闭此特性

pull/844/head
648540858 2023-05-04 14:21:58 +08:00
parent ebc904e4d5
commit 3fe47021b9
8 changed files with 61 additions and 21 deletions

View File

@ -53,6 +53,7 @@ public class UserSetting {
private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE; private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
private Boolean deviceStatusNotify = Boolean.FALSE; private Boolean deviceStatusNotify = Boolean.FALSE;
private Boolean useCustomSsrcForParentInvite = Boolean.TRUE;
private String serverId = "000000"; private String serverId = "000000";
@ -277,4 +278,12 @@ public class UserSetting {
public void setDeviceStatusNotify(Boolean deviceStatusNotify) { public void setDeviceStatusNotify(Boolean deviceStatusNotify) {
this.deviceStatusNotify = deviceStatusNotify; this.deviceStatusNotify = deviceStatusNotify;
} }
public Boolean getUseCustomSsrcForParentInvite() {
return useCustomSsrcForParentInvite;
}
public void setUseCustomSsrcForParentInvite(Boolean useCustomSsrcForParentInvite) {
this.useCustomSsrcForParentInvite = useCustomSsrcForParentInvite;
}
} }

View File

@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@ -37,6 +38,9 @@ public class SipRunner implements CommandLineRunner {
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Autowired
private SSRCFactory ssrcFactory;
@Autowired @Autowired
private UserSetting userSetting; private UserSetting userSetting;
@ -96,6 +100,7 @@ public class SipRunner implements CommandLineRunner {
MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId()); redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId());
if (mediaServerItem != null) { if (mediaServerItem != null) {
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("vhost","__defaultVhost__"); param.put("vhost","__defaultVhost__");
param.put("app",sendRtpItem.getApp()); param.put("app",sendRtpItem.getApp());

View File

@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType; import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
@ -60,6 +61,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired @Autowired
private ZLMRTPServerFactory zlmrtpServerFactory; private ZLMRTPServerFactory zlmrtpServerFactory;
@Autowired
private SSRCFactory ssrcFactory;
@Autowired @Autowired
private IMediaServerService mediaServerService; private IMediaServerService mediaServerService;
@ -102,6 +106,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
logger.info("[收到bye] 停止向上级推流:{}", streamId); logger.info("[收到bye] 停止向上级推流:{}", streamId);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
if (totalReaderCount <= 0) { if (totalReaderCount <= 0) {

View File

@ -245,18 +245,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
String contentString = new String(request.getRawContent()); String contentString = new String(request.getRawContent());
// jainSip不支持y=字段, 移除以解析。 // jainSip不支持y=字段, 移除以解析。
int ssrcIndex = contentString.indexOf("y=");
// 检查是否有y字段 // 检查是否有y字段
String ssrcDefault = "0000000000"; int ssrcIndex = contentString.indexOf("y=");
String ssrc;
SessionDescription sdp; SessionDescription sdp;
if (ssrcIndex >= 0) { if (ssrcIndex >= 0) {
//ssrc规定长度为10个字节不取余下长度以避免后续还有“f=”字段 //ssrc规定长度为10个字节不取余下长度以避免后续还有“f=”字段
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); String substring = contentString.substring(0, ssrcIndex);
String substring = contentString.substring(0, contentString.indexOf("y="));
sdp = SdpFactory.getInstance().createSessionDescription(substring); sdp = SdpFactory.getInstance().createSessionDescription(substring);
} else { } else {
ssrc = ssrcDefault;
sdp = SdpFactory.getInstance().createSessionDescription(contentString); sdp = SdpFactory.getInstance().createSessionDescription(contentString);
} }
String sessionName = sdp.getSessionName().getValue(); String sessionName = sdp.getSessionName().getValue();
@ -320,7 +317,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
String username = sdp.getOrigin().getUsername(); String username = sdp.getOrigin().getUsername();
String addressStr = sdp.getConnection().getAddress(); String addressStr = sdp.getConnection().getAddress();
logger.info("[上级点播]用户:{} 通道:{}, 地址:{}:{} ssrc{}", username, channelId, addressStr, port, ssrc);
Device device = null; Device device = null;
// 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标
if (channel != null) { if (channel != null) {
@ -344,6 +341,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
} }
return; return;
} }
String ssrc;
if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
}else {
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
}
logger.info("[上级点播] 用户:{} 通道:{}, 地址:{}:{} ssrc{}", username, channelId, addressStr, port, ssrc);
SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp()); device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp());
@ -465,29 +471,23 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
} }
} }
if (playTransaction == null) { if (playTransaction == null) {
// 被点播的通道目前未被点播,则开始点播
String streamId = null; String streamId = null;
if (mediaServerItem.isRtpEnable()) { if (mediaServerItem.isRtpEnable()) {
streamId = String.format("%s_%s", device.getDeviceId(), channelId); streamId = String.format("%s_%s", device.getDeviceId(), channelId);
} }
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam());
logger.info(JSONObject.toJSONString(ssrcInfo)); logger.info(JSONObject.toJSONString(ssrcInfo));
sendRtpItem.setStreamId(ssrcInfo.getStream()); sendRtpItem.setStreamId(ssrcInfo.getStream());
sendRtpItem.setSsrc(ssrc.equals(ssrcDefault) ? ssrcInfo.getSsrc() : ssrc); // sendRtpItem.setSsrc(ssrcInfo.getSsrc());
// 写入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);
}); });
} else { } else {
// 当前系统作为下级平台使用当上级平台点播时不携带ssrc时并且设备在当前系统中已经点播了。这个时候需要重新给生成一个ssrc不使用默认的"0000000000"。
if (ssrc.equals(ssrcDefault)) {
ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
sendRtpItem.setSsrc(ssrc);
}
sendRtpItem.setStreamId(playTransaction.getStream()); sendRtpItem.setStreamId(playTransaction.getStream());
// 写入redis 超时时回复 // 写入redis 超时时回复
@ -499,11 +499,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
} }
} }
} else if (gbStream != null) { } else if (gbStream != null) {
if(ssrc.equals(ssrcDefault))
{ String ssrc;
ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) {
ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc); // 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
}else {
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
} }
if("push".equals(gbStream.getStreamType())) { if("push".equals(gbStream.getStreamType())) {
if (streamPushItem != null && streamPushItem.isPushIng()) { if (streamPushItem != null && streamPushItem.isPushIng()) {
// 推流状态 // 推流状态

View File

@ -8,6 +8,7 @@ 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.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
@ -105,6 +106,9 @@ public class ZLMHttpHookListener {
@Autowired @Autowired
private AssistRESTfulUtils assistRESTfulUtils; private AssistRESTfulUtils assistRESTfulUtils;
@Autowired
private SSRCFactory ssrcFactory;
@Qualifier("taskExecutor") @Qualifier("taskExecutor")
@Autowired @Autowired
private ThreadPoolTaskExecutor taskExecutor; private ThreadPoolTaskExecutor taskExecutor;
@ -666,6 +670,7 @@ public class ZLMHttpHookListener {
if (sendRtpItems.size() > 0) { if (sendRtpItems.size() > 0) {
for (SendRtpItem sendRtpItem : sendRtpItems) { for (SendRtpItem sendRtpItem : sendRtpItems) {
ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
try { try {
commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
} catch (SipException | InvalidArgumentException | ParseException e) { } catch (SipException | InvalidArgumentException | ParseException e) {

View File

@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
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.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@ -53,6 +54,9 @@ public class PlatformServiceImpl implements IPlatformService {
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Autowired
private SSRCFactory ssrcFactory;
@Autowired @Autowired
private IMediaServerService mediaServerService; private IMediaServerService mediaServerService;
@ -328,6 +332,7 @@ public class PlatformServiceImpl implements IPlatformService {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(platformId); List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(platformId);
if (sendRtpItems != null && sendRtpItems.size() > 0) { if (sendRtpItems != null && sendRtpItems.size() > 0) {
for (SendRtpItem sendRtpItem : sendRtpItems) { for (SendRtpItem sendRtpItem : sendRtpItems) {
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null); redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Map<String, Object> param = new HashMap<>(3); Map<String, Object> param = new HashMap<>(3);

View File

@ -915,7 +915,12 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override @Override
public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) { public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) {
String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS; String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS;
logger.info("[redis通知] 推送设备/通道状态, {}/{}-{}", deviceId, channelId, online); if (channelId == null) {
logger.info("[redis通知] 推送设备状态, {}-{}", deviceId, online);
}else {
logger.info("[redis通知] 推送通道状态, {}/{}-{}", deviceId, channelId, online);
}
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
msg.append(deviceId); msg.append(deviceId);
if (channelId != null) { if (channelId != null) {

View File

@ -194,6 +194,8 @@ user-settings:
max-notify-count-queue: 10000 max-notify-count-queue: 10000
# 设备/通道状态变化时发送消息 # 设备/通道状态变化时发送消息
device-status-notify: false device-status-notify: false
# 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
use-custom-ssrc-for-parent-invite: true
# 跨域配置,配置你访问前端页面的地址即可, 可以配置多个 # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个
allowed-origins: allowed-origins:
- http://localhost:8008 - http://localhost:8008