Commit 3fe47021b9aefb11b1e659383ac2f3d0edd2aa42
1 parent
ebc904e4
优化国标规范,参考国标文档中-点播外域设备媒体流SSRC处理方式,上级点播时自定义ssrc,不适用上级携带的ssrc,也避免上级兼容性差,不携带ssrc的问题,可通过配置关闭此特性
Showing
8 changed files
with
61 additions
and
21 deletions
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
| ... | ... | @@ -53,6 +53,7 @@ public class UserSetting { |
| 53 | 53 | private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE; |
| 54 | 54 | |
| 55 | 55 | private Boolean deviceStatusNotify = Boolean.FALSE; |
| 56 | + private Boolean useCustomSsrcForParentInvite = Boolean.TRUE; | |
| 56 | 57 | |
| 57 | 58 | private String serverId = "000000"; |
| 58 | 59 | |
| ... | ... | @@ -277,4 +278,12 @@ public class UserSetting { |
| 277 | 278 | public void setDeviceStatusNotify(Boolean deviceStatusNotify) { |
| 278 | 279 | this.deviceStatusNotify = deviceStatusNotify; |
| 279 | 280 | } |
| 281 | + | |
| 282 | + public Boolean getUseCustomSsrcForParentInvite() { | |
| 283 | + return useCustomSsrcForParentInvite; | |
| 284 | + } | |
| 285 | + | |
| 286 | + public void setUseCustomSsrcForParentInvite(Boolean useCustomSsrcForParentInvite) { | |
| 287 | + this.useCustomSsrcForParentInvite = useCustomSsrcForParentInvite; | |
| 288 | + } | |
| 280 | 289 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
| ... | ... | @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.UserSetting; |
| 5 | 5 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 6 | 6 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 7 | 7 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 8 | +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; | |
| 8 | 9 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; |
| 9 | 10 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 10 | 11 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| ... | ... | @@ -38,6 +39,9 @@ public class SipRunner implements CommandLineRunner { |
| 38 | 39 | private IRedisCatchStorage redisCatchStorage; |
| 39 | 40 | |
| 40 | 41 | @Autowired |
| 42 | + private SSRCFactory ssrcFactory; | |
| 43 | + | |
| 44 | + @Autowired | |
| 41 | 45 | private UserSetting userSetting; |
| 42 | 46 | |
| 43 | 47 | @Autowired |
| ... | ... | @@ -96,6 +100,7 @@ public class SipRunner implements CommandLineRunner { |
| 96 | 100 | MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 97 | 101 | redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId()); |
| 98 | 102 | if (mediaServerItem != null) { |
| 103 | + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 99 | 104 | Map<String, Object> param = new HashMap<>(); |
| 100 | 105 | param.put("vhost","__defaultVhost__"); |
| 101 | 106 | 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.Device; |
| 6 | 6 | import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType; |
| 7 | 7 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 8 | 8 | import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; |
| 9 | +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; | |
| 9 | 10 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 10 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| 11 | 12 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; |
| ... | ... | @@ -61,6 +62,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 61 | 62 | private ZLMRTPServerFactory zlmrtpServerFactory; |
| 62 | 63 | |
| 63 | 64 | @Autowired |
| 65 | + private SSRCFactory ssrcFactory; | |
| 66 | + | |
| 67 | + @Autowired | |
| 64 | 68 | private IMediaServerService mediaServerService; |
| 65 | 69 | |
| 66 | 70 | @Autowired |
| ... | ... | @@ -102,6 +106,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 102 | 106 | logger.info("[收到bye] 停止向上级推流:{}", streamId); |
| 103 | 107 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 104 | 108 | redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); |
| 109 | + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 105 | 110 | zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); |
| 106 | 111 | int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); |
| 107 | 112 | if (totalReaderCount <= 0) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -245,18 +245,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 245 | 245 | String contentString = new String(request.getRawContent()); |
| 246 | 246 | |
| 247 | 247 | // jainSip不支持y=字段, 移除以解析。 |
| 248 | - int ssrcIndex = contentString.indexOf("y="); | |
| 249 | 248 | // 检查是否有y字段 |
| 250 | - String ssrcDefault = "0000000000"; | |
| 251 | - String ssrc; | |
| 249 | + int ssrcIndex = contentString.indexOf("y="); | |
| 250 | + | |
| 252 | 251 | SessionDescription sdp; |
| 253 | 252 | if (ssrcIndex >= 0) { |
| 254 | 253 | //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段 |
| 255 | - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 256 | - String substring = contentString.substring(0, contentString.indexOf("y=")); | |
| 254 | + String substring = contentString.substring(0, ssrcIndex); | |
| 257 | 255 | sdp = SdpFactory.getInstance().createSessionDescription(substring); |
| 258 | 256 | } else { |
| 259 | - ssrc = ssrcDefault; | |
| 260 | 257 | sdp = SdpFactory.getInstance().createSessionDescription(contentString); |
| 261 | 258 | } |
| 262 | 259 | String sessionName = sdp.getSessionName().getValue(); |
| ... | ... | @@ -320,7 +317,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 320 | 317 | String username = sdp.getOrigin().getUsername(); |
| 321 | 318 | String addressStr = sdp.getConnection().getAddress(); |
| 322 | 319 | |
| 323 | - logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); | |
| 320 | + | |
| 324 | 321 | Device device = null; |
| 325 | 322 | // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 |
| 326 | 323 | if (channel != null) { |
| ... | ... | @@ -344,6 +341,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 344 | 341 | } |
| 345 | 342 | return; |
| 346 | 343 | } |
| 344 | + | |
| 345 | + String ssrc; | |
| 346 | + if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) { | |
| 347 | + // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 | |
| 348 | + ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); | |
| 349 | + }else { | |
| 350 | + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 351 | + } | |
| 352 | + logger.info("[上级点播] 用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); | |
| 347 | 353 | SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, |
| 348 | 354 | device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp()); |
| 349 | 355 | |
| ... | ... | @@ -465,29 +471,23 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 465 | 471 | } |
| 466 | 472 | } |
| 467 | 473 | if (playTransaction == null) { |
| 474 | + // 被点播的通道目前未被点播,则开始点播 | |
| 468 | 475 | String streamId = null; |
| 469 | 476 | if (mediaServerItem.isRtpEnable()) { |
| 470 | 477 | streamId = String.format("%s_%s", device.getDeviceId(), channelId); |
| 471 | 478 | } |
| 472 | - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); | |
| 479 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); | |
| 473 | 480 | logger.info(JSONObject.toJSONString(ssrcInfo)); |
| 474 | 481 | sendRtpItem.setStreamId(ssrcInfo.getStream()); |
| 475 | - sendRtpItem.setSsrc(ssrc.equals(ssrcDefault) ? ssrcInfo.getSsrc() : ssrc); | |
| 482 | +// sendRtpItem.setSsrc(ssrcInfo.getSsrc()); | |
| 476 | 483 | |
| 477 | 484 | // 写入redis, 超时时回复 |
| 478 | 485 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 479 | - MediaServerItem finalMediaServerItem = mediaServerItem; | |
| 480 | 486 | playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> { |
| 481 | 487 | logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId); |
| 482 | 488 | redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); |
| 483 | 489 | }); |
| 484 | 490 | } else { |
| 485 | - // 当前系统作为下级平台使用,当上级平台点播时不携带ssrc时,并且设备在当前系统中已经点播了。这个时候需要重新给生成一个ssrc,不使用默认的"0000000000"。 | |
| 486 | - if (ssrc.equals(ssrcDefault)) { | |
| 487 | - ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); | |
| 488 | - ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc); | |
| 489 | - sendRtpItem.setSsrc(ssrc); | |
| 490 | - } | |
| 491 | 491 | |
| 492 | 492 | sendRtpItem.setStreamId(playTransaction.getStream()); |
| 493 | 493 | // 写入redis, 超时时回复 |
| ... | ... | @@ -499,11 +499,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 499 | 499 | } |
| 500 | 500 | } |
| 501 | 501 | } else if (gbStream != null) { |
| 502 | - if(ssrc.equals(ssrcDefault)) | |
| 503 | - { | |
| 504 | - ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); | |
| 505 | - ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc); | |
| 502 | + | |
| 503 | + String ssrc; | |
| 504 | + if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) { | |
| 505 | + // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 | |
| 506 | + ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); | |
| 507 | + }else { | |
| 508 | + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 506 | 509 | } |
| 510 | + | |
| 507 | 511 | if("push".equals(gbStream.getStreamType())) { |
| 508 | 512 | if (streamPushItem != null && streamPushItem.isPushIng()) { |
| 509 | 513 | // 推流状态 | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; |
| 8 | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 9 | 9 | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; |
| 10 | 10 | import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; |
| 11 | +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; | |
| 11 | 12 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 12 | 13 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| 13 | 14 | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; |
| ... | ... | @@ -105,6 +106,9 @@ public class ZLMHttpHookListener { |
| 105 | 106 | @Autowired |
| 106 | 107 | private AssistRESTfulUtils assistRESTfulUtils; |
| 107 | 108 | |
| 109 | + @Autowired | |
| 110 | + private SSRCFactory ssrcFactory; | |
| 111 | + | |
| 108 | 112 | @Qualifier("taskExecutor") |
| 109 | 113 | @Autowired |
| 110 | 114 | private ThreadPoolTaskExecutor taskExecutor; |
| ... | ... | @@ -666,6 +670,7 @@ public class ZLMHttpHookListener { |
| 666 | 670 | if (sendRtpItems.size() > 0) { |
| 667 | 671 | for (SendRtpItem sendRtpItem : sendRtpItems) { |
| 668 | 672 | ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); |
| 673 | + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 669 | 674 | try { |
| 670 | 675 | commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); |
| 671 | 676 | } 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.DynamicTask; |
| 4 | 4 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 5 | 5 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | 6 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 7 | +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; | |
| 7 | 8 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; |
| 8 | 9 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 9 | 10 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| ... | ... | @@ -54,6 +55,9 @@ public class PlatformServiceImpl implements IPlatformService { |
| 54 | 55 | private IRedisCatchStorage redisCatchStorage; |
| 55 | 56 | |
| 56 | 57 | @Autowired |
| 58 | + private SSRCFactory ssrcFactory; | |
| 59 | + | |
| 60 | + @Autowired | |
| 57 | 61 | private IMediaServerService mediaServerService; |
| 58 | 62 | |
| 59 | 63 | @Autowired |
| ... | ... | @@ -328,6 +332,7 @@ public class PlatformServiceImpl implements IPlatformService { |
| 328 | 332 | List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(platformId); |
| 329 | 333 | if (sendRtpItems != null && sendRtpItems.size() > 0) { |
| 330 | 334 | for (SendRtpItem sendRtpItem : sendRtpItems) { |
| 335 | + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 331 | 336 | redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null); |
| 332 | 337 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 333 | 338 | Map<String, Object> param = new HashMap<>(3); | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
| ... | ... | @@ -915,7 +915,12 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { |
| 915 | 915 | @Override |
| 916 | 916 | public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) { |
| 917 | 917 | String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS; |
| 918 | - logger.info("[redis通知] 推送设备/通道状态, {}/{}-{}", deviceId, channelId, online); | |
| 918 | + if (channelId == null) { | |
| 919 | + logger.info("[redis通知] 推送设备状态, {}-{}", deviceId, online); | |
| 920 | + }else { | |
| 921 | + logger.info("[redis通知] 推送通道状态, {}/{}-{}", deviceId, channelId, online); | |
| 922 | + } | |
| 923 | + | |
| 919 | 924 | StringBuilder msg = new StringBuilder(); |
| 920 | 925 | msg.append(deviceId); |
| 921 | 926 | if (channelId != null) { | ... | ... |
src/main/resources/all-application.yml
| ... | ... | @@ -194,6 +194,8 @@ user-settings: |
| 194 | 194 | max-notify-count-queue: 10000 |
| 195 | 195 | # 设备/通道状态变化时发送消息 |
| 196 | 196 | device-status-notify: false |
| 197 | + # 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 | |
| 198 | + use-custom-ssrc-for-parent-invite: true | |
| 197 | 199 | # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个 |
| 198 | 200 | allowed-origins: |
| 199 | 201 | - http://localhost:8008 | ... | ... |