src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
@@ -53,6 +53,7 @@ private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE; private Boolean deviceStatusNotify = Boolean.FALSE; private Boolean useCustomSsrcForParentInvite = Boolean.TRUE; private String serverId = "000000"; @@ -277,4 +278,12 @@ public void setDeviceStatusNotify(Boolean deviceStatusNotify) { this.deviceStatusNotify = deviceStatusNotify; } public Boolean getUseCustomSsrcForParentInvite() { return useCustomSsrcForParentInvite; } public void setUseCustomSsrcForParentInvite(Boolean useCustomSsrcForParentInvite) { this.useCustomSsrcForParentInvite = useCustomSsrcForParentInvite; } } src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; 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.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; @@ -36,6 +37,9 @@ @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private SSRCFactory ssrcFactory; @Autowired private UserSetting userSetting; @@ -96,6 +100,7 @@ MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId()); if (mediaServerItem != null) { ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); Map<String, Object> param = new HashMap<>(); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
@@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; 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.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; @@ -61,6 +62,9 @@ private ZLMRTPServerFactory zlmrtpServerFactory; @Autowired private SSRCFactory ssrcFactory; @Autowired private IMediaServerService mediaServerService; @Autowired @@ -102,6 +106,7 @@ logger.info("[收到bye] 停止向上级推流:{}", streamId); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); if (totalReaderCount <= 0) { src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -245,18 +245,15 @@ String contentString = new String(request.getRawContent()); // jainSip不支持y=字段, 移除以解析。 int ssrcIndex = contentString.indexOf("y="); // 检查是否有y字段 String ssrcDefault = "0000000000"; String ssrc; int ssrcIndex = contentString.indexOf("y="); SessionDescription sdp; if (ssrcIndex >= 0) { //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段 ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); String substring = contentString.substring(0, contentString.indexOf("y=")); String substring = contentString.substring(0, ssrcIndex); sdp = SdpFactory.getInstance().createSessionDescription(substring); } else { ssrc = ssrcDefault; sdp = SdpFactory.getInstance().createSessionDescription(contentString); } String sessionName = sdp.getSessionName().getValue(); @@ -320,7 +317,7 @@ String username = sdp.getOrigin().getUsername(); String addressStr = sdp.getConnection().getAddress(); logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); Device device = null; // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 if (channel != null) { @@ -344,6 +341,15 @@ } 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, device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp()); @@ -465,29 +471,23 @@ } } if (playTransaction == null) { // 被点播的通道目前未被点播,则开始点播 String streamId = null; if (mediaServerItem.isRtpEnable()) { 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)); sendRtpItem.setStreamId(ssrcInfo.getStream()); sendRtpItem.setSsrc(ssrc.equals(ssrcDefault) ? ssrcInfo.getSsrc() : ssrc); // sendRtpItem.setSsrc(ssrcInfo.getSsrc()); // 写入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); }); } 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()); // 写入redis, 超时时回复 @@ -499,11 +499,15 @@ } } } else if (gbStream != null) { if(ssrc.equals(ssrcDefault)) { ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc); 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); } if("push".equals(gbStream.getStreamType())) { if (streamPushItem != null && streamPushItem.isPushIng()) { // 推流状态 src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; 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.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; @@ -104,6 +105,9 @@ @Autowired private AssistRESTfulUtils assistRESTfulUtils; @Autowired private SSRCFactory ssrcFactory; @Qualifier("taskExecutor") @Autowired @@ -666,6 +670,7 @@ if (sendRtpItems.size() > 0) { for (SendRtpItem sendRtpItem : sendRtpItems) { ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); try { commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); } catch (SipException | InvalidArgumentException | ParseException e) { src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
@@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; 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.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; @@ -52,6 +53,9 @@ @Autowired private IRedisCatchStorage redisCatchStorage; @Autowired private SSRCFactory ssrcFactory; @Autowired private IMediaServerService mediaServerService; @@ -328,6 +332,7 @@ List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(platformId); if (sendRtpItems != null && sendRtpItems.size() > 0) { for (SendRtpItem sendRtpItem : sendRtpItems) { ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); Map<String, Object> param = new HashMap<>(3); src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -915,7 +915,12 @@ @Override public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) { 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(); msg.append(deviceId); if (channelId != null) { src/main/resources/all-application.yml
@@ -194,6 +194,8 @@ max-notify-count-queue: 10000 # 设备/通道状态变化时发送消息 device-status-notify: false # 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 use-custom-ssrc-for-parent-invite: true # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个 allowed-origins: - http://localhost:8008