Commit 6fa5b37b962b05b3aa4b7bf019eb47c4cdbb5738
Merge branch 'main' into main2
# Conflicts: # src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java # src/main/resources/all-application.yml
Showing
15 changed files
with
340 additions
and
143 deletions
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
| ... | ... | @@ -69,6 +69,7 @@ public class VideoManagerConstants { |
| 69 | 69 | public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_"; |
| 70 | 70 | |
| 71 | 71 | public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_"; |
| 72 | + public static final String BROADCAST_WAITE_INVITE = "task_broadcast_waite_invite_"; | |
| 72 | 73 | |
| 73 | 74 | |
| 74 | 75 | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
| ... | ... | @@ -43,6 +43,8 @@ public class UserSetting { |
| 43 | 43 | |
| 44 | 44 | private Boolean syncChannelOnDeviceOnline = Boolean.FALSE; |
| 45 | 45 | |
| 46 | + private Boolean pushStreamAfterAck = Boolean.FALSE; | |
| 47 | + | |
| 46 | 48 | private String serverId = "000000"; |
| 47 | 49 | |
| 48 | 50 | private String thirdPartyGBIdReg = "[\\s\\S]*"; |
| ... | ... | @@ -206,4 +208,12 @@ public class UserSetting { |
| 206 | 208 | public void setBroadcastForPlatform(String broadcastForPlatform) { |
| 207 | 209 | this.broadcastForPlatform = broadcastForPlatform; |
| 208 | 210 | } |
| 211 | + | |
| 212 | + public Boolean getPushStreamAfterAck() { | |
| 213 | + return pushStreamAfterAck; | |
| 214 | + } | |
| 215 | + | |
| 216 | + public void setPushStreamAfterAck(Boolean pushStreamAfterAck) { | |
| 217 | + this.pushStreamAfterAck = pushStreamAfterAck; | |
| 218 | + } | |
| 209 | 219 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
| ... | ... | @@ -83,4 +83,19 @@ public class AudioBroadcastManager { |
| 83 | 83 | |
| 84 | 84 | return audioBroadcastCatch; |
| 85 | 85 | } |
| 86 | + | |
| 87 | + public List<AudioBroadcastCatch> get(String deviceId) { | |
| 88 | + List<AudioBroadcastCatch> audioBroadcastCatchList= new ArrayList<>(); | |
| 89 | + if (SipUtils.isFrontEnd(deviceId)) { | |
| 90 | + audioBroadcastCatchList.add(data.get(deviceId)); | |
| 91 | + }else { | |
| 92 | + for (String key : data.keySet()) { | |
| 93 | + if (key.startsWith(deviceId)) { | |
| 94 | + audioBroadcastCatchList.add(data.get(key)); | |
| 95 | + } | |
| 96 | + } | |
| 97 | + } | |
| 98 | + | |
| 99 | + return audioBroadcastCatchList; | |
| 100 | + } | |
| 86 | 101 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request; |
| 2 | 2 | |
| 3 | -import com.genersoft.iot.vmp.conf.SipConfig; | |
| 4 | 3 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 5 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | |
| 6 | 4 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 7 | 5 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 8 | -import gov.nist.javax.sip.SipProviderImpl; | |
| 9 | 6 | import gov.nist.javax.sip.message.SIPRequest; |
| 10 | 7 | import gov.nist.javax.sip.message.SIPResponse; |
| 11 | -import gov.nist.javax.sip.stack.SIPServerTransactionImpl; | |
| 12 | 8 | import org.apache.commons.lang3.ArrayUtils; |
| 13 | 9 | import org.dom4j.Document; |
| 14 | 10 | import org.dom4j.DocumentException; |
| ... | ... | @@ -17,14 +13,14 @@ import org.dom4j.io.SAXReader; |
| 17 | 13 | import org.slf4j.Logger; |
| 18 | 14 | import org.slf4j.LoggerFactory; |
| 19 | 15 | import org.springframework.beans.factory.annotation.Autowired; |
| 20 | -import org.springframework.beans.factory.annotation.Qualifier; | |
| 21 | -import org.springframework.security.core.parameters.P; | |
| 22 | 16 | |
| 23 | 17 | import javax.sip.*; |
| 24 | 18 | import javax.sip.address.Address; |
| 25 | 19 | import javax.sip.address.AddressFactory; |
| 26 | 20 | import javax.sip.address.SipURI; |
| 27 | -import javax.sip.header.*; | |
| 21 | +import javax.sip.header.ContentTypeHeader; | |
| 22 | +import javax.sip.header.ExpiresHeader; | |
| 23 | +import javax.sip.header.HeaderFactory; | |
| 28 | 24 | import javax.sip.message.MessageFactory; |
| 29 | 25 | import javax.sip.message.Request; |
| 30 | 26 | import javax.sip.message.Response; |
| ... | ... | @@ -157,7 +153,10 @@ public abstract class SIPRequestProcessorParent { |
| 157 | 153 | responseAckExtraParam.content = sdp; |
| 158 | 154 | responseAckExtraParam.sipURI = sipURI; |
| 159 | 155 | |
| 160 | - return responseAck(request, Response.OK, null, responseAckExtraParam); | |
| 156 | + SIPResponse sipResponse = responseAck(request, Response.OK, null, responseAckExtraParam); | |
| 157 | + | |
| 158 | + | |
| 159 | + return sipResponse; | |
| 161 | 160 | } |
| 162 | 161 | |
| 163 | 162 | /** |
| ... | ... | @@ -190,7 +189,8 @@ public abstract class SIPRequestProcessorParent { |
| 190 | 189 | reader.setEncoding(charset); |
| 191 | 190 | // 对海康出现的未转义字符做处理。 |
| 192 | 191 | String[] destStrArray = new String[]{"<",">","&","'","""}; |
| 193 | - char despChar = '&'; // 或许可扩展兼容其他字符 | |
| 192 | + // 或许可扩展兼容其他字符 | |
| 193 | + char despChar = '&'; | |
| 194 | 194 | byte destBye = (byte) despChar; |
| 195 | 195 | List<Byte> result = new ArrayList<>(); |
| 196 | 196 | byte[] rawContent = request.getRawContent(); |
| ... | ... | @@ -220,4 +220,5 @@ public abstract class SIPRequestProcessorParent { |
| 220 | 220 | return xml.getRootElement(); |
| 221 | 221 | } |
| 222 | 222 | |
| 223 | + | |
| 223 | 224 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; |
| 2 | 2 | |
| 3 | -import com.alibaba.fastjson2.JSON; | |
| 4 | 3 | import com.alibaba.fastjson2.JSONObject; |
| 5 | 4 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 6 | -import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 7 | -import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; | |
| 8 | -import com.genersoft.iot.vmp.gb28181.bean.Device; | |
| 5 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 9 | 6 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 10 | 7 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 11 | -import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | |
| 12 | 8 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| 13 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | |
| 14 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | |
| 15 | 9 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; |
| 16 | 10 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 17 | 11 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 18 | 12 | import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; |
| 19 | 13 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 20 | -import com.genersoft.iot.vmp.service.IDeviceService; | |
| 21 | 14 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 15 | +import com.genersoft.iot.vmp.service.IPlayService; | |
| 22 | 16 | import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; |
| 23 | 17 | import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; |
| 24 | 18 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| ... | ... | @@ -29,15 +23,15 @@ import org.springframework.beans.factory.InitializingBean; |
| 29 | 23 | import org.springframework.beans.factory.annotation.Autowired; |
| 30 | 24 | import org.springframework.stereotype.Component; |
| 31 | 25 | |
| 32 | -import javax.sip.InvalidArgumentException; | |
| 33 | 26 | import javax.sip.RequestEvent; |
| 34 | -import javax.sip.SipException; | |
| 35 | 27 | import javax.sip.address.SipURI; |
| 36 | 28 | import javax.sip.header.CallIdHeader; |
| 37 | 29 | import javax.sip.header.FromHeader; |
| 38 | 30 | import javax.sip.header.HeaderAddress; |
| 39 | 31 | import javax.sip.header.ToHeader; |
| 40 | 32 | import java.text.ParseException; |
| 33 | +import java.util.HashMap; | |
| 34 | +import java.util.Map; | |
| 41 | 35 | |
| 42 | 36 | /** |
| 43 | 37 | * SIP命令类型: ACK请求 |
| ... | ... | @@ -46,7 +40,7 @@ import java.text.ParseException; |
| 46 | 40 | @Component |
| 47 | 41 | public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { |
| 48 | 42 | |
| 49 | - private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class); | |
| 43 | + private final Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class); | |
| 50 | 44 | private final String method = "ACK"; |
| 51 | 45 | |
| 52 | 46 | @Autowired |
| ... | ... | @@ -74,31 +68,20 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 74 | 68 | private IMediaServerService mediaServerService; |
| 75 | 69 | |
| 76 | 70 | @Autowired |
| 77 | - private ZlmHttpHookSubscribe subscribe; | |
| 78 | - | |
| 79 | - @Autowired | |
| 80 | 71 | private DynamicTask dynamicTask; |
| 81 | 72 | |
| 82 | 73 | @Autowired |
| 83 | - private ISIPCommander cmder; | |
| 84 | - | |
| 85 | - @Autowired | |
| 86 | - private IDeviceService deviceService; | |
| 87 | - | |
| 88 | - @Autowired | |
| 89 | - private ISIPCommanderForPlatform commanderForPlatform; | |
| 74 | + private RedisGbPlayMsgListener redisGbPlayMsgListener; | |
| 90 | 75 | |
| 91 | 76 | @Autowired |
| 92 | - private AudioBroadcastManager audioBroadcastManager; | |
| 77 | + private UserSetting userSetting; | |
| 93 | 78 | |
| 94 | 79 | @Autowired |
| 95 | - private RedisGbPlayMsgListener redisGbPlayMsgListener; | |
| 80 | + private IPlayService playService; | |
| 96 | 81 | |
| 97 | 82 | |
| 98 | 83 | /** |
| 99 | 84 | * 处理 ACK请求 |
| 100 | - * | |
| 101 | - * @param evt | |
| 102 | 85 | */ |
| 103 | 86 | @Override |
| 104 | 87 | public void process(RequestEvent evt) { |
| ... | ... | @@ -106,44 +89,45 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 106 | 89 | |
| 107 | 90 | String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); |
| 108 | 91 | logger.info("[收到ACK]: platformGbId->{}", platformGbId); |
| 109 | - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId); | |
| 110 | - // 取消设置的超时任务 | |
| 111 | - dynamicTask.stop(callIdHeader.getCallId()); | |
| 112 | - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); | |
| 113 | - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); | |
| 114 | - if (sendRtpItem == null) { | |
| 115 | - logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId); | |
| 116 | - return; | |
| 117 | - } | |
| 118 | - String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; | |
| 119 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 120 | - logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(), | |
| 121 | - sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); | |
| 122 | - if (mediaInfo == null) { | |
| 123 | - RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( | |
| 124 | - sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), | |
| 125 | - sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), | |
| 126 | - sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); | |
| 127 | - redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> { | |
| 128 | - startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, callIdHeader); | |
| 129 | - }); | |
| 130 | - }else { | |
| 131 | - JSONObject startSendRtpStreamResult = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem); | |
| 132 | - if (startSendRtpStreamResult != null) { | |
| 133 | - startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, callIdHeader); | |
| 92 | + if (userSetting.getPushStreamAfterAck()) { | |
| 93 | + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId); | |
| 94 | + // 取消设置的超时任务 | |
| 95 | + dynamicTask.stop(callIdHeader.getCallId()); | |
| 96 | + String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); | |
| 97 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); | |
| 98 | + if (sendRtpItem == null) { | |
| 99 | + logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId); | |
| 100 | + return; | |
| 101 | + } | |
| 102 | + String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; | |
| 103 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 104 | + logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(), | |
| 105 | + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); | |
| 106 | + if (mediaInfo == null) { | |
| 107 | + RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( | |
| 108 | + sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), | |
| 109 | + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), | |
| 110 | + sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); | |
| 111 | + redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> { | |
| 112 | + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, callIdHeader); | |
| 113 | + }); | |
| 114 | + }else { | |
| 115 | + JSONObject startSendRtpStreamResult = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem); | |
| 116 | + if (startSendRtpStreamResult != null) { | |
| 117 | + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, callIdHeader); | |
| 118 | + } | |
| 134 | 119 | } |
| 135 | 120 | } |
| 136 | 121 | } |
| 137 | - | |
| 138 | 122 | private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, |
| 139 | - JSONObject jsonObject, CallIdHeader callIdHeader) { | |
| 123 | + JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) { | |
| 140 | 124 | if (jsonObject == null) { |
| 141 | 125 | logger.error("RTP推流失败: 请检查ZLM服务"); |
| 142 | 126 | } else if (jsonObject.getInteger("code") == 0) { |
| 143 | 127 | logger.info("调用ZLM推流接口, 结果: {}", jsonObject); |
| 144 | - logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getIp(), sendRtpItem.getPort()); | |
| 128 | + logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); | |
| 145 | 129 | } else { |
| 146 | - logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(sendRtpItem)); | |
| 130 | + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param)); | |
| 147 | 131 | if (sendRtpItem.isOnlyAudio()) { |
| 148 | 132 | Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); |
| 149 | 133 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); |
| ... | ... | @@ -152,17 +136,12 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 152 | 136 | cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); |
| 153 | 137 | } catch (SipException | ParseException | InvalidArgumentException | |
| 154 | 138 | SsrcTransactionNotFoundException e) { |
| 155 | - logger.error("[命令发送失败] 停止语音喊话: {}", e.getMessage()); | |
| 139 | + logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage()); | |
| 156 | 140 | } |
| 157 | 141 | } |
| 158 | - }else { | |
| 159 | - // 向上级平台 | |
| 160 | - try { | |
| 161 | - commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); | |
| 162 | - } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 163 | - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 164 | - } | |
| 165 | 142 | } |
| 166 | 143 | } |
| 144 | + | |
| 167 | 145 | } |
| 146 | + | |
| 168 | 147 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; |
| 2 | 2 | |
| 3 | 3 | import com.alibaba.fastjson2.JSONObject; |
| 4 | +import com.genersoft.iot.vmp.common.VideoManagerConstants; | |
| 4 | 5 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 5 | 6 | import com.genersoft.iot.vmp.conf.SipConfig; |
| 6 | 7 | import com.genersoft.iot.vmp.conf.UserSetting; |
| ... | ... | @@ -439,18 +440,23 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 439 | 440 | |
| 440 | 441 | try { |
| 441 | 442 | // 超时未收到Ack应该回复bye,当前等待时间为10秒 |
| 442 | - dynamicTask.startDelay(callIdHeader.getCallId(), () -> { | |
| 443 | - logger.info("Ack 等待超时"); | |
| 444 | - mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), sendRtpItem.getSsrc()); | |
| 445 | - // 回复bye | |
| 446 | - try { | |
| 447 | - cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId()); | |
| 448 | - } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 449 | - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 450 | - } | |
| 451 | - }, 60 * 1000); | |
| 452 | - responseSdpAck(request, content.toString(), platform); | |
| 443 | + if (userSetting.getPushStreamAfterAck()) { | |
| 444 | + dynamicTask.startDelay(callIdHeader.getCallId(), () -> { | |
| 445 | + logger.info("Ack 等待超时"); | |
| 446 | + mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), sendRtpItem.getSsrc()); | |
| 447 | + // 回复bye | |
| 448 | + try { | |
| 449 | + cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId()); | |
| 450 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 451 | + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 452 | + } | |
| 453 | + }, 60 * 1000); | |
| 454 | + } | |
| 453 | 455 | |
| 456 | + SIPResponse sipResponse = responseSdpAck(request, content.toString(), platform); | |
| 457 | + if (!userSetting.getPushStreamAfterAck()) { | |
| 458 | + playService.startPushStream(sendRtpItem, sipResponse, platform, request.getCallIdHeader()); | |
| 459 | + } | |
| 454 | 460 | } catch (SipException e) { |
| 455 | 461 | e.printStackTrace(); |
| 456 | 462 | } catch (InvalidArgumentException e) { |
| ... | ... | @@ -878,7 +884,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 878 | 884 | content.append("f=\r\n"); |
| 879 | 885 | |
| 880 | 886 | try { |
| 881 | - return responseSdpAck(request, content.toString(), platform); | |
| 887 | + SIPResponse sipResponse = responseSdpAck(request, content.toString(), platform); | |
| 888 | + if (!userSetting.getPushStreamAfterAck()) { | |
| 889 | + playService.startPushStream(sendRtpItem, sipResponse, platform, request.getCallIdHeader()); | |
| 890 | + } | |
| 891 | + return sipResponse; | |
| 882 | 892 | } catch (SipException e) { |
| 883 | 893 | e.printStackTrace(); |
| 884 | 894 | } catch (InvalidArgumentException e) { |
| ... | ... | @@ -905,11 +915,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 905 | 915 | } |
| 906 | 916 | if (device != null) { |
| 907 | 917 | logger.info("收到设备" + requesterId + "的语音广播Invite请求"); |
| 908 | - | |
| 918 | + String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + audioBroadcastCatch.getChannelId(); | |
| 919 | + dynamicTask.stop(key); | |
| 909 | 920 | try { |
| 910 | 921 | responseAck(request, Response.TRYING); |
| 911 | 922 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 912 | 923 | logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage()); |
| 924 | + playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); | |
| 925 | + return; | |
| 913 | 926 | } |
| 914 | 927 | String contentString = new String(request.getRawContent()); |
| 915 | 928 | // jainSip不支持y=字段, 移除移除以解析。 |
| ... | ... | @@ -964,11 +977,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 964 | 977 | responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 |
| 965 | 978 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 966 | 979 | logger.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage()); |
| 980 | + playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); | |
| 981 | + return; | |
| 967 | 982 | } |
| 968 | 983 | return; |
| 969 | 984 | } |
| 970 | 985 | String addressStr = sdp.getOrigin().getAddress(); |
| 971 | - logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", requesterId, addressStr, port, ssrc); | |
| 986 | + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, ssrc, | |
| 987 | + mediaTransmissionTCP ? (tcpActive? "TCP主动":"TCP被动") : "UDP"); | |
| 972 | 988 | |
| 973 | 989 | MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device); |
| 974 | 990 | if (mediaServerItem == null) { |
| ... | ... | @@ -977,6 +993,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 977 | 993 | responseAck(request, Response.BUSY_HERE); |
| 978 | 994 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 979 | 995 | logger.error("[命令发送失败] invite 未找到可用的zlm: {}", e.getMessage()); |
| 996 | + playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); | |
| 980 | 997 | } |
| 981 | 998 | return; |
| 982 | 999 | } |
| ... | ... | @@ -990,13 +1007,12 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 990 | 1007 | responseAck(request, Response.BUSY_HERE); |
| 991 | 1008 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 992 | 1009 | logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage()); |
| 1010 | + playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); | |
| 1011 | + return; | |
| 993 | 1012 | } |
| 994 | 1013 | return; |
| 995 | 1014 | } |
| 996 | - sendRtpItem.setTcp(mediaTransmissionTCP); | |
| 997 | - if (tcpActive != null) { | |
| 998 | - sendRtpItem.setTcpActive(tcpActive); | |
| 999 | - } | |
| 1015 | + | |
| 1000 | 1016 | String app = "broadcast"; |
| 1001 | 1017 | String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); |
| 1002 | 1018 | |
| ... | ... | @@ -1011,6 +1027,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 1011 | 1027 | sendRtpItem.setUsePs(false); |
| 1012 | 1028 | sendRtpItem.setRtcp(false); |
| 1013 | 1029 | sendRtpItem.setOnlyAudio(true); |
| 1030 | + sendRtpItem.setTcp(mediaTransmissionTCP); | |
| 1031 | + if (tcpActive != null) { | |
| 1032 | + sendRtpItem.setTcpActive(tcpActive); | |
| 1033 | + } | |
| 1034 | + | |
| 1014 | 1035 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 1015 | 1036 | |
| 1016 | 1037 | |
| ... | ... | @@ -1023,11 +1044,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 1023 | 1044 | responseAck(request, Response.GONE); |
| 1024 | 1045 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 1025 | 1046 | logger.error("[命令发送失败] 语音通话 回复410失败, {}", e.getMessage()); |
| 1047 | + return; | |
| 1026 | 1048 | } |
| 1027 | 1049 | playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); |
| 1028 | 1050 | } |
| 1029 | 1051 | } catch (SdpException e) { |
| 1030 | 1052 | logger.error("[SDP解析异常]", e); |
| 1053 | + playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); | |
| 1031 | 1054 | } |
| 1032 | 1055 | } else { |
| 1033 | 1056 | logger.warn("来自无效设备/平台的请求"); |
| ... | ... | @@ -1084,6 +1107,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 1084 | 1107 | audioBroadcastCatch.setSipTransactionInfoByRequset(sipResponse); |
| 1085 | 1108 | audioBroadcastManager.update(audioBroadcastCatch); |
| 1086 | 1109 | |
| 1110 | + // 开启发流,大华在收到200OK后就会开始建立连接 | |
| 1111 | + if (!userSetting.getPushStreamAfterAck()) { | |
| 1112 | + playService.startPushStream(sendRtpItem, sipResponse, parentPlatform, request.getCallIdHeader()); | |
| 1113 | + } | |
| 1114 | + | |
| 1087 | 1115 | } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { |
| 1088 | 1116 | logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage()); |
| 1089 | 1117 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; |
| 2 | 2 | |
| 3 | +import com.genersoft.iot.vmp.common.VideoManagerConstants; | |
| 4 | +import com.genersoft.iot.vmp.conf.DynamicTask; | |
| 3 | 5 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; |
| 4 | 6 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus; |
| 5 | 7 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| ... | ... | @@ -9,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| 9 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 10 | 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; |
| 11 | 13 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; |
| 14 | +import com.genersoft.iot.vmp.service.IPlayService; | |
| 12 | 15 | import gov.nist.javax.sip.message.SIPRequest; |
| 13 | 16 | import org.dom4j.Element; |
| 14 | 17 | import org.slf4j.Logger; |
| ... | ... | @@ -35,11 +38,14 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i |
| 35 | 38 | private ResponseMessageHandler responseMessageHandler; |
| 36 | 39 | |
| 37 | 40 | @Autowired |
| 38 | - private DeferredResultHolder deferredResultHolder; | |
| 41 | + private DynamicTask dynamicTask; | |
| 39 | 42 | |
| 40 | 43 | @Autowired |
| 41 | 44 | private AudioBroadcastManager audioBroadcastManager; |
| 42 | 45 | |
| 46 | + @Autowired | |
| 47 | + private IPlayService playService; | |
| 48 | + | |
| 43 | 49 | @Override |
| 44 | 50 | public void afterPropertiesSet() throws Exception { |
| 45 | 51 | responseMessageHandler.addHandler(cmdType, this); |
| ... | ... | @@ -47,6 +53,8 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i |
| 47 | 53 | |
| 48 | 54 | @Override |
| 49 | 55 | public void handForDevice(RequestEvent evt, Device device, Element rootElement) { |
| 56 | + | |
| 57 | + SIPRequest request = (SIPRequest) evt.getRequest(); | |
| 50 | 58 | try { |
| 51 | 59 | String channelId = getText(rootElement, "DeviceID"); |
| 52 | 60 | if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { |
| ... | ... | @@ -55,12 +63,23 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i |
| 55 | 63 | return; |
| 56 | 64 | } |
| 57 | 65 | String result = getText(rootElement, "Result"); |
| 58 | - logger.info("收到语音广播的回复 {}:{}/{}", result, device.getDeviceId(), channelId ); | |
| 59 | - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId); | |
| 60 | - audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); | |
| 61 | - audioBroadcastManager.update(audioBroadcastCatch); | |
| 66 | + logger.info("[语音广播]回复:{}, {}/{}", result, device.getDeviceId(), channelId ); | |
| 67 | + | |
| 62 | 68 | // 回复200 OK |
| 63 | - responseAck((SIPRequest) evt.getRequest(), Response.OK); | |
| 69 | + responseAck(request, Response.OK); | |
| 70 | + if (result.equalsIgnoreCase("OK")) { | |
| 71 | + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId); | |
| 72 | + audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); | |
| 73 | + audioBroadcastManager.update(audioBroadcastCatch); | |
| 74 | + // 等待invite消息, 超时则结束 | |
| 75 | + String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + channelId; | |
| 76 | + dynamicTask.startDelay(key, ()->{ | |
| 77 | + logger.info("[语音广播]等待invite消息超时:{}/{}", device.getDeviceId(), channelId); | |
| 78 | + playService.stopAudioBroadcast(device.getDeviceId(), channelId); | |
| 79 | + }, 2000); | |
| 80 | + }else { | |
| 81 | + playService.stopAudioBroadcast(device.getDeviceId(), channelId); | |
| 82 | + } | |
| 64 | 83 | } catch (ParseException | SipException | InvalidArgumentException e) { |
| 65 | 84 | logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage()); |
| 66 | 85 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
| ... | ... | @@ -35,6 +35,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; |
| 35 | 35 | public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { |
| 36 | 36 | |
| 37 | 37 | private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class); |
| 38 | + | |
| 38 | 39 | private final String cmdType = "Catalog"; |
| 39 | 40 | |
| 40 | 41 | @Autowired | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -323,7 +323,7 @@ public class ZLMHttpHookListener { |
| 323 | 323 | }); |
| 324 | 324 | |
| 325 | 325 | if ("rtsp".equals(param.getSchema())){ |
| 326 | - logger.info("on_stream_changed:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream()); | |
| 326 | + logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream()); | |
| 327 | 327 | if (param.isRegist()) { |
| 328 | 328 | mediaServerService.addCount(param.getMediaServerId()); |
| 329 | 329 | }else { |
| ... | ... | @@ -383,10 +383,10 @@ public class ZLMHttpHookListener { |
| 383 | 383 | } |
| 384 | 384 | |
| 385 | 385 | }else { |
| 386 | - logger.info("[语音喊话] 推流指向的·通道{}未找到", channelId); | |
| 386 | + logger.info("[语音对讲] 未找到通道:{}", channelId); | |
| 387 | 387 | } |
| 388 | - }else { | |
| 389 | - logger.info("[语音喊话] 推流指向的·设备{}未找到", deviceId); | |
| 388 | + }else{ | |
| 389 | + logger.info("[语音对讲] 未找到设备:{}", deviceId); | |
| 390 | 390 | } |
| 391 | 391 | }else { |
| 392 | 392 | logger.info("[语音喊话] 推流格式有误, 格式为: broadcast/设备编号_通道编号 "); | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
| ... | ... | @@ -36,7 +36,7 @@ public class ZLMRESTfulUtils { |
| 36 | 36 | // 设置连接超时时间 |
| 37 | 37 | httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); |
| 38 | 38 | // 设置读取超时时间 |
| 39 | - httpClientBuilder.readTimeout(5,TimeUnit.SECONDS); | |
| 39 | + httpClientBuilder.readTimeout(15,TimeUnit.SECONDS); | |
| 40 | 40 | // 设置连接池 |
| 41 | 41 | httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); |
| 42 | 42 | if (logger.isDebugEnabled()) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
| ... | ... | @@ -3,9 +3,7 @@ package com.genersoft.iot.vmp.service; |
| 3 | 3 | import com.alibaba.fastjson2.JSONObject; |
| 4 | 4 | import com.genersoft.iot.vmp.common.StreamInfo; |
| 5 | 5 | import com.genersoft.iot.vmp.conf.exception.ServiceException; |
| 6 | -import com.genersoft.iot.vmp.gb28181.bean.Device; | |
| 7 | -import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback; | |
| 8 | -import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo; | |
| 6 | +import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 9 | 7 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 10 | 8 | import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; |
| 11 | 9 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| ... | ... | @@ -15,11 +13,14 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| 15 | 13 | import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; |
| 16 | 14 | import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; |
| 17 | 15 | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; |
| 16 | +import gov.nist.javax.sip.message.SIPResponse; | |
| 18 | 17 | import org.springframework.web.context.request.async.DeferredResult; |
| 19 | 18 | |
| 20 | 19 | import javax.sip.InvalidArgumentException; |
| 21 | 20 | import javax.sip.SipException; |
| 21 | +import javax.sip.header.CallIdHeader; | |
| 22 | 22 | import java.text.ParseException; |
| 23 | +import java.util.Map; | |
| 23 | 24 | |
| 24 | 25 | /** |
| 25 | 26 | * 点播处理 |
| ... | ... | @@ -64,4 +65,9 @@ public interface IPlayService { |
| 64 | 65 | void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| 65 | 66 | |
| 66 | 67 | void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| 68 | + | |
| 69 | + void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader); | |
| 70 | + | |
| 71 | + void startSendRtpStreamHand(SendRtpItem sendRtpItem, ParentPlatform parentPlatform, | |
| 72 | + JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader); | |
| 67 | 73 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
| ... | ... | @@ -3,12 +3,15 @@ package com.genersoft.iot.vmp.service.impl; |
| 3 | 3 | 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 | +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | |
| 6 | 7 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 7 | 8 | import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; |
| 8 | 9 | import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; |
| 9 | 10 | import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; |
| 10 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; |
| 11 | 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; |
| 13 | +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; | |
| 14 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 12 | 15 | import com.genersoft.iot.vmp.service.IDeviceChannelService; |
| 13 | 16 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 14 | 17 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| ... | ... | @@ -32,9 +35,7 @@ import javax.sip.InvalidArgumentException; |
| 32 | 35 | import javax.sip.SipException; |
| 33 | 36 | import java.text.ParseException; |
| 34 | 37 | import java.time.Instant; |
| 35 | -import java.util.ArrayList; | |
| 36 | -import java.util.Collections; | |
| 37 | -import java.util.List; | |
| 38 | +import java.util.*; | |
| 38 | 39 | import java.util.concurrent.TimeUnit; |
| 39 | 40 | |
| 40 | 41 | /** |
| ... | ... | @@ -89,6 +90,12 @@ public class DeviceServiceImpl implements IDeviceService { |
| 89 | 90 | @Autowired |
| 90 | 91 | private IMediaServerService mediaServerService; |
| 91 | 92 | |
| 93 | + @Autowired | |
| 94 | + private AudioBroadcastManager audioBroadcastManager; | |
| 95 | + | |
| 96 | + @Autowired | |
| 97 | + private ZLMRESTfulUtils zlmresTfulUtils; | |
| 98 | + | |
| 92 | 99 | @Override |
| 93 | 100 | public void online(Device device) { |
| 94 | 101 | logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); |
| ... | ... | @@ -183,6 +190,23 @@ public class DeviceServiceImpl implements IDeviceService { |
| 183 | 190 | // 移除订阅 |
| 184 | 191 | removeCatalogSubscribe(device); |
| 185 | 192 | removeMobilePositionSubscribe(device); |
| 193 | + List<AudioBroadcastCatch> audioBroadcastCatches = audioBroadcastManager.get(deviceId); | |
| 194 | + if (audioBroadcastCatches.size() > 0) { | |
| 195 | + for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatches) { | |
| 196 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null); | |
| 197 | + if (sendRtpItem != null) { | |
| 198 | + redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null); | |
| 199 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 200 | + Map<String, Object> param = new HashMap<>(); | |
| 201 | + param.put("vhost", "__defaultVhost__"); | |
| 202 | + param.put("app", sendRtpItem.getApp()); | |
| 203 | + param.put("stream", sendRtpItem.getStreamId()); | |
| 204 | + zlmresTfulUtils.stopSendRtp(mediaInfo, param); | |
| 205 | + } | |
| 206 | + | |
| 207 | + audioBroadcastManager.del(deviceId, audioBroadcastCatch.getChannelId()); | |
| 208 | + } | |
| 209 | + } | |
| 186 | 210 | } |
| 187 | 211 | |
| 188 | 212 | @Override | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -24,16 +24,15 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 24 | 24 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 25 | 25 | import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; |
| 26 | 26 | import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; |
| 27 | +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout; | |
| 27 | 28 | import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; |
| 28 | 29 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 29 | 30 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 30 | 31 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 31 | 32 | import com.genersoft.iot.vmp.service.IMediaService; |
| 32 | 33 | import com.genersoft.iot.vmp.service.IPlayService; |
| 33 | -import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; | |
| 34 | -import com.genersoft.iot.vmp.service.bean.PlayBackCallback; | |
| 35 | -import com.genersoft.iot.vmp.service.bean.PlayBackResult; | |
| 36 | -import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 34 | +import com.genersoft.iot.vmp.service.bean.*; | |
| 35 | +import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; | |
| 37 | 36 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 38 | 37 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 39 | 38 | import com.genersoft.iot.vmp.utils.DateUtil; |
| ... | ... | @@ -42,6 +41,7 @@ import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; |
| 42 | 41 | import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; |
| 43 | 42 | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; |
| 44 | 43 | import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; |
| 44 | +import gov.nist.javax.sip.message.SIPResponse; | |
| 45 | 45 | import org.slf4j.Logger; |
| 46 | 46 | import org.slf4j.LoggerFactory; |
| 47 | 47 | import org.springframework.beans.factory.annotation.Autowired; |
| ... | ... | @@ -54,13 +54,11 @@ import org.springframework.web.context.request.async.DeferredResult; |
| 54 | 54 | import javax.sip.InvalidArgumentException; |
| 55 | 55 | import javax.sip.ResponseEvent; |
| 56 | 56 | import javax.sip.SipException; |
| 57 | +import javax.sip.header.CallIdHeader; | |
| 57 | 58 | import java.math.BigDecimal; |
| 58 | 59 | import java.math.RoundingMode; |
| 59 | 60 | import java.text.ParseException; |
| 60 | -import java.util.HashMap; | |
| 61 | -import java.util.List; | |
| 62 | -import java.util.Map; | |
| 63 | -import java.util.UUID; | |
| 61 | +import java.util.*; | |
| 64 | 62 | |
| 65 | 63 | @SuppressWarnings(value = {"rawtypes", "unchecked"}) |
| 66 | 64 | @Service |
| ... | ... | @@ -119,11 +117,20 @@ public class PlayServiceImpl implements IPlayService { |
| 119 | 117 | @Autowired |
| 120 | 118 | private ZlmHttpHookSubscribe subscribe; |
| 121 | 119 | |
| 120 | + @Autowired | |
| 121 | + private ISIPCommanderForPlatform commanderForPlatform; | |
| 122 | + | |
| 122 | 123 | |
| 123 | 124 | @Qualifier("taskExecutor") |
| 124 | 125 | @Autowired |
| 125 | 126 | private ThreadPoolTaskExecutor taskExecutor; |
| 126 | 127 | |
| 128 | + @Autowired | |
| 129 | + private RedisGbPlayMsgListener redisGbPlayMsgListener; | |
| 130 | + | |
| 131 | + @Autowired | |
| 132 | + private ZlmHttpHookSubscribe hookSubscribe; | |
| 133 | + | |
| 127 | 134 | |
| 128 | 135 | @Override |
| 129 | 136 | public void play(MediaServerItem mediaServerItem, String deviceId, String channelId, |
| ... | ... | @@ -1024,8 +1031,20 @@ public class PlayServiceImpl implements IPlayService { |
| 1024 | 1031 | return false; |
| 1025 | 1032 | } |
| 1026 | 1033 | // 查询通道使用状态 |
| 1027 | - if (audioBroadcastInUse(device, channelId)) { | |
| 1028 | - return false; | |
| 1034 | + if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | |
| 1035 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); | |
| 1036 | + if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) { | |
| 1037 | + // 查询流是否存在,不存在则认为是异常状态 | |
| 1038 | + MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 1039 | + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStreamId()); | |
| 1040 | + if (streamReady) { | |
| 1041 | + logger.warn("语音广播已经开启: {}", channelId); | |
| 1042 | + event.call("语音广播已经开启"); | |
| 1043 | + return; | |
| 1044 | + } else { | |
| 1045 | + stopAudioBroadcast(device.getDeviceId(), channelId); | |
| 1046 | + } | |
| 1047 | + } | |
| 1029 | 1048 | } |
| 1030 | 1049 | |
| 1031 | 1050 | // 发送通知 |
| ... | ... | @@ -1063,28 +1082,31 @@ public class PlayServiceImpl implements IPlayService { |
| 1063 | 1082 | |
| 1064 | 1083 | @Override |
| 1065 | 1084 | public void stopAudioBroadcast(String deviceId, String channelId) { |
| 1066 | - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(deviceId, channelId); | |
| 1067 | - if (audioBroadcastCatch != null) { | |
| 1085 | + List<AudioBroadcastCatch> audioBroadcastCatchList = new ArrayList<>(); | |
| 1086 | + if (channelId == null) { | |
| 1087 | + audioBroadcastCatchList.addAll(audioBroadcastManager.get(deviceId)); | |
| 1088 | + }else { | |
| 1089 | + audioBroadcastCatchList.add(audioBroadcastManager.get(deviceId, channelId)); | |
| 1090 | + } | |
| 1091 | + if (audioBroadcastCatchList.size() > 0) { | |
| 1092 | + for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatchList) { | |
| 1093 | + Device device = deviceService.getDevice(deviceId); | |
| 1094 | + if (device == null || audioBroadcastCatch == null ) { | |
| 1095 | + return; | |
| 1096 | + } | |
| 1097 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null); | |
| 1098 | + if (sendRtpItem != null) { | |
| 1099 | + redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null); | |
| 1100 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 1101 | + Map<String, Object> param = new HashMap<>(); | |
| 1102 | + param.put("vhost", "__defaultVhost__"); | |
| 1103 | + param.put("app", sendRtpItem.getApp()); | |
| 1104 | + param.put("stream", sendRtpItem.getStreamId()); | |
| 1105 | + zlmresTfulUtils.stopSendRtp(mediaInfo, param); | |
| 1106 | + } | |
| 1068 | 1107 | |
| 1069 | - Device device = deviceService.getDevice(deviceId); | |
| 1070 | - if (device == null) { | |
| 1071 | - return; | |
| 1072 | - } | |
| 1073 | - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null); | |
| 1074 | - if (sendRtpItem != null) { | |
| 1075 | - redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null); | |
| 1076 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 1077 | - Map<String, Object> param = new HashMap<>(); | |
| 1078 | - param.put("vhost", "__defaultVhost__"); | |
| 1079 | - param.put("app", sendRtpItem.getApp()); | |
| 1080 | - param.put("stream", sendRtpItem.getStreamId()); | |
| 1081 | - zlmresTfulUtils.stopSendRtp(mediaInfo, param); | |
| 1082 | - } | |
| 1083 | - if (audioBroadcastCatch.isFromPlatform()) { | |
| 1084 | - // TODO 向上级发送BYE结束语音喊话 | |
| 1108 | + audioBroadcastManager.del(deviceId, channelId); | |
| 1085 | 1109 | } |
| 1086 | - | |
| 1087 | - audioBroadcastManager.del(deviceId, channelId); | |
| 1088 | 1110 | } |
| 1089 | 1111 | } |
| 1090 | 1112 | |
| ... | ... | @@ -1187,4 +1209,100 @@ public class PlayServiceImpl implements IPlayService { |
| 1187 | 1209 | Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); |
| 1188 | 1210 | cmder.playResumeCmd(device, streamInfo); |
| 1189 | 1211 | } |
| 1212 | + | |
| 1213 | + @Override | |
| 1214 | + public void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader) { | |
| 1215 | + | |
| 1216 | + // 开始发流 | |
| 1217 | + // 取消设置的超时任务 | |
| 1218 | +// String channelId = request.getCallIdHeader().getCallId(); | |
| 1219 | + | |
| 1220 | + String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; | |
| 1221 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 1222 | + logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(), | |
| 1223 | + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); | |
| 1224 | + Map<String, Object> param = new HashMap<>(12); | |
| 1225 | + param.put("vhost","__defaultVhost__"); | |
| 1226 | + param.put("app",sendRtpItem.getApp()); | |
| 1227 | + param.put("stream",sendRtpItem.getStreamId()); | |
| 1228 | + param.put("ssrc", sendRtpItem.getSsrc()); | |
| 1229 | + param.put("src_port", sendRtpItem.getLocalPort()); | |
| 1230 | + param.put("pt", sendRtpItem.getPt()); | |
| 1231 | + param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); | |
| 1232 | + param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); | |
| 1233 | + param.put("is_udp", is_Udp); | |
| 1234 | + if (!sendRtpItem.isTcp()) { | |
| 1235 | + // udp模式下开启rtcp保活 | |
| 1236 | + param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); | |
| 1237 | + } | |
| 1238 | + | |
| 1239 | + if (mediaInfo == null) { | |
| 1240 | + RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( | |
| 1241 | + sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), | |
| 1242 | + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), | |
| 1243 | + sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); | |
| 1244 | + redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> { | |
| 1245 | + startSendRtpStreamHand(sendRtpItem, platform, json, param, callIdHeader); | |
| 1246 | + }); | |
| 1247 | + } else { | |
| 1248 | + // 如果是非严格模式,需要关闭端口占用 | |
| 1249 | + JSONObject startSendRtpStreamResult = null; | |
| 1250 | + if (sendRtpItem.getLocalPort() != 0) { | |
| 1251 | + HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId()); | |
| 1252 | + hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout); | |
| 1253 | + if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) { | |
| 1254 | + if (sendRtpItem.isTcpActive()) { | |
| 1255 | + startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param); | |
| 1256 | + }else { | |
| 1257 | + param.put("dst_url", sendRtpItem.getIp()); | |
| 1258 | + param.put("dst_port", sendRtpItem.getPort()); | |
| 1259 | + startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 1260 | + } | |
| 1261 | + } | |
| 1262 | + }else { | |
| 1263 | + if (sendRtpItem.isTcpActive()) { | |
| 1264 | + startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param); | |
| 1265 | + }else { | |
| 1266 | + param.put("dst_url", sendRtpItem.getIp()); | |
| 1267 | + param.put("dst_port", sendRtpItem.getPort()); | |
| 1268 | + startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 1269 | + } | |
| 1270 | + } | |
| 1271 | + if (startSendRtpStreamResult != null) { | |
| 1272 | + startSendRtpStreamHand(sendRtpItem, platform, startSendRtpStreamResult, param, callIdHeader); | |
| 1273 | + } | |
| 1274 | + } | |
| 1275 | + } | |
| 1276 | + | |
| 1277 | + @Override | |
| 1278 | + public void startSendRtpStreamHand(SendRtpItem sendRtpItem, ParentPlatform parentPlatform, | |
| 1279 | + JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) { | |
| 1280 | + if (jsonObject == null) { | |
| 1281 | + logger.error("RTP推流失败: 请检查ZLM服务"); | |
| 1282 | + } else if (jsonObject.getInteger("code") == 0) { | |
| 1283 | + logger.info("调用ZLM推流接口, 结果: {}", jsonObject); | |
| 1284 | + logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); | |
| 1285 | + } else { | |
| 1286 | + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param)); | |
| 1287 | + if (sendRtpItem.isOnlyAudio()) { | |
| 1288 | + Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); | |
| 1289 | + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 1290 | + if (audioBroadcastCatch != null) { | |
| 1291 | + try { | |
| 1292 | + cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); | |
| 1293 | + } catch (SipException | ParseException | InvalidArgumentException | | |
| 1294 | + SsrcTransactionNotFoundException e) { | |
| 1295 | + logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage()); | |
| 1296 | + } | |
| 1297 | + } | |
| 1298 | + }else { | |
| 1299 | + // 向上级平台 | |
| 1300 | + try { | |
| 1301 | + commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); | |
| 1302 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 1303 | + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 1304 | + } | |
| 1305 | + } | |
| 1306 | + } | |
| 1307 | + } | |
| 1190 | 1308 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
| ... | ... | @@ -6,7 +6,6 @@ import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | 6 | import com.genersoft.iot.vmp.gb28181.event.EventPublisher; |
| 7 | 7 | import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; |
| 8 | 8 | import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; |
| 9 | -import com.genersoft.iot.vmp.service.IGbStreamService; | |
| 10 | 9 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 11 | 10 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 12 | 11 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| ... | ... | @@ -90,12 +89,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 90 | 89 | @Autowired |
| 91 | 90 | private PlatformGbStreamMapper platformGbStreamMapper; |
| 92 | 91 | |
| 93 | - @Autowired | |
| 94 | - private IGbStreamService gbStreamService; | |
| 95 | - | |
| 96 | - @Autowired | |
| 97 | - private ParentPlatformMapper parentPlatformMapper; | |
| 98 | - | |
| 99 | 92 | /** |
| 100 | 93 | * 根据设备ID判断设备是否存在 |
| 101 | 94 | * | ... | ... |
src/main/resources/all-application.yml
| ... | ... | @@ -197,6 +197,8 @@ user-settings: |
| 197 | 197 | sync-channel-on-device-online: false |
| 198 | 198 | # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式 |
| 199 | 199 | broadcast-for-platform: UDP |
| 200 | + # 收到ack消息后开始发流,默认false, 回复200ok后直接开始发流 | |
| 201 | + push-stream-after-ack: false | |
| 200 | 202 | |
| 201 | 203 | # 关闭在线文档(生产环境建议关闭) |
| 202 | 204 | springdoc: | ... | ... |