Commit 4b827f3897600e97023ded3df83a2f2551131d53
1 parent
38a85d43
级联语音对讲部分
Showing
28 changed files
with
940 additions
and
223 deletions
doc/README.md
doc/_content/theory/broadcast_cascade.md
0 → 100644
| 1 | +<!-- 点播流程 --> | |
| 2 | + | |
| 3 | +# 点播流程 | |
| 4 | +> 以下为WVP-PRO级联语音喊话流程。 | |
| 5 | + | |
| 6 | +```plantuml | |
| 7 | +@startuml | |
| 8 | +"上级平台" -> "下级平台": 1. 发起语音喊话请求 | |
| 9 | +"上级平台" <-- "下级平台": 2. 200OK | |
| 10 | +"上级平台" <- "下级平台": 3. 回复Result OK | |
| 11 | +"上级平台" --> "下级平台": 4. 200OK | |
| 12 | + | |
| 13 | +"下级平台" -> "设备": 5. 发起语音喊话请求 | |
| 14 | +"下级平台" <-- "设备": 6. 200OK | |
| 15 | +"下级平台" <- "设备": 7. 回复Result OK | |
| 16 | +"下级平台" --> "设备": 8. 200OK | |
| 17 | + | |
| 18 | +"下级平台" <- "设备": 9. invite(broadcast) | |
| 19 | +"下级平台" --> "设备": 10. 100 trying | |
| 20 | +"下级平台" --> "设备": 11. 200OK SDP | |
| 21 | +"下级平台" <-- "设备": 12. ack | |
| 22 | + | |
| 23 | +"上级平台" <- "下级平台": 13. invite(broadcast) | |
| 24 | +"上级平台" --> "下级平台": 14. 100 trying | |
| 25 | +"上级平台" --> "下级平台": 15. 200OK SDP | |
| 26 | +"上级平台" <-- "下级平台": 16. ack | |
| 27 | + | |
| 28 | +"上级平台" -> "下级平台": 17. 推送RTP | |
| 29 | +"下级平台" -> "设备": 18. 推送RTP | |
| 30 | + | |
| 31 | +@enduml | |
| 32 | +``` | |
| 33 | + | |
| 34 | + | |
| 35 | +## 注册流程描述如下: | |
| 36 | +1. 用户从网页或调用接口发起点播请求; | |
| 37 | +2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、 | |
| 38 | + 接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播,y字段描述SSRC值,f字段描述媒体参数。 | |
| 39 | +3. 摄像机向WVP-PRO回复200OK,消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。 | |
| 40 | +4. WVP-PRO向设备回复Ack, 会话建立成功。 | |
| 41 | +5. 设备向ZLMediaKit发送实时流。 | |
| 42 | +6. ZLMediaKit向WVP-PRO发送流改变事件。 | |
| 43 | +7. WVP-PRO向WEB用户回复播放地址。 | |
| 44 | +8. ZLMediaKit向WVP发送流无人观看事件。 | |
| 45 | +9. WVP-PRO向设备回复Bye, 结束会话。 | |
| 46 | +10. 设备回复200OK,会话结束成功。 | ... | ... |
doc/_sidebar.md
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
| ... | ... | @@ -47,6 +47,8 @@ public class UserSetting { |
| 47 | 47 | |
| 48 | 48 | private String thirdPartyGBIdReg = "[\\s\\S]*"; |
| 49 | 49 | |
| 50 | + private String broadcastForPlatform = "UDP"; | |
| 51 | + | |
| 50 | 52 | private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); |
| 51 | 53 | |
| 52 | 54 | public Boolean getSavePositionHistory() { |
| ... | ... | @@ -196,4 +198,12 @@ public class UserSetting { |
| 196 | 198 | public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) { |
| 197 | 199 | this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline; |
| 198 | 200 | } |
| 201 | + | |
| 202 | + public String getBroadcastForPlatform() { | |
| 203 | + return broadcastForPlatform; | |
| 204 | + } | |
| 205 | + | |
| 206 | + public void setBroadcastForPlatform(String broadcastForPlatform) { | |
| 207 | + this.broadcastForPlatform = broadcastForPlatform; | |
| 208 | + } | |
| 199 | 209 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.bean; |
| 2 | 2 | |
| 3 | 3 | |
| 4 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 5 | +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; | |
| 4 | 6 | import gov.nist.javax.sip.message.SIPResponse; |
| 5 | 7 | |
| 6 | 8 | /** |
| ... | ... | @@ -10,10 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse; |
| 10 | 12 | public class AudioBroadcastCatch { |
| 11 | 13 | |
| 12 | 14 | |
| 13 | - public AudioBroadcastCatch(String deviceId, String channelId, AudioBroadcastCatchStatus status) { | |
| 15 | + public AudioBroadcastCatch( | |
| 16 | + String deviceId, | |
| 17 | + String channelId, | |
| 18 | + MediaServerItem mediaServerItem, | |
| 19 | + String app, | |
| 20 | + String stream, | |
| 21 | + AudioBroadcastEvent event, | |
| 22 | + AudioBroadcastCatchStatus status, | |
| 23 | + boolean isFromPlatform | |
| 24 | + ) { | |
| 14 | 25 | this.deviceId = deviceId; |
| 15 | 26 | this.channelId = channelId; |
| 16 | 27 | this.status = status; |
| 28 | + this.event = event; | |
| 29 | + this.isFromPlatform = isFromPlatform; | |
| 30 | + this.app = app; | |
| 31 | + this.stream = stream; | |
| 32 | + this.mediaServerItem = mediaServerItem; | |
| 17 | 33 | } |
| 18 | 34 | |
| 19 | 35 | public AudioBroadcastCatch() { |
| ... | ... | @@ -30,6 +46,26 @@ public class AudioBroadcastCatch { |
| 30 | 46 | private String channelId; |
| 31 | 47 | |
| 32 | 48 | /** |
| 49 | + * 流媒体信息 | |
| 50 | + */ | |
| 51 | + private MediaServerItem mediaServerItem; | |
| 52 | + | |
| 53 | + /** | |
| 54 | + * 关联的流APP | |
| 55 | + */ | |
| 56 | + private String app; | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * 关联的流STREAM | |
| 60 | + */ | |
| 61 | + private String stream; | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * 是否是级联语音喊话 | |
| 65 | + */ | |
| 66 | + private boolean isFromPlatform; | |
| 67 | + | |
| 68 | + /** | |
| 33 | 69 | * 语音广播状态 |
| 34 | 70 | */ |
| 35 | 71 | private AudioBroadcastCatchStatus status; |
| ... | ... | @@ -39,6 +75,11 @@ public class AudioBroadcastCatch { |
| 39 | 75 | */ |
| 40 | 76 | private SipTransactionInfo sipTransactionInfo; |
| 41 | 77 | |
| 78 | + /** | |
| 79 | + * 请求结果回调 | |
| 80 | + */ | |
| 81 | + private AudioBroadcastEvent event; | |
| 82 | + | |
| 42 | 83 | |
| 43 | 84 | public String getDeviceId() { |
| 44 | 85 | return deviceId; |
| ... | ... | @@ -75,4 +116,44 @@ public class AudioBroadcastCatch { |
| 75 | 116 | public void setSipTransactionInfoByRequset(SIPResponse response) { |
| 76 | 117 | this.sipTransactionInfo = new SipTransactionInfo(response, false); |
| 77 | 118 | } |
| 119 | + | |
| 120 | + public AudioBroadcastEvent getEvent() { | |
| 121 | + return event; | |
| 122 | + } | |
| 123 | + | |
| 124 | + public void setEvent(AudioBroadcastEvent event) { | |
| 125 | + this.event = event; | |
| 126 | + } | |
| 127 | + | |
| 128 | + public String getApp() { | |
| 129 | + return app; | |
| 130 | + } | |
| 131 | + | |
| 132 | + public void setApp(String app) { | |
| 133 | + this.app = app; | |
| 134 | + } | |
| 135 | + | |
| 136 | + public String getStream() { | |
| 137 | + return stream; | |
| 138 | + } | |
| 139 | + | |
| 140 | + public void setStream(String stream) { | |
| 141 | + this.stream = stream; | |
| 142 | + } | |
| 143 | + | |
| 144 | + public boolean isFromPlatform() { | |
| 145 | + return isFromPlatform; | |
| 146 | + } | |
| 147 | + | |
| 148 | + public void setFromPlatform(boolean fromPlatform) { | |
| 149 | + isFromPlatform = fromPlatform; | |
| 150 | + } | |
| 151 | + | |
| 152 | + public MediaServerItem getMediaServerItem() { | |
| 153 | + return mediaServerItem; | |
| 154 | + } | |
| 155 | + | |
| 156 | + public void setMediaServerItem(MediaServerItem mediaServerItem) { | |
| 157 | + this.mediaServerItem = mediaServerItem; | |
| 158 | + } | |
| 78 | 159 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
| ... | ... | @@ -66,7 +66,7 @@ public class ParentPlatform { |
| 66 | 66 | * 设备端口 |
| 67 | 67 | */ |
| 68 | 68 | @Schema(description = "设备端口") |
| 69 | - private String devicePort; | |
| 69 | + private int devicePort; | |
| 70 | 70 | |
| 71 | 71 | /** |
| 72 | 72 | * SIP认证用户名(默认使用设备国标编号) |
| ... | ... | @@ -261,11 +261,11 @@ public class ParentPlatform { |
| 261 | 261 | this.deviceIp = deviceIp; |
| 262 | 262 | } |
| 263 | 263 | |
| 264 | - public String getDevicePort() { | |
| 264 | + public int getDevicePort() { | |
| 265 | 265 | return devicePort; |
| 266 | 266 | } |
| 267 | 267 | |
| 268 | - public void setDevicePort(String devicePort) { | |
| 268 | + public void setDevicePort(int devicePort) { | |
| 269 | 269 | this.devicePort = devicePort; |
| 270 | 270 | } |
| 271 | 271 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.event; |
| 2 | 2 | |
| 3 | -import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 4 | 3 | import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent; |
| 5 | 4 | import gov.nist.javax.sip.message.SIPRequest; |
| 6 | 5 | import org.slf4j.Logger; |
| ... | ... | @@ -87,6 +86,11 @@ public class SipSubscribe { |
| 87 | 86 | public String callId; |
| 88 | 87 | public EventObject event; |
| 89 | 88 | |
| 89 | + public EventResult(int statusCode, String msg) { | |
| 90 | + this.statusCode = statusCode; | |
| 91 | + this.msg = msg; | |
| 92 | + } | |
| 93 | + | |
| 90 | 94 | public EventResult(EventObject event) { |
| 91 | 95 | this.event = event; |
| 92 | 96 | if (event instanceof ResponseEvent) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
| ... | ... | @@ -23,10 +23,6 @@ public class AudioBroadcastManager { |
| 23 | 23 | |
| 24 | 24 | public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>(); |
| 25 | 25 | |
| 26 | - public void add(AudioBroadcastCatch audioBroadcastCatch) { | |
| 27 | - this.update(audioBroadcastCatch); | |
| 28 | - } | |
| 29 | - | |
| 30 | 26 | public void update(AudioBroadcastCatch audioBroadcastCatch) { |
| 31 | 27 | if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) { |
| 32 | 28 | data.put(audioBroadcastCatch.getDeviceId(), audioBroadcastCatch); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
| ... | ... | @@ -49,8 +49,6 @@ public class DeferredResultHolder { |
| 49 | 49 | |
| 50 | 50 | public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; |
| 51 | 51 | |
| 52 | - public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST"; | |
| 53 | - | |
| 54 | 52 | private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>(); |
| 55 | 53 | |
| 56 | 54 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd; |
| 2 | 2 | |
| 3 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 3 | 4 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 4 | 5 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 6 | +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | |
| 7 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 5 | 8 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 9 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 6 | 10 | |
| 7 | 11 | import javax.sip.InvalidArgumentException; |
| 8 | 12 | import javax.sip.SipException; |
| ... | ... | @@ -14,77 +18,98 @@ public interface ISIPCommanderForPlatform { |
| 14 | 18 | |
| 15 | 19 | /** |
| 16 | 20 | * 向上级平台注册 |
| 21 | + * | |
| 17 | 22 | * @param parentPlatform |
| 18 | 23 | * @return |
| 19 | 24 | */ |
| 20 | - void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; | |
| 21 | - void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException; | |
| 25 | + void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) | |
| 26 | + throws InvalidArgumentException, ParseException, SipException; | |
| 27 | + | |
| 28 | + void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, | |
| 29 | + SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) | |
| 30 | + throws SipException, InvalidArgumentException, ParseException; | |
| 22 | 31 | |
| 23 | 32 | /** |
| 24 | 33 | * 向上级平台注销 |
| 34 | + * | |
| 25 | 35 | * @param parentPlatform |
| 26 | 36 | * @return |
| 27 | 37 | */ |
| 28 | - void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; | |
| 38 | + void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) | |
| 39 | + throws InvalidArgumentException, ParseException, SipException; | |
| 29 | 40 | |
| 30 | 41 | |
| 31 | 42 | /** |
| 32 | 43 | * 向上级平发送心跳信息 |
| 44 | + * | |
| 33 | 45 | * @param parentPlatform |
| 34 | 46 | * @return callId(作为接受回复的判定) |
| 35 | 47 | */ |
| 36 | - String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; | |
| 48 | + String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) | |
| 49 | + throws SipException, InvalidArgumentException, ParseException; | |
| 37 | 50 | |
| 38 | 51 | |
| 39 | 52 | /** |
| 40 | 53 | * 向上级回复通道信息 |
| 41 | - * @param channel 通道信息 | |
| 54 | + * | |
| 55 | + * @param channel 通道信息 | |
| 42 | 56 | * @param parentPlatform 平台信息 |
| 43 | 57 | * @param sn |
| 44 | 58 | * @param fromTag |
| 45 | 59 | * @param size |
| 46 | 60 | * @return |
| 47 | 61 | */ |
| 48 | - void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException; | |
| 49 | - void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException; | |
| 62 | + void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) | |
| 63 | + throws SipException, InvalidArgumentException, ParseException; | |
| 64 | + | |
| 65 | + void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) | |
| 66 | + throws InvalidArgumentException, ParseException, SipException; | |
| 50 | 67 | |
| 51 | 68 | /** |
| 52 | 69 | * 向上级回复DeviceInfo查询信息 |
| 70 | + * | |
| 53 | 71 | * @param parentPlatform 平台信息 |
| 54 | 72 | * @param sn |
| 55 | 73 | * @param fromTag |
| 56 | 74 | * @return |
| 57 | 75 | */ |
| 58 | - void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException; | |
| 76 | + void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) | |
| 77 | + throws SipException, InvalidArgumentException, ParseException; | |
| 59 | 78 | |
| 60 | 79 | /** |
| 61 | 80 | * 向上级回复DeviceStatus查询信息 |
| 81 | + * | |
| 62 | 82 | * @param parentPlatform 平台信息 |
| 63 | 83 | * @param sn |
| 64 | 84 | * @param fromTag |
| 65 | 85 | * @return |
| 66 | 86 | */ |
| 67 | - void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException; | |
| 87 | + void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) | |
| 88 | + throws SipException, InvalidArgumentException, ParseException; | |
| 68 | 89 | |
| 69 | 90 | /** |
| 70 | 91 | * 向上级回复移动位置订阅消息 |
| 92 | + * | |
| 71 | 93 | * @param parentPlatform 平台信息 |
| 72 | - * @param gpsMsgInfo GPS信息 | |
| 73 | - * @param subscribeInfo 订阅相关的信息 | |
| 94 | + * @param gpsMsgInfo GPS信息 | |
| 95 | + * @param subscribeInfo 订阅相关的信息 | |
| 74 | 96 | * @return |
| 75 | 97 | */ |
| 76 | - void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; | |
| 98 | + void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) | |
| 99 | + throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; | |
| 77 | 100 | |
| 78 | 101 | /** |
| 79 | 102 | * 向上级回复报警消息 |
| 103 | + * | |
| 80 | 104 | * @param parentPlatform 平台信息 |
| 81 | - * @param deviceAlarm 报警信息信息 | |
| 105 | + * @param deviceAlarm 报警信息信息 | |
| 82 | 106 | * @return |
| 83 | 107 | */ |
| 84 | 108 | void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; |
| 85 | 109 | |
| 86 | 110 | /** |
| 87 | 111 | * 回复catalog事件-增加/更新 |
| 112 | + * | |
| 88 | 113 | * @param parentPlatform |
| 89 | 114 | * @param deviceChannels |
| 90 | 115 | */ |
| ... | ... | @@ -92,22 +117,28 @@ public interface ISIPCommanderForPlatform { |
| 92 | 117 | |
| 93 | 118 | /** |
| 94 | 119 | * 回复catalog事件-删除 |
| 120 | + * | |
| 95 | 121 | * @param parentPlatform |
| 96 | 122 | * @param deviceChannels |
| 97 | 123 | */ |
| 98 | - void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; | |
| 124 | + void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, | |
| 125 | + SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, | |
| 126 | + ParseException, NoSuchFieldException, SipException, IllegalAccessException; | |
| 99 | 127 | |
| 100 | 128 | /** |
| 101 | 129 | * 回复recordInfo |
| 102 | - * @param deviceChannel 通道信息 | |
| 130 | + * | |
| 131 | + * @param deviceChannel 通道信息 | |
| 103 | 132 | * @param parentPlatform 平台信息 |
| 104 | - * @param fromTag fromTag | |
| 105 | - * @param recordInfo 录像信息 | |
| 133 | + * @param fromTag fromTag | |
| 134 | + * @param recordInfo 录像信息 | |
| 106 | 135 | */ |
| 107 | - void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException; | |
| 136 | + void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) | |
| 137 | + throws SipException, InvalidArgumentException, ParseException; | |
| 108 | 138 | |
| 109 | 139 | /** |
| 110 | 140 | * 录像播放推送完成时发送MediaStatus消息 |
| 141 | + * | |
| 111 | 142 | * @param platform |
| 112 | 143 | * @param sendRtpItem |
| 113 | 144 | * @return |
| ... | ... | @@ -116,9 +147,19 @@ public interface ISIPCommanderForPlatform { |
| 116 | 147 | |
| 117 | 148 | /** |
| 118 | 149 | * 向发起点播的上级回复bye |
| 150 | + * | |
| 119 | 151 | * @param platform 平台信息 |
| 120 | - * @param callId callId | |
| 152 | + * @param callId callId | |
| 121 | 153 | */ |
| 122 | 154 | void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; |
| 155 | + | |
| 123 | 156 | void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException; |
| 157 | + | |
| 158 | + void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; | |
| 159 | + | |
| 160 | + void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, | |
| 161 | + SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, | |
| 162 | + SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException; | |
| 163 | + | |
| 164 | + void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; | |
| 124 | 165 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
| ... | ... | @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig; |
| 4 | 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 5 | 5 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 6 | 6 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; | |
| 7 | 8 | import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; |
| 8 | 9 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 9 | 10 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| ... | ... | @@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; |
| 14 | 15 | import org.springframework.stereotype.Component; |
| 15 | 16 | import org.springframework.util.DigestUtils; |
| 16 | 17 | |
| 17 | -import javax.sip.*; | |
| 18 | +import javax.sip.InvalidArgumentException; | |
| 19 | +import javax.sip.PeerUnavailableException; | |
| 18 | 20 | import javax.sip.address.Address; |
| 19 | 21 | import javax.sip.address.SipURI; |
| 20 | 22 | import javax.sip.header.*; |
| ... | ... | @@ -22,7 +24,6 @@ import javax.sip.message.Request; |
| 22 | 24 | import javax.validation.constraints.NotNull; |
| 23 | 25 | import java.text.ParseException; |
| 24 | 26 | import java.util.ArrayList; |
| 25 | -import java.util.List; | |
| 26 | 27 | import java.util.UUID; |
| 27 | 28 | |
| 28 | 29 | /** |
| ... | ... | @@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider { |
| 175 | 176 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); |
| 176 | 177 | // via |
| 177 | 178 | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); |
| 178 | - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()), | |
| 179 | + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), | |
| 179 | 180 | parentPlatform.getTransport(), viaTag); |
| 180 | 181 | viaHeader.setRPort(); |
| 181 | 182 | viaHeaders.add(viaHeader); |
| ... | ... | @@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider { |
| 212 | 213 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); |
| 213 | 214 | // via |
| 214 | 215 | ArrayList<ViaHeader> viaHeaders = new ArrayList<>(); |
| 215 | - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()), | |
| 216 | + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), | |
| 216 | 217 | parentPlatform.getTransport(), SipUtils.getNewViaTag()); |
| 217 | 218 | viaHeader.setRPort(); |
| 218 | 219 | viaHeaders.add(viaHeader); |
| ... | ... | @@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider { |
| 272 | 273 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); |
| 273 | 274 | // via |
| 274 | 275 | ArrayList<ViaHeader> viaHeaders = new ArrayList<>(); |
| 275 | - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()), | |
| 276 | + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(), | |
| 276 | 277 | platform.getTransport(), SipUtils.getNewViaTag()); |
| 277 | 278 | viaHeader.setRPort(); |
| 278 | 279 | viaHeaders.add(viaHeader); |
| ... | ... | @@ -308,4 +309,81 @@ public class SIPRequestHeaderPlarformProvider { |
| 308 | 309 | |
| 309 | 310 | return request; |
| 310 | 311 | } |
| 312 | + | |
| 313 | + public Request createInviteRequest(ParentPlatform platform, String channelId, String toString, String viaTag, String fromTag, Object content, String ssrc, CallIdHeader callIdHeader, String transport) throws PeerUnavailableException, ParseException, InvalidArgumentException { | |
| 314 | + Request request = null; | |
| 315 | + //请求行 | |
| 316 | + String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); | |
| 317 | + SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress); | |
| 318 | + //via | |
| 319 | + ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); | |
| 320 | + HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory(); | |
| 321 | + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag); | |
| 322 | + viaHeader.setRPort(); | |
| 323 | + viaHeaders.add(viaHeader); | |
| 324 | + | |
| 325 | + //from | |
| 326 | + SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); | |
| 327 | + Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); | |
| 328 | + FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack | |
| 329 | + //to | |
| 330 | + SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress); | |
| 331 | + Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); | |
| 332 | + ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null); | |
| 333 | + | |
| 334 | + //Forwards | |
| 335 | + MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); | |
| 336 | + | |
| 337 | + //ceq | |
| 338 | + CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); | |
| 339 | + request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); | |
| 340 | + | |
| 341 | + request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); | |
| 342 | + | |
| 343 | + Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ deviceHostAddress)); | |
| 344 | + // Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); | |
| 345 | + request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); | |
| 346 | + // Subject | |
| 347 | + SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); | |
| 348 | + request.addHeader(subjectHeader); | |
| 349 | + ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); | |
| 350 | + request.setContent(content, contentTypeHeader); | |
| 351 | + return request; | |
| 352 | + } | |
| 353 | + | |
| 354 | + public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException { | |
| 355 | + String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); | |
| 356 | + Request request = null; | |
| 357 | + SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress); | |
| 358 | + | |
| 359 | + // via | |
| 360 | + ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); | |
| 361 | + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); | |
| 362 | + viaHeaders.add(viaHeader); | |
| 363 | + //from | |
| 364 | + SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); | |
| 365 | + Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); | |
| 366 | + FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isFromServer()?transactionInfo.getFromTag():transactionInfo.getToTag()); | |
| 367 | + //to | |
| 368 | + SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress); | |
| 369 | + Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); | |
| 370 | + ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,transactionInfo.isFromServer()?transactionInfo.getToTag():transactionInfo.getFromTag()); | |
| 371 | + | |
| 372 | + //Forwards | |
| 373 | + MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); | |
| 374 | + | |
| 375 | + //ceq | |
| 376 | + CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); | |
| 377 | + CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); | |
| 378 | + request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); | |
| 379 | + | |
| 380 | + request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); | |
| 381 | + | |
| 382 | + Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort())); | |
| 383 | + request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); | |
| 384 | + | |
| 385 | + request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); | |
| 386 | + | |
| 387 | + return request; | |
| 388 | + } | |
| 311 | 389 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
| ... | ... | @@ -590,12 +590,12 @@ public class SIPCommander implements ISIPCommander { |
| 590 | 590 | return; |
| 591 | 591 | } |
| 592 | 592 | if (!mediaServerItem.isRtpEnable()) { |
| 593 | - // 单端口暂不支持语音对讲 | |
| 594 | - logger.info("[语音对讲] 单端口暂不支持此操作"); | |
| 593 | + // 单端口暂不支持语音喊话 | |
| 594 | + logger.info("[语音喊话] 单端口暂不支持此操作"); | |
| 595 | 595 | return; |
| 596 | 596 | } |
| 597 | 597 | |
| 598 | - logger.info("[语音对讲] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); | |
| 598 | + logger.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); | |
| 599 | 599 | HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); |
| 600 | 600 | subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { |
| 601 | 601 | if (event != null) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; |
| 2 | 2 | |
| 3 | 3 | import com.alibaba.fastjson2.JSON; |
| 4 | +import com.alibaba.fastjson2.JSONObject; | |
| 5 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 6 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 4 | 7 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 5 | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | 9 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 10 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 7 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 8 | 12 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; |
| 9 | 13 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; |
| 10 | 14 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 11 | 15 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 16 | +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | |
| 17 | +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; | |
| 18 | +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; | |
| 12 | 19 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 13 | 20 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 14 | 21 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 22 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 15 | 23 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 16 | 24 | import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; |
| 17 | 25 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 18 | 26 | import gov.nist.javax.sip.message.MessageFactoryImpl; |
| 19 | 27 | import gov.nist.javax.sip.message.SIPRequest; |
| 28 | +import gov.nist.javax.sip.message.SIPResponse; | |
| 20 | 29 | import org.slf4j.Logger; |
| 21 | 30 | import org.slf4j.LoggerFactory; |
| 22 | 31 | import org.springframework.beans.factory.annotation.Autowired; |
| ... | ... | @@ -26,6 +35,7 @@ import org.springframework.stereotype.Component; |
| 26 | 35 | import org.springframework.util.ObjectUtils; |
| 27 | 36 | |
| 28 | 37 | import javax.sip.InvalidArgumentException; |
| 38 | +import javax.sip.ResponseEvent; | |
| 29 | 39 | import javax.sip.SipException; |
| 30 | 40 | import javax.sip.header.CallIdHeader; |
| 31 | 41 | import javax.sip.header.WWWAuthenticateHeader; |
| ... | ... | @@ -61,6 +71,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 61 | 71 | @Autowired |
| 62 | 72 | private SIPSender sipSender; |
| 63 | 73 | |
| 74 | + @Autowired | |
| 75 | + private ZlmHttpHookSubscribe subscribe; | |
| 76 | + | |
| 77 | + @Autowired | |
| 78 | + private UserSetting userSetting; | |
| 79 | + | |
| 80 | + | |
| 81 | + @Autowired | |
| 82 | + private VideoStreamSessionManager streamSession; | |
| 83 | + | |
| 64 | 84 | @Override |
| 65 | 85 | public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { |
| 66 | 86 | register(parentPlatform, null, null, errorEvent, okEvent, false, true); |
| ... | ... | @@ -645,4 +665,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 645 | 665 | } |
| 646 | 666 | sipSender.transmitRequest(platform.getDeviceIp(),byeRequest); |
| 647 | 667 | } |
| 668 | + | |
| 669 | + @Override | |
| 670 | + public void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { | |
| 671 | + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(platform.getServerGBId(), channelId, callId, stream); | |
| 672 | + if (ssrcTransaction == null) { | |
| 673 | + throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channelId, callId, stream); | |
| 674 | + } | |
| 675 | + | |
| 676 | + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); | |
| 677 | + mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream()); | |
| 678 | + streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); | |
| 679 | + | |
| 680 | + Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channelId, ssrcTransaction.getSipTransactionInfo()); | |
| 681 | + sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent); | |
| 682 | + } | |
| 683 | + | |
| 684 | + @Override | |
| 685 | + public void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { | |
| 686 | + if (platform == null || deviceChannel == null) { | |
| 687 | + return; | |
| 688 | + } | |
| 689 | + String characterSet = platform.getCharacterSet(); | |
| 690 | + StringBuffer mediaStatusXml = new StringBuffer(200); | |
| 691 | + mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n"); | |
| 692 | + mediaStatusXml.append("<Notify>\r\n"); | |
| 693 | + mediaStatusXml.append("<CmdType>Broadcast</CmdType>\r\n"); | |
| 694 | + mediaStatusXml.append("<SN>" + sn + "</SN>\r\n"); | |
| 695 | + mediaStatusXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n"); | |
| 696 | + mediaStatusXml.append("<Result>" + (result?"OK":"ERROR") + "</Result>\r\n"); | |
| 697 | + mediaStatusXml.append("</Notify>\r\n"); | |
| 698 | + | |
| 699 | + CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(platform.getDeviceIp(), platform.getTransport()); | |
| 700 | + | |
| 701 | + SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(), | |
| 702 | + SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader); | |
| 703 | + | |
| 704 | + sipSender.transmitRequest(platform.getDeviceIp(),messageRequest, errorEvent, okEvent); | |
| 705 | + } | |
| 706 | + | |
| 707 | + @Override | |
| 708 | + public void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, | |
| 709 | + SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, | |
| 710 | + SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException { | |
| 711 | + String stream = ssrcInfo.getStream(); | |
| 712 | + | |
| 713 | + if (platform == null) { | |
| 714 | + return; | |
| 715 | + } | |
| 716 | + | |
| 717 | + logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); | |
| 718 | + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); | |
| 719 | + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { | |
| 720 | + if (event != null) { | |
| 721 | + event.response(mediaServerItemInUse, json); | |
| 722 | + subscribe.removeSubscribe(hookSubscribe); | |
| 723 | + } | |
| 724 | + }); | |
| 725 | + String sdpIp = mediaServerItem.getSdpIp(); | |
| 726 | + | |
| 727 | + StringBuffer content = new StringBuffer(200); | |
| 728 | + content.append("v=0\r\n"); | |
| 729 | + content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n"); | |
| 730 | + content.append("s=Play\r\n"); | |
| 731 | + content.append("c=IN IP4 " + sdpIp + "\r\n"); | |
| 732 | + content.append("t=0 0\r\n"); | |
| 733 | + | |
| 734 | + if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { | |
| 735 | + content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n"); | |
| 736 | + } else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { | |
| 737 | + content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n"); | |
| 738 | + } else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { | |
| 739 | + content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 8 96\r\n"); | |
| 740 | + } | |
| 741 | + | |
| 742 | + content.append("a=recvonly\r\n"); | |
| 743 | + content.append("a=rtpmap:8 PCMA/8000\r\n"); | |
| 744 | + content.append("a=rtpmap:96 PS/90000\r\n"); | |
| 745 | + if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { | |
| 746 | + content.append("a=setup:passive\r\n"); | |
| 747 | + content.append("a=connection:new\r\n"); | |
| 748 | + }else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) { | |
| 749 | + content.append("a=setup:active\r\n"); | |
| 750 | + content.append("a=connection:new\r\n"); | |
| 751 | + } | |
| 752 | + | |
| 753 | + content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc | |
| 754 | + CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getTransport()); | |
| 755 | + | |
| 756 | + Request request = headerProviderPlatformProvider.createInviteRequest(platform, channelId, | |
| 757 | + content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), | |
| 758 | + callIdHeader ,platform.getTransport()); | |
| 759 | + sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> { | |
| 760 | + streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream()); | |
| 761 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | |
| 762 | + errorEvent.response(e); | |
| 763 | + }), e -> { | |
| 764 | + // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 | |
| 765 | + ResponseEvent responseEvent = (ResponseEvent) e.event; | |
| 766 | + SIPResponse response = (SIPResponse) responseEvent.getResponse(); | |
| 767 | + streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(), stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play); | |
| 768 | + okEvent.response(e); | |
| 769 | + }); | |
| 770 | + } | |
| 648 | 771 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| ... | ... | @@ -16,8 +16,6 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor |
| 16 | 16 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 17 | 17 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 18 | 18 | import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; |
| 19 | -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; | |
| 20 | -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout; | |
| 21 | 19 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 22 | 20 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 23 | 21 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| ... | ... | @@ -40,8 +38,6 @@ import javax.sip.header.FromHeader; |
| 40 | 38 | import javax.sip.header.HeaderAddress; |
| 41 | 39 | import javax.sip.header.ToHeader; |
| 42 | 40 | import java.text.ParseException; |
| 43 | -import java.util.HashMap; | |
| 44 | -import java.util.Map; | |
| 45 | 41 | |
| 46 | 42 | /** |
| 47 | 43 | * SIP命令类型: ACK请求 |
| ... | ... | @@ -123,68 +119,31 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 123 | 119 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 124 | 120 | logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(), |
| 125 | 121 | sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); |
| 126 | - Map<String, Object> param = new HashMap<>(12); | |
| 127 | - param.put("vhost","__defaultVhost__"); | |
| 128 | - param.put("app",sendRtpItem.getApp()); | |
| 129 | - param.put("stream",sendRtpItem.getStreamId()); | |
| 130 | - param.put("ssrc", sendRtpItem.getSsrc()); | |
| 131 | - param.put("src_port", sendRtpItem.getLocalPort()); | |
| 132 | - param.put("pt", sendRtpItem.getPt()); | |
| 133 | - param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); | |
| 134 | - param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); | |
| 135 | - if (!sendRtpItem.isTcp()) { | |
| 136 | - // udp模式下开启rtcp保活 | |
| 137 | - param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); | |
| 138 | - } | |
| 139 | - | |
| 140 | 122 | if (mediaInfo == null) { |
| 141 | 123 | RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( |
| 142 | 124 | sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), |
| 143 | 125 | sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), |
| 144 | 126 | sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); |
| 145 | 127 | redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> { |
| 146 | - startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, param, callIdHeader); | |
| 128 | + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, callIdHeader); | |
| 147 | 129 | }); |
| 148 | - } else { | |
| 149 | - // 如果是非严格模式,需要关闭端口占用 | |
| 150 | - JSONObject startSendRtpStreamResult = null; | |
| 151 | - if (sendRtpItem.getLocalPort() != 0) { | |
| 152 | - HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId()); | |
| 153 | - hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout); | |
| 154 | - if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) { | |
| 155 | - if (sendRtpItem.isTcpActive()) { | |
| 156 | - startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param); | |
| 157 | - }else { | |
| 158 | - param.put("is_udp", is_Udp); | |
| 159 | - param.put("dst_url", sendRtpItem.getIp()); | |
| 160 | - param.put("dst_port", sendRtpItem.getPort()); | |
| 161 | - startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 162 | - } | |
| 163 | - } | |
| 164 | - }else { | |
| 165 | - if (sendRtpItem.isTcpActive()) { | |
| 166 | - startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param); | |
| 167 | - }else { | |
| 168 | - param.put("is_udp", is_Udp); | |
| 169 | - param.put("dst_url", sendRtpItem.getIp()); | |
| 170 | - param.put("dst_port", sendRtpItem.getPort()); | |
| 171 | - startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); | |
| 172 | - } | |
| 173 | - } | |
| 130 | + }else { | |
| 131 | + JSONObject startSendRtpStreamResult = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem); | |
| 174 | 132 | if (startSendRtpStreamResult != null) { |
| 175 | - startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader); | |
| 133 | + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, callIdHeader); | |
| 176 | 134 | } |
| 177 | 135 | } |
| 178 | 136 | } |
| 137 | + | |
| 179 | 138 | private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, |
| 180 | - JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) { | |
| 139 | + JSONObject jsonObject, CallIdHeader callIdHeader) { | |
| 181 | 140 | if (jsonObject == null) { |
| 182 | 141 | logger.error("RTP推流失败: 请检查ZLM服务"); |
| 183 | 142 | } else if (jsonObject.getInteger("code") == 0) { |
| 184 | 143 | logger.info("调用ZLM推流接口, 结果: {}", jsonObject); |
| 185 | - logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); | |
| 144 | + logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getIp(), sendRtpItem.getPort()); | |
| 186 | 145 | } else { |
| 187 | - logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param)); | |
| 146 | + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(sendRtpItem)); | |
| 188 | 147 | if (sendRtpItem.isOnlyAudio()) { |
| 189 | 148 | Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); |
| 190 | 149 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); |
| ... | ... | @@ -193,7 +152,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 193 | 152 | cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); |
| 194 | 153 | } catch (SipException | ParseException | InvalidArgumentException | |
| 195 | 154 | SsrcTransactionNotFoundException e) { |
| 196 | - logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage()); | |
| 155 | + logger.error("[命令发送失败] 停止语音喊话: {}", e.getMessage()); | |
| 197 | 156 | } |
| 198 | 157 | } |
| 199 | 158 | }else { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -201,7 +201,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 201 | 201 | |
| 202 | 202 | MediaServerItem mediaServerItem = null; |
| 203 | 203 | StreamPushItem streamPushItem = null; |
| 204 | - StreamProxyItem proxyByAppAndStream =null; | |
| 204 | + StreamProxyItem proxyByAppAndStream = null; | |
| 205 | 205 | // 不是通道可能是直播流 |
| 206 | 206 | if (channel != null && gbStream == null) { |
| 207 | 207 | // 通道存在,发100,TRYING |
| ... | ... | @@ -1001,7 +1001,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 1001 | 1001 | String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); |
| 1002 | 1002 | |
| 1003 | 1003 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); |
| 1004 | - sendRtpItem.setPlayType(InviteStreamType.TALK); | |
| 1004 | + sendRtpItem.setPlayType(InviteStreamType.BROADCAST); | |
| 1005 | 1005 | sendRtpItem.setCallId(callIdHeader.getCallId()); |
| 1006 | 1006 | sendRtpItem.setPlatformId(requesterId); |
| 1007 | 1007 | sendRtpItem.setStatus(1); |
| ... | ... | @@ -1013,6 +1013,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 1013 | 1013 | sendRtpItem.setOnlyAudio(true); |
| 1014 | 1014 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 1015 | 1015 | |
| 1016 | + | |
| 1016 | 1017 | Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream); |
| 1017 | 1018 | if (streamReady) { |
| 1018 | 1019 | sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, ssrc); |
| ... | ... | @@ -1084,7 +1085,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 1084 | 1085 | audioBroadcastManager.update(audioBroadcastCatch); |
| 1085 | 1086 | |
| 1086 | 1087 | } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { |
| 1087 | - logger.error("[命令发送失败] 语音对讲 回复200OK(SDP): {}", e.getMessage()); | |
| 1088 | + logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage()); | |
| 1088 | 1089 | } |
| 1089 | 1090 | return sipResponse; |
| 1090 | 1091 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson2.JSONObject; | |
| 4 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 5 | +import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 6 | +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | |
| 8 | +import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; | |
| 9 | +import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; | |
| 10 | +import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler; | |
| 11 | +import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | |
| 12 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 13 | +import com.genersoft.iot.vmp.service.IDeviceService; | |
| 14 | +import com.genersoft.iot.vmp.service.IMediaServerService; | |
| 15 | +import com.genersoft.iot.vmp.service.IPlatformService; | |
| 16 | +import com.genersoft.iot.vmp.service.IPlayService; | |
| 17 | +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 18 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 19 | +import gov.nist.javax.sip.message.SIPRequest; | |
| 20 | +import org.dom4j.Element; | |
| 21 | +import org.slf4j.Logger; | |
| 22 | +import org.slf4j.LoggerFactory; | |
| 23 | +import org.springframework.beans.factory.InitializingBean; | |
| 24 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 25 | +import org.springframework.stereotype.Component; | |
| 26 | + | |
| 27 | +import javax.sip.InvalidArgumentException; | |
| 28 | +import javax.sip.RequestEvent; | |
| 29 | +import javax.sip.SipException; | |
| 30 | +import javax.sip.message.Response; | |
| 31 | +import java.text.ParseException; | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * 状态信息(心跳)报送 | |
| 35 | + */ | |
| 36 | +@Component | |
| 37 | +public class BroadcastNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { | |
| 38 | + | |
| 39 | + private Logger logger = LoggerFactory.getLogger(BroadcastNotifyMessageHandler.class); | |
| 40 | + private final static String cmdType = "Broadcast"; | |
| 41 | + | |
| 42 | + @Autowired | |
| 43 | + private NotifyMessageHandler notifyMessageHandler; | |
| 44 | + | |
| 45 | + @Autowired | |
| 46 | + private IVideoManagerStorage storage; | |
| 47 | + | |
| 48 | + @Autowired | |
| 49 | + private ISIPCommanderForPlatform commanderForPlatform; | |
| 50 | + | |
| 51 | + @Autowired | |
| 52 | + private IMediaServerService mediaServerService; | |
| 53 | + | |
| 54 | + @Autowired | |
| 55 | + private IPlayService playService; | |
| 56 | + | |
| 57 | + @Autowired | |
| 58 | + private IDeviceService deviceService; | |
| 59 | + | |
| 60 | + @Autowired | |
| 61 | + private IPlatformService platformService; | |
| 62 | + | |
| 63 | + @Autowired | |
| 64 | + private AudioBroadcastManager audioBroadcastManager; | |
| 65 | + | |
| 66 | + @Autowired | |
| 67 | + private ZLMRTPServerFactory zlmrtpServerFactory; | |
| 68 | + | |
| 69 | + @Autowired | |
| 70 | + private IRedisCatchStorage redisCatchStorage; | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public void afterPropertiesSet() throws Exception { | |
| 74 | + notifyMessageHandler.addHandler(cmdType, this); | |
| 75 | + } | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + public void handForDevice(RequestEvent evt, Device device, Element element) { | |
| 79 | + | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Override | |
| 83 | + public void handForPlatform(RequestEvent evt, ParentPlatform platform, Element rootElement) { | |
| 84 | + // 来自上级平台的语音喊话请求 | |
| 85 | + SIPRequest request = (SIPRequest) evt.getRequest(); | |
| 86 | + try { | |
| 87 | + Element snElement = rootElement.element("SN"); | |
| 88 | + if (snElement == null) { | |
| 89 | + responseAck(request, Response.BAD_REQUEST, "sn must not null"); | |
| 90 | + return; | |
| 91 | + } | |
| 92 | + String sn = snElement.getText(); | |
| 93 | + Element targetIDElement = rootElement.element("TargetID"); | |
| 94 | + if (targetIDElement == null) { | |
| 95 | + responseAck(request, Response.BAD_REQUEST, "TargetID must not null"); | |
| 96 | + return; | |
| 97 | + } | |
| 98 | + String targetId = targetIDElement.getText(); | |
| 99 | + | |
| 100 | + | |
| 101 | + logger.info("[国标级联 语音喊话] platform: {}, channel: {}", platform.getServerGBId(), targetId); | |
| 102 | + | |
| 103 | + DeviceChannel deviceChannel = storage.queryChannelInParentPlatform(platform.getServerGBId(), targetId); | |
| 104 | + if (deviceChannel == null) { | |
| 105 | + responseAck(request, Response.NOT_FOUND, "TargetID not found"); | |
| 106 | + return; | |
| 107 | + } | |
| 108 | + // 向下级发送语音的喊话请求 | |
| 109 | + Device device = deviceService.getDevice(deviceChannel.getDeviceId()); | |
| 110 | + if (device == null) { | |
| 111 | + responseAck(request, Response.NOT_FOUND, "device not found"); | |
| 112 | + return; | |
| 113 | + } | |
| 114 | + responseAck(request, Response.OK); | |
| 115 | + | |
| 116 | + // 查看语音通道是否已经建立并且已经在使用 | |
| 117 | + if (playService.audioBroadcastInUse(device, targetId)) { | |
| 118 | + commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, false,null, null); | |
| 119 | + return; | |
| 120 | + } | |
| 121 | + | |
| 122 | + MediaServerItem mediaServerForMinimumLoad = mediaServerService.getMediaServerForMinimumLoad(); | |
| 123 | + commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, true, eventResult->{ | |
| 124 | + logger.info("[国标级联] 语音喊话 回复失败 platform: {}, 错误:{}/{}", platform.getServerGBId(), eventResult.statusCode, eventResult.msg); | |
| 125 | + }, eventResult->{ | |
| 126 | + // 消息发送成功, 向上级发送invite,获取推流 | |
| 127 | + try { | |
| 128 | + platformService.broadcastInvite(platform, deviceChannel.getChannelId(), mediaServerForMinimumLoad, (mediaServerItem, response)->{ | |
| 129 | + // 上级平台推流成功 | |
| 130 | + String app = response.getString("app"); | |
| 131 | + String stream = response.getString("stream"); | |
| 132 | + AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), targetId); | |
| 133 | + if (broadcastCatch != null ) { | |
| 134 | + if (playService.audioBroadcastInUse(device, targetId)) { | |
| 135 | + logger.info("[国标级联] 语音喊话 设备正正在使用中 platform: {}, channel: {}", | |
| 136 | + platform.getServerGBId(), deviceChannel.getChannelId()); | |
| 137 | + // 查看语音通道已经建立且已经占用 回复BYE | |
| 138 | + try { | |
| 139 | + platformService.stopBroadcast(platform, deviceChannel.getChannelId(), stream); | |
| 140 | + } catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException | | |
| 141 | + SipException e) { | |
| 142 | + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}, channel: {}", platform.getServerGBId(), deviceChannel.getChannelId()); | |
| 143 | + } | |
| 144 | + }else { | |
| 145 | + // 查看语音通道已经建立但是未占用 | |
| 146 | + broadcastCatch.setApp(app); | |
| 147 | + broadcastCatch.setStream(stream); | |
| 148 | + broadcastCatch.setMediaServerItem(mediaServerItem); | |
| 149 | + audioBroadcastManager.update(broadcastCatch); | |
| 150 | + // 推流到设备 | |
| 151 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, targetId, stream, null); | |
| 152 | + if (sendRtpItem == null) { | |
| 153 | + logger.warn("[国标级联] 语音喊话 异常,未找到发流信息, channelId: {}, stream: {}", targetId, stream); | |
| 154 | + logger.info("[国标级联] 语音喊话 重新开始,channelId: {}, stream: {}", targetId, stream); | |
| 155 | + try { | |
| 156 | + playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> { | |
| 157 | + logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId); | |
| 158 | + }); | |
| 159 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 160 | + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId()); | |
| 161 | + } | |
| 162 | + }else { | |
| 163 | + // 发流 | |
| 164 | + JSONObject jsonObject = zlmrtpServerFactory.startSendRtp(mediaServerItem, sendRtpItem); | |
| 165 | + if (jsonObject != null && jsonObject.getInteger("code") == 0 ) { | |
| 166 | + logger.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), targetId); | |
| 167 | + }else { | |
| 168 | + logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject); | |
| 169 | + } | |
| 170 | + } | |
| 171 | + } | |
| 172 | + }else { | |
| 173 | + try { | |
| 174 | + playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> { | |
| 175 | + logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId); | |
| 176 | + }); | |
| 177 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 178 | + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId()); | |
| 179 | + } | |
| 180 | + } | |
| 181 | + | |
| 182 | + }, eventResultForBroadcastInvite -> { | |
| 183 | + // 收到错误 | |
| 184 | + logger.info("[国标级联-语音喊话] 与下级通道建立失败 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(), | |
| 185 | + targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg); | |
| 186 | + }, (code, msg)->{ | |
| 187 | + // 超时 | |
| 188 | + logger.info("[国标级联-语音喊话] 与下级通道建立超时 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(), | |
| 189 | + targetId, code, msg); | |
| 190 | + }); | |
| 191 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 192 | + logger.info("[消息发送失败] 国标级联 语音喊话 invite消息 platform: {}", platform.getServerGBId()); | |
| 193 | + } | |
| 194 | + }); | |
| 195 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 196 | + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId()); | |
| 197 | + } | |
| 198 | + | |
| 199 | + } | |
| 200 | +} | ... | ... |
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.alibaba.fastjson2.JSONObject; | |
| 4 | 3 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; |
| 5 | 4 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus; |
| 6 | 5 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 7 | 6 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 8 | 7 | import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; |
| 9 | 8 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| 10 | -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | |
| 11 | 9 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 12 | 10 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; |
| 13 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; |
| 14 | -import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; | |
| 15 | 12 | import gov.nist.javax.sip.message.SIPRequest; |
| 16 | 13 | import org.dom4j.Element; |
| 17 | 14 | import org.slf4j.Logger; |
| ... | ... | @@ -52,26 +49,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i |
| 52 | 49 | public void handForDevice(RequestEvent evt, Device device, Element rootElement) { |
| 53 | 50 | try { |
| 54 | 51 | String channelId = getText(rootElement, "DeviceID"); |
| 55 | - String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId; | |
| 56 | - | |
| 57 | - // 此处是对本平台发出Broadcast指令的应答 | |
| 58 | - JSONObject json = new JSONObject(); | |
| 59 | - XmlUtil.node2Json(rootElement, json); | |
| 60 | - if (logger.isDebugEnabled()) { | |
| 61 | - logger.debug(json.toJSONString()); | |
| 62 | - } | |
| 63 | - RequestMessage msg = new RequestMessage(); | |
| 64 | - msg.setKey(key); | |
| 65 | - msg.setData(json); | |
| 66 | - deferredResultHolder.invokeAllResult(msg); | |
| 67 | - | |
| 68 | - | |
| 69 | 52 | if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { |
| 70 | 53 | // 回复410 |
| 71 | 54 | responseAck((SIPRequest) evt.getRequest(), Response.GONE); |
| 72 | 55 | return; |
| 73 | 56 | } |
| 74 | - logger.info("收到语音广播的回复:{}/{}", device.getDeviceId(), channelId ); | |
| 57 | + String result = getText(rootElement, "Result"); | |
| 58 | + logger.info("收到语音广播的回复 {}:{}/{}", result, device.getDeviceId(), channelId ); | |
| 75 | 59 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId); |
| 76 | 60 | audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); |
| 77 | 61 | audioBroadcastManager.update(audioBroadcastCatch); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; |
| 2 | 2 | |
| 3 | -import com.genersoft.iot.vmp.conf.SipConfig; | |
| 4 | 3 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 5 | -import com.genersoft.iot.vmp.gb28181.bean.Device; | |
| 6 | -import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; | |
| 7 | -import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 8 | 4 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| 9 | 5 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 10 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | |
| 11 | 6 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; |
| 12 | 7 | import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; |
| 13 | -import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | |
| 14 | -import com.genersoft.iot.vmp.service.IDeviceService; | |
| 15 | -import com.genersoft.iot.vmp.utils.GitUtil; | |
| 16 | 8 | import gov.nist.javax.sip.ResponseEventExt; |
| 17 | -import gov.nist.javax.sip.SipProviderImpl; | |
| 18 | 9 | import gov.nist.javax.sip.message.SIPResponse; |
| 19 | -import gov.nist.javax.sip.stack.SIPClientTransaction; | |
| 20 | -import gov.nist.javax.sip.stack.SIPDialog; | |
| 21 | -import gov.nist.javax.sip.stack.SIPTransaction; | |
| 22 | -import gov.nist.javax.sip.stack.SIPTransactionImpl; | |
| 23 | 10 | import org.slf4j.Logger; |
| 24 | 11 | import org.slf4j.LoggerFactory; |
| 25 | 12 | import org.springframework.beans.factory.annotation.Autowired; |
| 26 | -import org.springframework.beans.factory.annotation.Qualifier; | |
| 27 | 13 | import org.springframework.stereotype.Component; |
| 28 | 14 | |
| 29 | 15 | import javax.sdp.SdpFactory; |
| 30 | 16 | import javax.sdp.SdpParseException; |
| 31 | 17 | import javax.sdp.SessionDescription; |
| 32 | -import javax.sip.*; | |
| 33 | -import javax.sip.address.Address; | |
| 18 | +import javax.sip.InvalidArgumentException; | |
| 19 | +import javax.sip.ResponseEvent; | |
| 20 | +import javax.sip.SipException; | |
| 34 | 21 | import javax.sip.address.SipURI; |
| 35 | -import javax.sip.header.CSeqHeader; | |
| 36 | -import javax.sip.header.UserAgentHeader; | |
| 37 | 22 | import javax.sip.message.Request; |
| 38 | 23 | import javax.sip.message.Response; |
| 39 | 24 | import java.text.ParseException; |
| ... | ... | @@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract { |
| 104 | 89 | } else { |
| 105 | 90 | sdp = SdpFactory.getInstance().createSessionDescription(contentString); |
| 106 | 91 | } |
| 92 | + // 查看是否是来自设备的,此是回复 | |
| 107 | 93 | |
| 108 | 94 | SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); |
| 109 | 95 | Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response); | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -287,15 +287,19 @@ public class ZLMHttpHookListener { |
| 287 | 287 | logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); |
| 288 | 288 | } |
| 289 | 289 | |
| 290 | + JSONObject ret = new JSONObject(); | |
| 291 | + ret.put("code", 0); | |
| 292 | + ret.put("msg", "success"); | |
| 290 | 293 | |
| 291 | 294 | JSONObject json = (JSONObject) JSON.toJSON(param); |
| 295 | + MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | |
| 296 | + if (mediaInfo == null) { | |
| 297 | + return ret; | |
| 298 | + } | |
| 292 | 299 | taskExecutor.execute(()-> { |
| 293 | 300 | ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); |
| 294 | - if (subscribe != null) { | |
| 295 | - MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | |
| 296 | - if (mediaInfo != null) { | |
| 297 | - subscribe.response(mediaInfo, json); | |
| 298 | - } | |
| 301 | + if (subscribe != null ) { | |
| 302 | + subscribe.response(mediaInfo, json); | |
| 299 | 303 | } |
| 300 | 304 | // 流消失移除redis play |
| 301 | 305 | List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); |
| ... | ... | @@ -343,7 +347,7 @@ public class ZLMHttpHookListener { |
| 343 | 347 | } |
| 344 | 348 | } |
| 345 | 349 | }else if ("broadcast".equals(param.getApp())){ |
| 346 | - // 语音对讲推流 stream需要满足格式deviceId_channelId | |
| 350 | + // 语音喊话推流 stream需要满足格式deviceId_channelId | |
| 347 | 351 | if (param.isRegist() && param.getStream().indexOf("_") > 0) { |
| 348 | 352 | String[] streamArray = param.getStream().split("_"); |
| 349 | 353 | if (streamArray.length == 2) { |
| ... | ... | @@ -359,53 +363,38 @@ public class ZLMHttpHookListener { |
| 359 | 363 | if (sendRtpItem == null) { |
| 360 | 364 | // TODO 可能数据错误,重新开启语音通道 |
| 361 | 365 | }else { |
| 362 | - String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; | |
| 363 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 364 | - logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); | |
| 365 | - Map<String, Object> sendParam = new HashMap<>(12); | |
| 366 | - sendParam.put("vhost","__defaultVhost__"); | |
| 367 | - sendParam.put("app",sendRtpItem.getApp()); | |
| 368 | - sendParam.put("stream",sendRtpItem.getStreamId()); | |
| 369 | - sendParam.put("ssrc", sendRtpItem.getSsrc()); | |
| 370 | - sendParam.put("src_port", sendRtpItem.getLocalPort()); | |
| 371 | - sendParam.put("pt", sendRtpItem.getPt()); | |
| 372 | - sendParam.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); | |
| 373 | - sendParam.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); | |
| 374 | - | |
| 375 | - JSONObject jsonObject; | |
| 376 | - if (sendRtpItem.isTcpActive()) { | |
| 377 | - jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, sendParam); | |
| 378 | - } else { | |
| 379 | - sendParam.put("is_udp", is_Udp); | |
| 380 | - sendParam.put("dst_url", sendRtpItem.getIp()); | |
| 381 | - sendParam.put("dst_port", sendRtpItem.getPort()); | |
| 382 | - jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, sendParam); | |
| 383 | - } | |
| 384 | - if (jsonObject != null && jsonObject.getInteger("code") == 0) { | |
| 385 | - logger.info("[语音对讲] 自动推流成功, device: {}, channel: {}", deviceId, channelId); | |
| 366 | + JSONObject jsonObject = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem); | |
| 367 | + if (jsonObject != null && jsonObject.getInteger("code") == 0 ) { | |
| 368 | + logger.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), channelId); | |
| 386 | 369 | }else { |
| 387 | - logger.info("[语音对讲] 推流失败, 结果: {}", jsonObject); | |
| 370 | + logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject); | |
| 388 | 371 | } |
| 389 | 372 | |
| 390 | 373 | } |
| 391 | 374 | }else { |
| 392 | - // 开启语音对讲通道 | |
| 375 | + // 开启语音喊话通道 | |
| 393 | 376 | try { |
| 394 | - playService.audioBroadcastCmd(device, channelId, 60, (msg)->{ | |
| 395 | - logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); | |
| 377 | + playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(),60, false, (msg)->{ | |
| 378 | + logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", deviceId, channelId); | |
| 396 | 379 | }); |
| 397 | 380 | } catch (InvalidArgumentException | ParseException | SipException e) { |
| 398 | - logger.error("[命令发送失败] 语音对讲: {}", e.getMessage()); | |
| 381 | + logger.error("[命令发送失败] 语音喊话: {}", e.getMessage()); | |
| 399 | 382 | } |
| 400 | 383 | } |
| 401 | 384 | |
| 385 | + }else { | |
| 386 | + logger.info("[语音喊话] 推流指向的·通道{}未找到", channelId); | |
| 402 | 387 | } |
| 388 | + }else { | |
| 389 | + logger.info("[语音喊话] 推流指向的·设备{}未找到", deviceId); | |
| 403 | 390 | } |
| 391 | + }else { | |
| 392 | + logger.info("[语音喊话] 推流格式有误, 格式为: broadcast/设备编号_通道编号 "); | |
| 404 | 393 | } |
| 405 | 394 | } |
| 406 | 395 | |
| 407 | 396 | }else if ("talk".equals(param.getApp())){ |
| 408 | - // 语音对讲推流 stream需要满足格式deviceId_channelId | |
| 397 | + // 语音喊话推流 stream需要满足格式deviceId_channelId | |
| 409 | 398 | if (param.isRegist() && param.getStream().indexOf("_") > 0) { |
| 410 | 399 | String[] streamArray = param.getStream().split("_"); |
| 411 | 400 | if (streamArray.length == 2) { |
| ... | ... | @@ -421,33 +410,11 @@ public class ZLMHttpHookListener { |
| 421 | 410 | if (sendRtpItem == null) { |
| 422 | 411 | // TODO 可能数据错误,重新开启语音通道 |
| 423 | 412 | }else { |
| 424 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 425 | 413 | logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); |
| 426 | - Map<String, Object> sendParam = new HashMap<>(12); | |
| 427 | - sendParam.put("vhost","__defaultVhost__"); | |
| 428 | - sendParam.put("app",sendRtpItem.getApp()); | |
| 429 | - sendParam.put("stream",sendRtpItem.getStreamId()); | |
| 430 | - sendParam.put("ssrc", sendRtpItem.getSsrc()); | |
| 431 | - sendParam.put("src_port", sendRtpItem.getLocalPort()); | |
| 432 | - sendParam.put("pt", sendRtpItem.getPt()); | |
| 433 | - sendParam.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); | |
| 434 | - sendParam.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); | |
| 435 | - | |
| 436 | - JSONObject jsonObject; | |
| 437 | - if (sendRtpItem.isTcpActive()) { | |
| 438 | - jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, sendParam); | |
| 439 | - } else { | |
| 440 | - sendParam.put("is_udp", sendRtpItem.isTcp() ? "0" : "1"); | |
| 441 | - sendParam.put("dst_url", sendRtpItem.getIp()); | |
| 442 | - sendParam.put("dst_port", sendRtpItem.getPort()); | |
| 443 | - jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, sendParam); | |
| 444 | - } | |
| 445 | - if (jsonObject != null && jsonObject.getInteger("code") == 0) { | |
| 446 | - logger.info("[语音对讲] 自动推流成功, device: {}, channel: {}", deviceId, channelId); | |
| 447 | - } | |
| 414 | + zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem); | |
| 448 | 415 | } |
| 449 | 416 | }else { |
| 450 | - // 开启语音对讲通道 | |
| 417 | + // 开启语音喊话通道 | |
| 451 | 418 | MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); |
| 452 | 419 | playService.talk(mediaServerItem, device, channelId, (mediaServer, jsonObject)->{ |
| 453 | 420 | System.out.println("开始推流"); |
| ... | ... | @@ -549,9 +516,7 @@ public class ZLMHttpHookListener { |
| 549 | 516 | } |
| 550 | 517 | } |
| 551 | 518 | |
| 552 | - JSONObject ret = new JSONObject(); | |
| 553 | - ret.put("code", 0); | |
| 554 | - ret.put("msg", "success"); | |
| 519 | + | |
| 555 | 520 | return ret; |
| 556 | 521 | } |
| 557 | 522 | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
| ... | ... | @@ -348,4 +348,52 @@ public class ZLMRTPServerFactory { |
| 348 | 348 | return result; |
| 349 | 349 | } |
| 350 | 350 | |
| 351 | + public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) { | |
| 352 | + String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; | |
| 353 | + logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); | |
| 354 | + Map<String, Object> param = new HashMap<>(12); | |
| 355 | + param.put("vhost","__defaultVhost__"); | |
| 356 | + param.put("app",sendRtpItem.getApp()); | |
| 357 | + param.put("stream",sendRtpItem.getStreamId()); | |
| 358 | + param.put("ssrc", sendRtpItem.getSsrc()); | |
| 359 | + param.put("src_port", sendRtpItem.getLocalPort()); | |
| 360 | + param.put("pt", sendRtpItem.getPt()); | |
| 361 | + param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); | |
| 362 | + param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); | |
| 363 | + if (!sendRtpItem.isTcp()) { | |
| 364 | + // udp模式下开启rtcp保活 | |
| 365 | + param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); | |
| 366 | + } | |
| 367 | + | |
| 368 | + if (mediaInfo == null) { | |
| 369 | + return null; | |
| 370 | + } | |
| 371 | + // 如果是非严格模式,需要关闭端口占用 | |
| 372 | + JSONObject startSendRtpStreamResult = null; | |
| 373 | + if (sendRtpItem.getLocalPort() != 0) { | |
| 374 | + HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId()); | |
| 375 | + hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout); | |
| 376 | + if (releasePort(mediaInfo, sendRtpItem.getSsrc())) { | |
| 377 | + if (sendRtpItem.isTcpActive()) { | |
| 378 | + startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param); | |
| 379 | + System.out.println(JSON.toJSON(param)); | |
| 380 | + }else { | |
| 381 | + param.put("is_udp", is_Udp); | |
| 382 | + param.put("dst_url", sendRtpItem.getIp()); | |
| 383 | + param.put("dst_port", sendRtpItem.getPort()); | |
| 384 | + startSendRtpStreamResult = startSendRtpStream(mediaInfo, param); | |
| 385 | + } | |
| 386 | + } | |
| 387 | + }else { | |
| 388 | + if (sendRtpItem.isTcpActive()) { | |
| 389 | + startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param); | |
| 390 | + }else { | |
| 391 | + param.put("is_udp", is_Udp); | |
| 392 | + param.put("dst_url", sendRtpItem.getIp()); | |
| 393 | + param.put("dst_port", sendRtpItem.getPort()); | |
| 394 | + startSendRtpStreamResult = startSendRtpStream(mediaInfo, param); | |
| 395 | + } | |
| 396 | + } | |
| 397 | + return startSendRtpStreamResult; | |
| 398 | + } | |
| 351 | 399 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
| 1 | 1 | package com.genersoft.iot.vmp.service; |
| 2 | 2 | |
| 3 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 3 | 4 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 5 | +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | |
| 6 | +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | |
| 7 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 8 | +import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; | |
| 4 | 9 | import com.github.pagehelper.PageInfo; |
| 5 | 10 | |
| 11 | +import javax.sip.InvalidArgumentException; | |
| 12 | +import javax.sip.SipException; | |
| 13 | +import java.text.ParseException; | |
| 14 | + | |
| 6 | 15 | /** |
| 7 | 16 | * 国标平台的业务类 |
| 8 | 17 | * @author lin |
| ... | ... | @@ -48,4 +57,23 @@ public interface IPlatformService { |
| 48 | 57 | * @param platformId 平台 |
| 49 | 58 | */ |
| 50 | 59 | void sendNotifyMobilePosition(String platformId); |
| 60 | + | |
| 61 | + /** | |
| 62 | + * 向上级发送语音喊话的消息 | |
| 63 | + * @param platform 平台 | |
| 64 | + * @param channelId 通道 | |
| 65 | + * @param hookEvent hook事件 | |
| 66 | + * @param errorEvent 信令错误事件 | |
| 67 | + * @param timeoutCallback 超时事件 | |
| 68 | + */ | |
| 69 | + void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent, | |
| 70 | + SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException; | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * 语音喊话回复BYE | |
| 74 | + * @param platform 平台 | |
| 75 | + * @param channelId 通道 | |
| 76 | + * @param stream 流信息 | |
| 77 | + */ | |
| 78 | + void stopBroadcast(ParentPlatform platform, String channelId, String stream )throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException; | |
| 51 | 79 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
| ... | ... | @@ -53,10 +53,13 @@ public interface IPlayService { |
| 53 | 53 | |
| 54 | 54 | void zlmServerOnline(String mediaServerId); |
| 55 | 55 | |
| 56 | - AudioBroadcastResult audioBroadcast(Device device, String channelId); | |
| 57 | - void stopAudioBroadcast(String deviceId, String channelId); | |
| 56 | + AudioBroadcastResult audioBroadcastInfo(Device device, String channelId); | |
| 57 | + | |
| 58 | + boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException; | |
| 58 | 59 | |
| 59 | - void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException; | |
| 60 | + boolean audioBroadcastInUse(Device device, String channelId); | |
| 61 | + | |
| 62 | + void stopAudioBroadcast(String deviceId, String channelId); | |
| 60 | 63 | |
| 61 | 64 | void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| 62 | 65 | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
| 1 | 1 | package com.genersoft.iot.vmp.service.impl; |
| 2 | 2 | |
| 3 | +import com.alibaba.fastjson2.JSONObject; | |
| 4 | +import com.genersoft.iot.vmp.common.StreamInfo; | |
| 3 | 5 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 4 | 6 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 7 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | |
| 5 | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | 9 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 10 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | |
| 7 | 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; |
| 8 | 12 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 13 | +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | |
| 14 | +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; | |
| 15 | +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; | |
| 9 | 16 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 10 | 17 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 11 | 18 | import com.genersoft.iot.vmp.service.IPlatformService; |
| 19 | +import com.genersoft.iot.vmp.service.IPlayService; | |
| 12 | 20 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 21 | +import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; | |
| 22 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 13 | 23 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 14 | 24 | import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; |
| 15 | 25 | import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper; |
| ... | ... | @@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired; |
| 21 | 31 | import org.springframework.stereotype.Service; |
| 22 | 32 | |
| 23 | 33 | import javax.sip.InvalidArgumentException; |
| 34 | +import javax.sip.ResponseEvent; | |
| 24 | 35 | import javax.sip.SipException; |
| 25 | 36 | import java.text.ParseException; |
| 26 | 37 | import java.util.HashMap; |
| 27 | 38 | import java.util.List; |
| 28 | 39 | import java.util.Map; |
| 40 | +import java.util.UUID; | |
| 29 | 41 | |
| 30 | 42 | /** |
| 31 | 43 | * @author lin |
| ... | ... | @@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService { |
| 65 | 77 | @Autowired |
| 66 | 78 | private UserSetting userSetting; |
| 67 | 79 | |
| 80 | + @Autowired | |
| 81 | + private ZlmHttpHookSubscribe subscribe; | |
| 82 | + | |
| 83 | + @Autowired | |
| 84 | + private VideoStreamSessionManager streamSession; | |
| 85 | + | |
| 86 | + | |
| 87 | + @Autowired | |
| 88 | + private IPlayService playService; | |
| 89 | + | |
| 68 | 90 | |
| 69 | 91 | |
| 70 | 92 | @Override |
| ... | ... | @@ -295,4 +317,137 @@ public class PlatformServiceImpl implements IPlatformService { |
| 295 | 317 | } |
| 296 | 318 | } |
| 297 | 319 | } |
| 320 | + | |
| 321 | + @Override | |
| 322 | + public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent, | |
| 323 | + SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException { | |
| 324 | + | |
| 325 | + if (mediaServerItem == null) { | |
| 326 | + logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId()); | |
| 327 | + return; | |
| 328 | + } | |
| 329 | + StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId); | |
| 330 | + if (streamInfo != null) { | |
| 331 | + // 如果zlm不存在这个流,则删除数据即可 | |
| 332 | + MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(streamInfo.getMediaServerId()); | |
| 333 | + if (mediaServerItemForStreamInfo != null) { | |
| 334 | + Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemForStreamInfo, streamInfo.getApp(), streamInfo.getStream()); | |
| 335 | + if (!ready) { | |
| 336 | + // 错误存在于redis中的数据 | |
| 337 | + redisCatchStorage.stopPlay(streamInfo); | |
| 338 | + }else { | |
| 339 | + // 流确实尚在推流,直接回调结果 | |
| 340 | + JSONObject json = new JSONObject(); | |
| 341 | + json.put("app", streamInfo.getApp()); | |
| 342 | + json.put("stream", streamInfo.getStream()); | |
| 343 | + hookEvent.response(mediaServerItemForStreamInfo, json); | |
| 344 | + return; | |
| 345 | + } | |
| 346 | + } | |
| 347 | + } | |
| 348 | + | |
| 349 | + String streamId = null; | |
| 350 | + if (mediaServerItem.isRtpEnable()) { | |
| 351 | + streamId = String.format("%s_%s", platform.getServerGBId(), channelId); | |
| 352 | + } | |
| 353 | + // 默认不进行SSRC校验, TODO 后续可改为配置 | |
| 354 | + boolean ssrcCheck = false; | |
| 355 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrcCheck, false); | |
| 356 | + if (ssrcInfo == null || ssrcInfo.getPort() < 0) { | |
| 357 | + logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId); | |
| 358 | + errorEvent.response(new SipSubscribe.EventResult(-1, "端口监听失败")); | |
| 359 | + return; | |
| 360 | + } | |
| 361 | + logger.info("[国标级联] 发起语音喊话 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", | |
| 362 | + platform.getServerGBId(), channelId, ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck); | |
| 363 | + | |
| 364 | + String timeOutTaskKey = UUID.randomUUID().toString(); | |
| 365 | + dynamicTask.startDelay(timeOutTaskKey, () -> { | |
| 366 | + // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 | |
| 367 | + if (redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId) == null) { | |
| 368 | + logger.info("[国标级联] 发起语音喊话 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", platform.getServerGBId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc()); | |
| 369 | + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 | |
| 370 | + try { | |
| 371 | + commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null); | |
| 372 | + } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { | |
| 373 | + logger.error("[点播超时], 发送BYE失败 {}", e.getMessage()); | |
| 374 | + } finally { | |
| 375 | + timeoutCallback.run(1, "收流超时"); | |
| 376 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | |
| 377 | + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); | |
| 378 | + streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream()); | |
| 379 | + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); | |
| 380 | + } | |
| 381 | + } | |
| 382 | + }, userSetting.getPlayTimeout()); | |
| 383 | + commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (mediaServerItemForInvite, response)->{ | |
| 384 | + dynamicTask.stop(timeOutTaskKey); | |
| 385 | + // hook响应 | |
| 386 | + playService.onPublishHandlerForPlay(mediaServerItemForInvite, response, platform.getServerGBId(), channelId); | |
| 387 | + // 收到流 | |
| 388 | + if (hookEvent != null) { | |
| 389 | + hookEvent.response(mediaServerItem, response); | |
| 390 | + } | |
| 391 | + }, event -> { | |
| 392 | + // 收到200OK 检测ssrc是否有变化,防止上级自定义了ssrc | |
| 393 | + ResponseEvent responseEvent = (ResponseEvent) event.event; | |
| 394 | + String contentString = new String(responseEvent.getResponse().getRawContent()); | |
| 395 | + // 获取ssrc | |
| 396 | + int ssrcIndex = contentString.indexOf("y="); | |
| 397 | + // 检查是否有y字段 | |
| 398 | + if (ssrcIndex >= 0) { | |
| 399 | + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 | |
| 400 | + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 401 | + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 | |
| 402 | + if (ssrcInfo.getSsrc().equals(ssrcInResponse) || ssrcCheck) { | |
| 403 | + return; | |
| 404 | + } | |
| 405 | + logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); | |
| 406 | + if (!mediaServerItem.isRtpEnable()) { | |
| 407 | + logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); | |
| 408 | + | |
| 409 | + if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { | |
| 410 | + // ssrc 不可用 | |
| 411 | + // 释放ssrc | |
| 412 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | |
| 413 | + streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream()); | |
| 414 | + event.msg = "下级自定义了ssrc,但是此ssrc不可用"; | |
| 415 | + event.statusCode = 400; | |
| 416 | + errorEvent.response(event); | |
| 417 | + return; | |
| 418 | + } | |
| 419 | + | |
| 420 | + // 单端口模式streamId也有变化,需要重新设置监听 | |
| 421 | + if (!mediaServerItem.isRtpEnable()) { | |
| 422 | + // 添加订阅 | |
| 423 | + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); | |
| 424 | + subscribe.removeSubscribe(hookSubscribe); | |
| 425 | + hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase()); | |
| 426 | + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { | |
| 427 | + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); | |
| 428 | + dynamicTask.stop(timeOutTaskKey); | |
| 429 | + // hook响应 | |
| 430 | + playService.onPublishHandlerForPlay(mediaServerItemInUse, response, platform.getServerGBId(), channelId); | |
| 431 | + hookEvent.response(mediaServerItemInUse, response); | |
| 432 | + }); | |
| 433 | + } | |
| 434 | + // 关闭rtp server | |
| 435 | + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); | |
| 436 | + // 重新开启ssrc server | |
| 437 | + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, false, false, ssrcInfo.getPort()); | |
| 438 | + | |
| 439 | + } | |
| 440 | + } | |
| 441 | + }, eventResult -> { | |
| 442 | + // 收到错误回复 | |
| 443 | + if (errorEvent != null) { | |
| 444 | + errorEvent.response(eventResult); | |
| 445 | + } | |
| 446 | + }); | |
| 447 | + } | |
| 448 | + | |
| 449 | + @Override | |
| 450 | + public void stopBroadcast(ParentPlatform platform, String channelId, String stream) throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException { | |
| 451 | + commanderForPlatform.streamByeCmd(platform, channelId, stream, null, null); | |
| 452 | + } | |
| 298 | 453 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -987,7 +987,7 @@ public class PlayServiceImpl implements IPlayService { |
| 987 | 987 | } |
| 988 | 988 | |
| 989 | 989 | @Override |
| 990 | - public AudioBroadcastResult audioBroadcast(Device device, String channelId) { | |
| 990 | + public AudioBroadcastResult audioBroadcastInfo(Device device, String channelId) { | |
| 991 | 991 | if (device == null || channelId == null) { |
| 992 | 992 | return null; |
| 993 | 993 | } |
| ... | ... | @@ -1012,46 +1012,51 @@ public class PlayServiceImpl implements IPlayService { |
| 1012 | 1012 | } |
| 1013 | 1013 | |
| 1014 | 1014 | @Override |
| 1015 | - public void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException { | |
| 1015 | + public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException { | |
| 1016 | 1016 | if (device == null || channelId == null) { |
| 1017 | - return; | |
| 1017 | + return false; | |
| 1018 | 1018 | } |
| 1019 | 1019 | logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); |
| 1020 | 1020 | DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); |
| 1021 | 1021 | if (deviceChannel == null) { |
| 1022 | 1022 | logger.warn("开启语音广播的时候未找到通道: {}", channelId); |
| 1023 | 1023 | event.call("开启语音广播的时候未找到通道"); |
| 1024 | - return; | |
| 1024 | + return false; | |
| 1025 | 1025 | } |
| 1026 | 1026 | // 查询通道使用状态 |
| 1027 | - if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | |
| 1028 | - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); | |
| 1029 | - if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) { | |
| 1030 | - // 查询流是否存在,不存在则认为是异常状态 | |
| 1031 | - MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 1032 | - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStreamId()); | |
| 1033 | - if (streamReady) { | |
| 1034 | - logger.warn("语音广播已经开启: {}", channelId); | |
| 1035 | - event.call("语音广播已经开启"); | |
| 1036 | - return; | |
| 1037 | - } else { | |
| 1038 | - audioBroadcastManager.del(deviceChannel.getDeviceId(), channelId); | |
| 1039 | - redisCatchStorage.deleteSendRTPServer(device.getDeviceId(), channelId, sendRtpItem.getCallId(), sendRtpItem.getStreamId()); | |
| 1040 | - } | |
| 1041 | - } | |
| 1027 | + if (audioBroadcastInUse(device, channelId)) { | |
| 1028 | + return false; | |
| 1042 | 1029 | } |
| 1043 | 1030 | |
| 1044 | 1031 | // 发送通知 |
| 1045 | 1032 | cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> { |
| 1046 | 1033 | // 发送成功 |
| 1047 | - AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, AudioBroadcastCatchStatus.Ready); | |
| 1048 | - audioBroadcastManager.add(audioBroadcastCatch); | |
| 1034 | + AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform); | |
| 1035 | + audioBroadcastManager.update(audioBroadcastCatch); | |
| 1049 | 1036 | }, eventResultForError -> { |
| 1050 | 1037 | // 发送失败 |
| 1051 | 1038 | logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg); |
| 1052 | 1039 | event.call("语音广播发送失败"); |
| 1053 | 1040 | stopAudioBroadcast(device.getDeviceId(), channelId); |
| 1054 | 1041 | }); |
| 1042 | + return true; | |
| 1043 | + } | |
| 1044 | + | |
| 1045 | + @Override | |
| 1046 | + public boolean audioBroadcastInUse(Device device, String channelId) { | |
| 1047 | + if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | |
| 1048 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); | |
| 1049 | + if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) { | |
| 1050 | + // 查询流是否存在,不存在则认为是异常状态 | |
| 1051 | + MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 1052 | + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStreamId()); | |
| 1053 | + if (streamReady) { | |
| 1054 | + logger.warn("语音广播通道使用中: {}", channelId); | |
| 1055 | + return true; | |
| 1056 | + } | |
| 1057 | + } | |
| 1058 | + } | |
| 1059 | + return false; | |
| 1055 | 1060 | } |
| 1056 | 1061 | |
| 1057 | 1062 | |
| ... | ... | @@ -1075,6 +1080,9 @@ public class PlayServiceImpl implements IPlayService { |
| 1075 | 1080 | param.put("stream", sendRtpItem.getStreamId()); |
| 1076 | 1081 | zlmresTfulUtils.stopSendRtp(mediaInfo, param); |
| 1077 | 1082 | } |
| 1083 | + if (audioBroadcastCatch.isFromPlatform()) { | |
| 1084 | + // TODO 向上级发送BYE结束语音喊话 | |
| 1085 | + } | |
| 1078 | 1086 | |
| 1079 | 1087 | audioBroadcastManager.del(deviceId, channelId); |
| 1080 | 1088 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
| ... | ... | @@ -267,7 +267,7 @@ public class PlayController { |
| 267 | 267 | throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelId); |
| 268 | 268 | } |
| 269 | 269 | |
| 270 | - return playService.audioBroadcast(device, channelId); | |
| 270 | + return playService.audioBroadcastInfo(device, channelId); | |
| 271 | 271 | |
| 272 | 272 | } |
| 273 | 273 | ... | ... |
src/main/resources/all-application.yml
| ... | ... | @@ -168,7 +168,7 @@ user-settings: |
| 168 | 168 | # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认) |
| 169 | 169 | save-position-history: false |
| 170 | 170 | # 点播等待超时时间,单位:毫秒 |
| 171 | - play-timeout: 3000 | |
| 171 | + play-timeout: 18000 | |
| 172 | 172 | # 上级点播等待超时时间,单位:毫秒 |
| 173 | 173 | platform-play-timeout: 60000 |
| 174 | 174 | # 是否开启接口鉴权 |
| ... | ... | @@ -195,6 +195,8 @@ user-settings: |
| 195 | 195 | gb-send-stream-strict: false |
| 196 | 196 | # 设备上线时是否自动同步通道 |
| 197 | 197 | sync-channel-on-device-online: false |
| 198 | + # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式 | |
| 199 | + broadcast-for-platform: UDP | |
| 198 | 200 | |
| 199 | 201 | # 关闭在线文档(生产环境建议关闭) |
| 200 | 202 | springdoc: | ... | ... |
web_src/src/components/dialog/devicePlayer.vue
| ... | ... | @@ -279,7 +279,7 @@ |
| 279 | 279 | </div> |
| 280 | 280 | |
| 281 | 281 | </el-tab-pane> |
| 282 | - <el-tab-pane label="语音对讲" name="broadcast" > | |
| 282 | + <el-tab-pane label="语音喊话" name="broadcast" > | |
| 283 | 283 | <div class="trank" style="text-align: center;"> |
| 284 | 284 | <el-button @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2" circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;"/> |
| 285 | 285 | <p> |
| ... | ... | @@ -854,7 +854,7 @@ export default { |
| 854 | 854 | if (this.broadcastStatus == -1) { |
| 855 | 855 | // 默认状态, 开始 |
| 856 | 856 | this.broadcastStatus = 0 |
| 857 | - // 发起语音对讲 | |
| 857 | + // 发起语音喊话 | |
| 858 | 858 | this.$axios({ |
| 859 | 859 | method: 'get', |
| 860 | 860 | url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30" |
| ... | ... | @@ -897,7 +897,7 @@ export default { |
| 897 | 897 | let pushKey = res.data.data.pushKey; |
| 898 | 898 | // 获取推流鉴权KEY |
| 899 | 899 | url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex') |
| 900 | - console.log("开始语音对讲: " + url) | |
| 900 | + console.log("开始语音喊话: " + url) | |
| 901 | 901 | this.broadcastRtc = new ZLMRTCClient.Endpoint({ |
| 902 | 902 | debug: true, // 是否打印日志 |
| 903 | 903 | zlmsdpUrl: url, //流地址 |
| ... | ... | @@ -923,7 +923,7 @@ export default { |
| 923 | 923 | console.error('不支持webrtc',e) |
| 924 | 924 | this.$message({ |
| 925 | 925 | showClose: true, |
| 926 | - message: '不支持webrtc, 无法进行语音对讲', | |
| 926 | + message: '不支持webrtc, 无法进行语音喊话', | |
| 927 | 927 | type: 'error' |
| 928 | 928 | }); |
| 929 | 929 | this.broadcastStatus = -1; | ... | ... |