Commit 4b827f3897600e97023ded3df83a2f2551131d53
1 parent
38a85d43
级联语音对讲部分
Showing
28 changed files
with
940 additions
and
223 deletions
doc/README.md
| @@ -52,7 +52,7 @@ | @@ -52,7 +52,7 @@ | ||
| 52 | - [X] 报警订阅 | 52 | - [X] 报警订阅 |
| 53 | - [X] 目录订阅 | 53 | - [X] 目录订阅 |
| 54 | - [ ] 语音广播 | 54 | - [ ] 语音广播 |
| 55 | -- [ ] 语音对讲 | 55 | +- [ ] 语音喊话 |
| 56 | 56 | ||
| 57 | **作为下级平台** | 57 | **作为下级平台** |
| 58 | - [X] 注册 | 58 | - [X] 注册 |
| @@ -91,7 +91,7 @@ | @@ -91,7 +91,7 @@ | ||
| 91 | - [ ] 报警订阅 | 91 | - [ ] 报警订阅 |
| 92 | - [X] 目录订阅 | 92 | - [X] 目录订阅 |
| 93 | - [ ] 语音广播 | 93 | - [ ] 语音广播 |
| 94 | -- [ ] 语音对讲 | 94 | +- [ ] 语音喊话 |
| 95 | 95 | ||
| 96 | 96 | ||
| 97 | 97 |
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
| @@ -19,6 +19,7 @@ | @@ -19,6 +19,7 @@ | ||
| 19 | * [树形结构](_content/theory/channel_tree.md) | 19 | * [树形结构](_content/theory/channel_tree.md) |
| 20 | * [注册流程](_content/theory/register.md) | 20 | * [注册流程](_content/theory/register.md) |
| 21 | * [点播流程](_content/theory/play.md) | 21 | * [点播流程](_content/theory/play.md) |
| 22 | + * [级联语音喊话流程](_content/theory/broadcast_cascade.md) | ||
| 22 | * **必备技巧** | 23 | * **必备技巧** |
| 23 | * [抓包](_content/skill/tcpdump.md) | 24 | * [抓包](_content/skill/tcpdump.md) |
| 24 | 25 |
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
| @@ -47,6 +47,8 @@ public class UserSetting { | @@ -47,6 +47,8 @@ public class UserSetting { | ||
| 47 | 47 | ||
| 48 | private String thirdPartyGBIdReg = "[\\s\\S]*"; | 48 | private String thirdPartyGBIdReg = "[\\s\\S]*"; |
| 49 | 49 | ||
| 50 | + private String broadcastForPlatform = "UDP"; | ||
| 51 | + | ||
| 50 | private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); | 52 | private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); |
| 51 | 53 | ||
| 52 | public Boolean getSavePositionHistory() { | 54 | public Boolean getSavePositionHistory() { |
| @@ -196,4 +198,12 @@ public class UserSetting { | @@ -196,4 +198,12 @@ public class UserSetting { | ||
| 196 | public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) { | 198 | public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) { |
| 197 | this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline; | 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 | package com.genersoft.iot.vmp.gb28181.bean; | 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 | import gov.nist.javax.sip.message.SIPResponse; | 6 | import gov.nist.javax.sip.message.SIPResponse; |
| 5 | 7 | ||
| 6 | /** | 8 | /** |
| @@ -10,10 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse; | @@ -10,10 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse; | ||
| 10 | public class AudioBroadcastCatch { | 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 | this.deviceId = deviceId; | 25 | this.deviceId = deviceId; |
| 15 | this.channelId = channelId; | 26 | this.channelId = channelId; |
| 16 | this.status = status; | 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 | public AudioBroadcastCatch() { | 35 | public AudioBroadcastCatch() { |
| @@ -30,6 +46,26 @@ public class AudioBroadcastCatch { | @@ -30,6 +46,26 @@ public class AudioBroadcastCatch { | ||
| 30 | private String channelId; | 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 | private AudioBroadcastCatchStatus status; | 71 | private AudioBroadcastCatchStatus status; |
| @@ -39,6 +75,11 @@ public class AudioBroadcastCatch { | @@ -39,6 +75,11 @@ public class AudioBroadcastCatch { | ||
| 39 | */ | 75 | */ |
| 40 | private SipTransactionInfo sipTransactionInfo; | 76 | private SipTransactionInfo sipTransactionInfo; |
| 41 | 77 | ||
| 78 | + /** | ||
| 79 | + * 请求结果回调 | ||
| 80 | + */ | ||
| 81 | + private AudioBroadcastEvent event; | ||
| 82 | + | ||
| 42 | 83 | ||
| 43 | public String getDeviceId() { | 84 | public String getDeviceId() { |
| 44 | return deviceId; | 85 | return deviceId; |
| @@ -75,4 +116,44 @@ public class AudioBroadcastCatch { | @@ -75,4 +116,44 @@ public class AudioBroadcastCatch { | ||
| 75 | public void setSipTransactionInfoByRequset(SIPResponse response) { | 116 | public void setSipTransactionInfoByRequset(SIPResponse response) { |
| 76 | this.sipTransactionInfo = new SipTransactionInfo(response, false); | 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
| @@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; | @@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; | ||
| 2 | 2 | ||
| 3 | public enum InviteStreamType { | 3 | public enum InviteStreamType { |
| 4 | 4 | ||
| 5 | - PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,TALK | 5 | + PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK |
| 6 | 6 | ||
| 7 | 7 | ||
| 8 | } | 8 | } |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
| @@ -66,7 +66,7 @@ public class ParentPlatform { | @@ -66,7 +66,7 @@ public class ParentPlatform { | ||
| 66 | * 设备端口 | 66 | * 设备端口 |
| 67 | */ | 67 | */ |
| 68 | @Schema(description = "设备端口") | 68 | @Schema(description = "设备端口") |
| 69 | - private String devicePort; | 69 | + private int devicePort; |
| 70 | 70 | ||
| 71 | /** | 71 | /** |
| 72 | * SIP认证用户名(默认使用设备国标编号) | 72 | * SIP认证用户名(默认使用设备国标编号) |
| @@ -261,11 +261,11 @@ public class ParentPlatform { | @@ -261,11 +261,11 @@ public class ParentPlatform { | ||
| 261 | this.deviceIp = deviceIp; | 261 | this.deviceIp = deviceIp; |
| 262 | } | 262 | } |
| 263 | 263 | ||
| 264 | - public String getDevicePort() { | 264 | + public int getDevicePort() { |
| 265 | return devicePort; | 265 | return devicePort; |
| 266 | } | 266 | } |
| 267 | 267 | ||
| 268 | - public void setDevicePort(String devicePort) { | 268 | + public void setDevicePort(int devicePort) { |
| 269 | this.devicePort = devicePort; | 269 | this.devicePort = devicePort; |
| 270 | } | 270 | } |
| 271 | 271 |
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
| 1 | package com.genersoft.iot.vmp.gb28181.event; | 1 | package com.genersoft.iot.vmp.gb28181.event; |
| 2 | 2 | ||
| 3 | -import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 4 | import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent; | 3 | import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent; |
| 5 | import gov.nist.javax.sip.message.SIPRequest; | 4 | import gov.nist.javax.sip.message.SIPRequest; |
| 6 | import org.slf4j.Logger; | 5 | import org.slf4j.Logger; |
| @@ -87,6 +86,11 @@ public class SipSubscribe { | @@ -87,6 +86,11 @@ public class SipSubscribe { | ||
| 87 | public String callId; | 86 | public String callId; |
| 88 | public EventObject event; | 87 | public EventObject event; |
| 89 | 88 | ||
| 89 | + public EventResult(int statusCode, String msg) { | ||
| 90 | + this.statusCode = statusCode; | ||
| 91 | + this.msg = msg; | ||
| 92 | + } | ||
| 93 | + | ||
| 90 | public EventResult(EventObject event) { | 94 | public EventResult(EventObject event) { |
| 91 | this.event = event; | 95 | this.event = event; |
| 92 | if (event instanceof ResponseEvent) { | 96 | if (event instanceof ResponseEvent) { |
src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
| @@ -23,10 +23,6 @@ public class AudioBroadcastManager { | @@ -23,10 +23,6 @@ public class AudioBroadcastManager { | ||
| 23 | 23 | ||
| 24 | public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>(); | 24 | public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>(); |
| 25 | 25 | ||
| 26 | - public void add(AudioBroadcastCatch audioBroadcastCatch) { | ||
| 27 | - this.update(audioBroadcastCatch); | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | public void update(AudioBroadcastCatch audioBroadcastCatch) { | 26 | public void update(AudioBroadcastCatch audioBroadcastCatch) { |
| 31 | if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) { | 27 | if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) { |
| 32 | data.put(audioBroadcastCatch.getDeviceId(), audioBroadcastCatch); | 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,8 +49,6 @@ public class DeferredResultHolder { | ||
| 49 | 49 | ||
| 50 | public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; | 50 | public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; |
| 51 | 51 | ||
| 52 | - public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST"; | ||
| 53 | - | ||
| 54 | private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>(); | 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 | package com.genersoft.iot.vmp.gb28181.transmit.cmd; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd; |
| 2 | 2 | ||
| 3 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 3 | import com.genersoft.iot.vmp.gb28181.bean.*; | 4 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 4 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | 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 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; | 8 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 9 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | ||
| 6 | 10 | ||
| 7 | import javax.sip.InvalidArgumentException; | 11 | import javax.sip.InvalidArgumentException; |
| 8 | import javax.sip.SipException; | 12 | import javax.sip.SipException; |
| @@ -14,77 +18,98 @@ public interface ISIPCommanderForPlatform { | @@ -14,77 +18,98 @@ public interface ISIPCommanderForPlatform { | ||
| 14 | 18 | ||
| 15 | /** | 19 | /** |
| 16 | * 向上级平台注册 | 20 | * 向上级平台注册 |
| 21 | + * | ||
| 17 | * @param parentPlatform | 22 | * @param parentPlatform |
| 18 | * @return | 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 | * @param parentPlatform | 35 | * @param parentPlatform |
| 26 | * @return | 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 | * @param parentPlatform | 45 | * @param parentPlatform |
| 34 | * @return callId(作为接受回复的判定) | 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 | * @param parentPlatform 平台信息 | 56 | * @param parentPlatform 平台信息 |
| 43 | * @param sn | 57 | * @param sn |
| 44 | * @param fromTag | 58 | * @param fromTag |
| 45 | * @param size | 59 | * @param size |
| 46 | * @return | 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 | * 向上级回复DeviceInfo查询信息 | 69 | * 向上级回复DeviceInfo查询信息 |
| 70 | + * | ||
| 53 | * @param parentPlatform 平台信息 | 71 | * @param parentPlatform 平台信息 |
| 54 | * @param sn | 72 | * @param sn |
| 55 | * @param fromTag | 73 | * @param fromTag |
| 56 | * @return | 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 | * 向上级回复DeviceStatus查询信息 | 80 | * 向上级回复DeviceStatus查询信息 |
| 81 | + * | ||
| 62 | * @param parentPlatform 平台信息 | 82 | * @param parentPlatform 平台信息 |
| 63 | * @param sn | 83 | * @param sn |
| 64 | * @param fromTag | 84 | * @param fromTag |
| 65 | * @return | 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 | * @param parentPlatform 平台信息 | 93 | * @param parentPlatform 平台信息 |
| 72 | - * @param gpsMsgInfo GPS信息 | ||
| 73 | - * @param subscribeInfo 订阅相关的信息 | 94 | + * @param gpsMsgInfo GPS信息 |
| 95 | + * @param subscribeInfo 订阅相关的信息 | ||
| 74 | * @return | 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 | * @param parentPlatform 平台信息 | 104 | * @param parentPlatform 平台信息 |
| 81 | - * @param deviceAlarm 报警信息信息 | 105 | + * @param deviceAlarm 报警信息信息 |
| 82 | * @return | 106 | * @return |
| 83 | */ | 107 | */ |
| 84 | void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; | 108 | void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; |
| 85 | 109 | ||
| 86 | /** | 110 | /** |
| 87 | * 回复catalog事件-增加/更新 | 111 | * 回复catalog事件-增加/更新 |
| 112 | + * | ||
| 88 | * @param parentPlatform | 113 | * @param parentPlatform |
| 89 | * @param deviceChannels | 114 | * @param deviceChannels |
| 90 | */ | 115 | */ |
| @@ -92,22 +117,28 @@ public interface ISIPCommanderForPlatform { | @@ -92,22 +117,28 @@ public interface ISIPCommanderForPlatform { | ||
| 92 | 117 | ||
| 93 | /** | 118 | /** |
| 94 | * 回复catalog事件-删除 | 119 | * 回复catalog事件-删除 |
| 120 | + * | ||
| 95 | * @param parentPlatform | 121 | * @param parentPlatform |
| 96 | * @param deviceChannels | 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 | * 回复recordInfo | 129 | * 回复recordInfo |
| 102 | - * @param deviceChannel 通道信息 | 130 | + * |
| 131 | + * @param deviceChannel 通道信息 | ||
| 103 | * @param parentPlatform 平台信息 | 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 | * 录像播放推送完成时发送MediaStatus消息 | 140 | * 录像播放推送完成时发送MediaStatus消息 |
| 141 | + * | ||
| 111 | * @param platform | 142 | * @param platform |
| 112 | * @param sendRtpItem | 143 | * @param sendRtpItem |
| 113 | * @return | 144 | * @return |
| @@ -116,9 +147,19 @@ public interface ISIPCommanderForPlatform { | @@ -116,9 +147,19 @@ public interface ISIPCommanderForPlatform { | ||
| 116 | 147 | ||
| 117 | /** | 148 | /** |
| 118 | * 向发起点播的上级回复bye | 149 | * 向发起点播的上级回复bye |
| 150 | + * | ||
| 119 | * @param platform 平台信息 | 151 | * @param platform 平台信息 |
| 120 | - * @param callId callId | 152 | + * @param callId callId |
| 121 | */ | 153 | */ |
| 122 | void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; | 154 | void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; |
| 155 | + | ||
| 123 | void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException; | 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,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig; | ||
| 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; | 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 5 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | 5 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 6 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | 6 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; | ||
| 7 | import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; | 8 | import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; |
| 8 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | 9 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 9 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 10 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| @@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
| 14 | import org.springframework.stereotype.Component; | 15 | import org.springframework.stereotype.Component; |
| 15 | import org.springframework.util.DigestUtils; | 16 | import org.springframework.util.DigestUtils; |
| 16 | 17 | ||
| 17 | -import javax.sip.*; | 18 | +import javax.sip.InvalidArgumentException; |
| 19 | +import javax.sip.PeerUnavailableException; | ||
| 18 | import javax.sip.address.Address; | 20 | import javax.sip.address.Address; |
| 19 | import javax.sip.address.SipURI; | 21 | import javax.sip.address.SipURI; |
| 20 | import javax.sip.header.*; | 22 | import javax.sip.header.*; |
| @@ -22,7 +24,6 @@ import javax.sip.message.Request; | @@ -22,7 +24,6 @@ import javax.sip.message.Request; | ||
| 22 | import javax.validation.constraints.NotNull; | 24 | import javax.validation.constraints.NotNull; |
| 23 | import java.text.ParseException; | 25 | import java.text.ParseException; |
| 24 | import java.util.ArrayList; | 26 | import java.util.ArrayList; |
| 25 | -import java.util.List; | ||
| 26 | import java.util.UUID; | 27 | import java.util.UUID; |
| 27 | 28 | ||
| 28 | /** | 29 | /** |
| @@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider { | @@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 175 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); | 176 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); |
| 176 | // via | 177 | // via |
| 177 | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); | 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 | parentPlatform.getTransport(), viaTag); | 180 | parentPlatform.getTransport(), viaTag); |
| 180 | viaHeader.setRPort(); | 181 | viaHeader.setRPort(); |
| 181 | viaHeaders.add(viaHeader); | 182 | viaHeaders.add(viaHeader); |
| @@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider { | @@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 212 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); | 213 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); |
| 213 | // via | 214 | // via |
| 214 | ArrayList<ViaHeader> viaHeaders = new ArrayList<>(); | 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 | parentPlatform.getTransport(), SipUtils.getNewViaTag()); | 217 | parentPlatform.getTransport(), SipUtils.getNewViaTag()); |
| 217 | viaHeader.setRPort(); | 218 | viaHeader.setRPort(); |
| 218 | viaHeaders.add(viaHeader); | 219 | viaHeaders.add(viaHeader); |
| @@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider { | @@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 272 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); | 273 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); |
| 273 | // via | 274 | // via |
| 274 | ArrayList<ViaHeader> viaHeaders = new ArrayList<>(); | 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 | platform.getTransport(), SipUtils.getNewViaTag()); | 277 | platform.getTransport(), SipUtils.getNewViaTag()); |
| 277 | viaHeader.setRPort(); | 278 | viaHeader.setRPort(); |
| 278 | viaHeaders.add(viaHeader); | 279 | viaHeaders.add(viaHeader); |
| @@ -308,4 +309,81 @@ public class SIPRequestHeaderPlarformProvider { | @@ -308,4 +309,81 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 308 | 309 | ||
| 309 | return request; | 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,12 +590,12 @@ public class SIPCommander implements ISIPCommander { | ||
| 590 | return; | 590 | return; |
| 591 | } | 591 | } |
| 592 | if (!mediaServerItem.isRtpEnable()) { | 592 | if (!mediaServerItem.isRtpEnable()) { |
| 593 | - // 单端口暂不支持语音对讲 | ||
| 594 | - logger.info("[语音对讲] 单端口暂不支持此操作"); | 593 | + // 单端口暂不支持语音喊话 |
| 594 | + logger.info("[语音喊话] 单端口暂不支持此操作"); | ||
| 595 | return; | 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 | HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); | 599 | HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); |
| 600 | subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { | 600 | subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { |
| 601 | if (event != null) { | 601 | if (event != null) { |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
| 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; |
| 2 | 2 | ||
| 3 | import com.alibaba.fastjson2.JSON; | 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 | import com.genersoft.iot.vmp.gb28181.SipLayer; | 7 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 5 | import com.genersoft.iot.vmp.gb28181.bean.*; | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | 9 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 10 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | ||
| 7 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; | 11 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 8 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | 12 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; |
| 9 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; | 13 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; |
| 10 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | 14 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 11 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | 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 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 19 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 13 | import com.genersoft.iot.vmp.service.IMediaServerService; | 20 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 14 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; | 21 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 22 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | ||
| 15 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 23 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 16 | import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; | 24 | import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; |
| 17 | import com.genersoft.iot.vmp.utils.DateUtil; | 25 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 18 | import gov.nist.javax.sip.message.MessageFactoryImpl; | 26 | import gov.nist.javax.sip.message.MessageFactoryImpl; |
| 19 | import gov.nist.javax.sip.message.SIPRequest; | 27 | import gov.nist.javax.sip.message.SIPRequest; |
| 28 | +import gov.nist.javax.sip.message.SIPResponse; | ||
| 20 | import org.slf4j.Logger; | 29 | import org.slf4j.Logger; |
| 21 | import org.slf4j.LoggerFactory; | 30 | import org.slf4j.LoggerFactory; |
| 22 | import org.springframework.beans.factory.annotation.Autowired; | 31 | import org.springframework.beans.factory.annotation.Autowired; |
| @@ -26,6 +35,7 @@ import org.springframework.stereotype.Component; | @@ -26,6 +35,7 @@ import org.springframework.stereotype.Component; | ||
| 26 | import org.springframework.util.ObjectUtils; | 35 | import org.springframework.util.ObjectUtils; |
| 27 | 36 | ||
| 28 | import javax.sip.InvalidArgumentException; | 37 | import javax.sip.InvalidArgumentException; |
| 38 | +import javax.sip.ResponseEvent; | ||
| 29 | import javax.sip.SipException; | 39 | import javax.sip.SipException; |
| 30 | import javax.sip.header.CallIdHeader; | 40 | import javax.sip.header.CallIdHeader; |
| 31 | import javax.sip.header.WWWAuthenticateHeader; | 41 | import javax.sip.header.WWWAuthenticateHeader; |
| @@ -61,6 +71,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | @@ -61,6 +71,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | ||
| 61 | @Autowired | 71 | @Autowired |
| 62 | private SIPSender sipSender; | 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 | @Override | 84 | @Override |
| 65 | public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { | 85 | public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException { |
| 66 | register(parentPlatform, null, null, errorEvent, okEvent, false, true); | 86 | register(parentPlatform, null, null, errorEvent, okEvent, false, true); |
| @@ -645,4 +665,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | @@ -645,4 +665,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | ||
| 645 | } | 665 | } |
| 646 | sipSender.transmitRequest(platform.getDeviceIp(),byeRequest); | 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,8 +16,6 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor | ||
| 16 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; | 16 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 17 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | 17 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 18 | import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | 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 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 19 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 22 | import com.genersoft.iot.vmp.service.IDeviceService; | 20 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 23 | import com.genersoft.iot.vmp.service.IMediaServerService; | 21 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| @@ -40,8 +38,6 @@ import javax.sip.header.FromHeader; | @@ -40,8 +38,6 @@ import javax.sip.header.FromHeader; | ||
| 40 | import javax.sip.header.HeaderAddress; | 38 | import javax.sip.header.HeaderAddress; |
| 41 | import javax.sip.header.ToHeader; | 39 | import javax.sip.header.ToHeader; |
| 42 | import java.text.ParseException; | 40 | import java.text.ParseException; |
| 43 | -import java.util.HashMap; | ||
| 44 | -import java.util.Map; | ||
| 45 | 41 | ||
| 46 | /** | 42 | /** |
| 47 | * SIP命令类型: ACK请求 | 43 | * SIP命令类型: ACK请求 |
| @@ -123,68 +119,31 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In | @@ -123,68 +119,31 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In | ||
| 123 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | 119 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 124 | logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(), | 120 | logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(), |
| 125 | sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); | 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 | if (mediaInfo == null) { | 122 | if (mediaInfo == null) { |
| 141 | RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( | 123 | RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( |
| 142 | sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), | 124 | sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), |
| 143 | sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), | 125 | sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), |
| 144 | sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); | 126 | sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); |
| 145 | redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> { | 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 | if (startSendRtpStreamResult != null) { | 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 | private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, | 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 | if (jsonObject == null) { | 140 | if (jsonObject == null) { |
| 182 | logger.error("RTP推流失败: 请检查ZLM服务"); | 141 | logger.error("RTP推流失败: 请检查ZLM服务"); |
| 183 | } else if (jsonObject.getInteger("code") == 0) { | 142 | } else if (jsonObject.getInteger("code") == 0) { |
| 184 | logger.info("调用ZLM推流接口, 结果: {}", jsonObject); | 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 | } else { | 145 | } else { |
| 187 | - logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param)); | 146 | + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(sendRtpItem)); |
| 188 | if (sendRtpItem.isOnlyAudio()) { | 147 | if (sendRtpItem.isOnlyAudio()) { |
| 189 | Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); | 148 | Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); |
| 190 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | 149 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); |
| @@ -193,7 +152,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In | @@ -193,7 +152,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In | ||
| 193 | cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); | 152 | cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); |
| 194 | } catch (SipException | ParseException | InvalidArgumentException | | 153 | } catch (SipException | ParseException | InvalidArgumentException | |
| 195 | SsrcTransactionNotFoundException e) { | 154 | SsrcTransactionNotFoundException e) { |
| 196 | - logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage()); | 155 | + logger.error("[命令发送失败] 停止语音喊话: {}", e.getMessage()); |
| 197 | } | 156 | } |
| 198 | } | 157 | } |
| 199 | }else { | 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,7 +201,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 201 | 201 | ||
| 202 | MediaServerItem mediaServerItem = null; | 202 | MediaServerItem mediaServerItem = null; |
| 203 | StreamPushItem streamPushItem = null; | 203 | StreamPushItem streamPushItem = null; |
| 204 | - StreamProxyItem proxyByAppAndStream =null; | 204 | + StreamProxyItem proxyByAppAndStream = null; |
| 205 | // 不是通道可能是直播流 | 205 | // 不是通道可能是直播流 |
| 206 | if (channel != null && gbStream == null) { | 206 | if (channel != null && gbStream == null) { |
| 207 | // 通道存在,发100,TRYING | 207 | // 通道存在,发100,TRYING |
| @@ -1001,7 +1001,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | @@ -1001,7 +1001,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 1001 | String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); | 1001 | String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); |
| 1002 | 1002 | ||
| 1003 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); | 1003 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); |
| 1004 | - sendRtpItem.setPlayType(InviteStreamType.TALK); | 1004 | + sendRtpItem.setPlayType(InviteStreamType.BROADCAST); |
| 1005 | sendRtpItem.setCallId(callIdHeader.getCallId()); | 1005 | sendRtpItem.setCallId(callIdHeader.getCallId()); |
| 1006 | sendRtpItem.setPlatformId(requesterId); | 1006 | sendRtpItem.setPlatformId(requesterId); |
| 1007 | sendRtpItem.setStatus(1); | 1007 | sendRtpItem.setStatus(1); |
| @@ -1013,6 +1013,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | @@ -1013,6 +1013,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 1013 | sendRtpItem.setOnlyAudio(true); | 1013 | sendRtpItem.setOnlyAudio(true); |
| 1014 | redisCatchStorage.updateSendRTPSever(sendRtpItem); | 1014 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 1015 | 1015 | ||
| 1016 | + | ||
| 1016 | Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream); | 1017 | Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream); |
| 1017 | if (streamReady) { | 1018 | if (streamReady) { |
| 1018 | sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, ssrc); | 1019 | sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, ssrc); |
| @@ -1084,7 +1085,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | @@ -1084,7 +1085,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 1084 | audioBroadcastManager.update(audioBroadcastCatch); | 1085 | audioBroadcastManager.update(audioBroadcastCatch); |
| 1085 | 1086 | ||
| 1086 | } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { | 1087 | } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { |
| 1087 | - logger.error("[命令发送失败] 语音对讲 回复200OK(SDP): {}", e.getMessage()); | 1088 | + logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage()); |
| 1088 | } | 1089 | } |
| 1089 | return sipResponse; | 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 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; |
| 2 | 2 | ||
| 3 | -import com.alibaba.fastjson2.JSONObject; | ||
| 4 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; | 3 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; |
| 5 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus; | 4 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus; |
| 6 | import com.genersoft.iot.vmp.gb28181.bean.Device; | 5 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 7 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | 6 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 8 | import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | 7 | import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; |
| 9 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | 8 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| 10 | -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | ||
| 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; | 9 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; | 10 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; |
| 13 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; | 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; |
| 14 | -import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; | ||
| 15 | import gov.nist.javax.sip.message.SIPRequest; | 12 | import gov.nist.javax.sip.message.SIPRequest; |
| 16 | import org.dom4j.Element; | 13 | import org.dom4j.Element; |
| 17 | import org.slf4j.Logger; | 14 | import org.slf4j.Logger; |
| @@ -52,26 +49,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i | @@ -52,26 +49,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i | ||
| 52 | public void handForDevice(RequestEvent evt, Device device, Element rootElement) { | 49 | public void handForDevice(RequestEvent evt, Device device, Element rootElement) { |
| 53 | try { | 50 | try { |
| 54 | String channelId = getText(rootElement, "DeviceID"); | 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 | if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | 52 | if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { |
| 70 | // 回复410 | 53 | // 回复410 |
| 71 | responseAck((SIPRequest) evt.getRequest(), Response.GONE); | 54 | responseAck((SIPRequest) evt.getRequest(), Response.GONE); |
| 72 | return; | 55 | return; |
| 73 | } | 56 | } |
| 74 | - logger.info("收到语音广播的回复:{}/{}", device.getDeviceId(), channelId ); | 57 | + String result = getText(rootElement, "Result"); |
| 58 | + logger.info("收到语音广播的回复 {}:{}/{}", result, device.getDeviceId(), channelId ); | ||
| 75 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId); | 59 | AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId); |
| 76 | audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); | 60 | audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); |
| 77 | audioBroadcastManager.update(audioBroadcastCatch); | 61 | audioBroadcastManager.update(audioBroadcastCatch); |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
| 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; |
| 2 | 2 | ||
| 3 | -import com.genersoft.iot.vmp.conf.SipConfig; | ||
| 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; | 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 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; | 4 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| 9 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; | 5 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 10 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | ||
| 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; | 6 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; |
| 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; | 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 | import gov.nist.javax.sip.ResponseEventExt; | 8 | import gov.nist.javax.sip.ResponseEventExt; |
| 17 | -import gov.nist.javax.sip.SipProviderImpl; | ||
| 18 | import gov.nist.javax.sip.message.SIPResponse; | 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 | import org.slf4j.Logger; | 10 | import org.slf4j.Logger; |
| 24 | import org.slf4j.LoggerFactory; | 11 | import org.slf4j.LoggerFactory; |
| 25 | import org.springframework.beans.factory.annotation.Autowired; | 12 | import org.springframework.beans.factory.annotation.Autowired; |
| 26 | -import org.springframework.beans.factory.annotation.Qualifier; | ||
| 27 | import org.springframework.stereotype.Component; | 13 | import org.springframework.stereotype.Component; |
| 28 | 14 | ||
| 29 | import javax.sdp.SdpFactory; | 15 | import javax.sdp.SdpFactory; |
| 30 | import javax.sdp.SdpParseException; | 16 | import javax.sdp.SdpParseException; |
| 31 | import javax.sdp.SessionDescription; | 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 | import javax.sip.address.SipURI; | 21 | import javax.sip.address.SipURI; |
| 35 | -import javax.sip.header.CSeqHeader; | ||
| 36 | -import javax.sip.header.UserAgentHeader; | ||
| 37 | import javax.sip.message.Request; | 22 | import javax.sip.message.Request; |
| 38 | import javax.sip.message.Response; | 23 | import javax.sip.message.Response; |
| 39 | import java.text.ParseException; | 24 | import java.text.ParseException; |
| @@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract { | @@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract { | ||
| 104 | } else { | 89 | } else { |
| 105 | sdp = SdpFactory.getInstance().createSessionDescription(contentString); | 90 | sdp = SdpFactory.getInstance().createSessionDescription(contentString); |
| 106 | } | 91 | } |
| 92 | + // 查看是否是来自设备的,此是回复 | ||
| 107 | 93 | ||
| 108 | SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); | 94 | SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); |
| 109 | Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response); | 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,15 +287,19 @@ public class ZLMHttpHookListener { | ||
| 287 | logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | 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 | JSONObject json = (JSONObject) JSON.toJSON(param); | 294 | JSONObject json = (JSONObject) JSON.toJSON(param); |
| 295 | + MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | ||
| 296 | + if (mediaInfo == null) { | ||
| 297 | + return ret; | ||
| 298 | + } | ||
| 292 | taskExecutor.execute(()-> { | 299 | taskExecutor.execute(()-> { |
| 293 | ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); | 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 | // 流消失移除redis play | 304 | // 流消失移除redis play |
| 301 | List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); | 305 | List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); |
| @@ -343,7 +347,7 @@ public class ZLMHttpHookListener { | @@ -343,7 +347,7 @@ public class ZLMHttpHookListener { | ||
| 343 | } | 347 | } |
| 344 | } | 348 | } |
| 345 | }else if ("broadcast".equals(param.getApp())){ | 349 | }else if ("broadcast".equals(param.getApp())){ |
| 346 | - // 语音对讲推流 stream需要满足格式deviceId_channelId | 350 | + // 语音喊话推流 stream需要满足格式deviceId_channelId |
| 347 | if (param.isRegist() && param.getStream().indexOf("_") > 0) { | 351 | if (param.isRegist() && param.getStream().indexOf("_") > 0) { |
| 348 | String[] streamArray = param.getStream().split("_"); | 352 | String[] streamArray = param.getStream().split("_"); |
| 349 | if (streamArray.length == 2) { | 353 | if (streamArray.length == 2) { |
| @@ -359,53 +363,38 @@ public class ZLMHttpHookListener { | @@ -359,53 +363,38 @@ public class ZLMHttpHookListener { | ||
| 359 | if (sendRtpItem == null) { | 363 | if (sendRtpItem == null) { |
| 360 | // TODO 可能数据错误,重新开启语音通道 | 364 | // TODO 可能数据错误,重新开启语音通道 |
| 361 | }else { | 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 | }else { | 369 | }else { |
| 387 | - logger.info("[语音对讲] 推流失败, 结果: {}", jsonObject); | 370 | + logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject); |
| 388 | } | 371 | } |
| 389 | 372 | ||
| 390 | } | 373 | } |
| 391 | }else { | 374 | }else { |
| 392 | - // 开启语音对讲通道 | 375 | + // 开启语音喊话通道 |
| 393 | try { | 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 | } catch (InvalidArgumentException | ParseException | SipException e) { | 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 | }else if ("talk".equals(param.getApp())){ | 396 | }else if ("talk".equals(param.getApp())){ |
| 408 | - // 语音对讲推流 stream需要满足格式deviceId_channelId | 397 | + // 语音喊话推流 stream需要满足格式deviceId_channelId |
| 409 | if (param.isRegist() && param.getStream().indexOf("_") > 0) { | 398 | if (param.isRegist() && param.getStream().indexOf("_") > 0) { |
| 410 | String[] streamArray = param.getStream().split("_"); | 399 | String[] streamArray = param.getStream().split("_"); |
| 411 | if (streamArray.length == 2) { | 400 | if (streamArray.length == 2) { |
| @@ -421,33 +410,11 @@ public class ZLMHttpHookListener { | @@ -421,33 +410,11 @@ public class ZLMHttpHookListener { | ||
| 421 | if (sendRtpItem == null) { | 410 | if (sendRtpItem == null) { |
| 422 | // TODO 可能数据错误,重新开启语音通道 | 411 | // TODO 可能数据错误,重新开启语音通道 |
| 423 | }else { | 412 | }else { |
| 424 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | ||
| 425 | logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); | 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 | }else { | 416 | }else { |
| 450 | - // 开启语音对讲通道 | 417 | + // 开启语音喊话通道 |
| 451 | MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); | 418 | MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); |
| 452 | playService.talk(mediaServerItem, device, channelId, (mediaServer, jsonObject)->{ | 419 | playService.talk(mediaServerItem, device, channelId, (mediaServer, jsonObject)->{ |
| 453 | System.out.println("开始推流"); | 420 | System.out.println("开始推流"); |
| @@ -549,9 +516,7 @@ public class ZLMHttpHookListener { | @@ -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 | return ret; | 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,4 +348,52 @@ public class ZLMRTPServerFactory { | ||
| 348 | return result; | 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 | package com.genersoft.iot.vmp.service; | 1 | package com.genersoft.iot.vmp.service; |
| 2 | 2 | ||
| 3 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 3 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | 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 | import com.github.pagehelper.PageInfo; | 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 | * @author lin | 17 | * @author lin |
| @@ -48,4 +57,23 @@ public interface IPlatformService { | @@ -48,4 +57,23 @@ public interface IPlatformService { | ||
| 48 | * @param platformId 平台 | 57 | * @param platformId 平台 |
| 49 | */ | 58 | */ |
| 50 | void sendNotifyMobilePosition(String platformId); | 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,10 +53,13 @@ public interface IPlayService { | ||
| 53 | 53 | ||
| 54 | void zlmServerOnline(String mediaServerId); | 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 | void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; | 64 | void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| 62 | 65 |
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
| 1 | package com.genersoft.iot.vmp.service.impl; | 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 | import com.genersoft.iot.vmp.conf.DynamicTask; | 5 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 4 | import com.genersoft.iot.vmp.conf.UserSetting; | 6 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 7 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 5 | import com.genersoft.iot.vmp.gb28181.bean.*; | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | 9 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 10 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | ||
| 7 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; | 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; |
| 8 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | 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 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 16 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 10 | import com.genersoft.iot.vmp.service.IMediaServerService; | 17 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 11 | import com.genersoft.iot.vmp.service.IPlatformService; | 18 | import com.genersoft.iot.vmp.service.IPlatformService; |
| 19 | +import com.genersoft.iot.vmp.service.IPlayService; | ||
| 12 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; | 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 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 23 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 14 | import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; | 24 | import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; |
| 15 | import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper; | 25 | import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper; |
| @@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
| 21 | import org.springframework.stereotype.Service; | 31 | import org.springframework.stereotype.Service; |
| 22 | 32 | ||
| 23 | import javax.sip.InvalidArgumentException; | 33 | import javax.sip.InvalidArgumentException; |
| 34 | +import javax.sip.ResponseEvent; | ||
| 24 | import javax.sip.SipException; | 35 | import javax.sip.SipException; |
| 25 | import java.text.ParseException; | 36 | import java.text.ParseException; |
| 26 | import java.util.HashMap; | 37 | import java.util.HashMap; |
| 27 | import java.util.List; | 38 | import java.util.List; |
| 28 | import java.util.Map; | 39 | import java.util.Map; |
| 40 | +import java.util.UUID; | ||
| 29 | 41 | ||
| 30 | /** | 42 | /** |
| 31 | * @author lin | 43 | * @author lin |
| @@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService { | @@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService { | ||
| 65 | @Autowired | 77 | @Autowired |
| 66 | private UserSetting userSetting; | 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 | @Override | 92 | @Override |
| @@ -295,4 +317,137 @@ public class PlatformServiceImpl implements IPlatformService { | @@ -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,7 +987,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 987 | } | 987 | } |
| 988 | 988 | ||
| 989 | @Override | 989 | @Override |
| 990 | - public AudioBroadcastResult audioBroadcast(Device device, String channelId) { | 990 | + public AudioBroadcastResult audioBroadcastInfo(Device device, String channelId) { |
| 991 | if (device == null || channelId == null) { | 991 | if (device == null || channelId == null) { |
| 992 | return null; | 992 | return null; |
| 993 | } | 993 | } |
| @@ -1012,46 +1012,51 @@ public class PlayServiceImpl implements IPlayService { | @@ -1012,46 +1012,51 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1012 | } | 1012 | } |
| 1013 | 1013 | ||
| 1014 | @Override | 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 | if (device == null || channelId == null) { | 1016 | if (device == null || channelId == null) { |
| 1017 | - return; | 1017 | + return false; |
| 1018 | } | 1018 | } |
| 1019 | logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); | 1019 | logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); |
| 1020 | DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); | 1020 | DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); |
| 1021 | if (deviceChannel == null) { | 1021 | if (deviceChannel == null) { |
| 1022 | logger.warn("开启语音广播的时候未找到通道: {}", channelId); | 1022 | logger.warn("开启语音广播的时候未找到通道: {}", channelId); |
| 1023 | event.call("开启语音广播的时候未找到通道"); | 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 | cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> { | 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 | }, eventResultForError -> { | 1036 | }, eventResultForError -> { |
| 1050 | // 发送失败 | 1037 | // 发送失败 |
| 1051 | logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg); | 1038 | logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg); |
| 1052 | event.call("语音广播发送失败"); | 1039 | event.call("语音广播发送失败"); |
| 1053 | stopAudioBroadcast(device.getDeviceId(), channelId); | 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,6 +1080,9 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1075 | param.put("stream", sendRtpItem.getStreamId()); | 1080 | param.put("stream", sendRtpItem.getStreamId()); |
| 1076 | zlmresTfulUtils.stopSendRtp(mediaInfo, param); | 1081 | zlmresTfulUtils.stopSendRtp(mediaInfo, param); |
| 1077 | } | 1082 | } |
| 1083 | + if (audioBroadcastCatch.isFromPlatform()) { | ||
| 1084 | + // TODO 向上级发送BYE结束语音喊话 | ||
| 1085 | + } | ||
| 1078 | 1086 | ||
| 1079 | audioBroadcastManager.del(deviceId, channelId); | 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,7 +267,7 @@ public class PlayController { | ||
| 267 | throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelId); | 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,7 +168,7 @@ user-settings: | ||
| 168 | # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认) | 168 | # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认) |
| 169 | save-position-history: false | 169 | save-position-history: false |
| 170 | # 点播等待超时时间,单位:毫秒 | 170 | # 点播等待超时时间,单位:毫秒 |
| 171 | - play-timeout: 3000 | 171 | + play-timeout: 18000 |
| 172 | # 上级点播等待超时时间,单位:毫秒 | 172 | # 上级点播等待超时时间,单位:毫秒 |
| 173 | platform-play-timeout: 60000 | 173 | platform-play-timeout: 60000 |
| 174 | # 是否开启接口鉴权 | 174 | # 是否开启接口鉴权 |
| @@ -195,6 +195,8 @@ user-settings: | @@ -195,6 +195,8 @@ user-settings: | ||
| 195 | gb-send-stream-strict: false | 195 | gb-send-stream-strict: false |
| 196 | # 设备上线时是否自动同步通道 | 196 | # 设备上线时是否自动同步通道 |
| 197 | sync-channel-on-device-online: false | 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 | springdoc: | 202 | springdoc: |
web_src/src/components/dialog/devicePlayer.vue
| @@ -279,7 +279,7 @@ | @@ -279,7 +279,7 @@ | ||
| 279 | </div> | 279 | </div> |
| 280 | 280 | ||
| 281 | </el-tab-pane> | 281 | </el-tab-pane> |
| 282 | - <el-tab-pane label="语音对讲" name="broadcast" > | 282 | + <el-tab-pane label="语音喊话" name="broadcast" > |
| 283 | <div class="trank" style="text-align: center;"> | 283 | <div class="trank" style="text-align: center;"> |
| 284 | <el-button @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2" circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;"/> | 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 | <p> | 285 | <p> |
| @@ -854,7 +854,7 @@ export default { | @@ -854,7 +854,7 @@ export default { | ||
| 854 | if (this.broadcastStatus == -1) { | 854 | if (this.broadcastStatus == -1) { |
| 855 | // 默认状态, 开始 | 855 | // 默认状态, 开始 |
| 856 | this.broadcastStatus = 0 | 856 | this.broadcastStatus = 0 |
| 857 | - // 发起语音对讲 | 857 | + // 发起语音喊话 |
| 858 | this.$axios({ | 858 | this.$axios({ |
| 859 | method: 'get', | 859 | method: 'get', |
| 860 | url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30" | 860 | url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30" |
| @@ -897,7 +897,7 @@ export default { | @@ -897,7 +897,7 @@ export default { | ||
| 897 | let pushKey = res.data.data.pushKey; | 897 | let pushKey = res.data.data.pushKey; |
| 898 | // 获取推流鉴权KEY | 898 | // 获取推流鉴权KEY |
| 899 | url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex') | 899 | url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex') |
| 900 | - console.log("开始语音对讲: " + url) | 900 | + console.log("开始语音喊话: " + url) |
| 901 | this.broadcastRtc = new ZLMRTCClient.Endpoint({ | 901 | this.broadcastRtc = new ZLMRTCClient.Endpoint({ |
| 902 | debug: true, // 是否打印日志 | 902 | debug: true, // 是否打印日志 |
| 903 | zlmsdpUrl: url, //流地址 | 903 | zlmsdpUrl: url, //流地址 |
| @@ -923,7 +923,7 @@ export default { | @@ -923,7 +923,7 @@ export default { | ||
| 923 | console.error('不支持webrtc',e) | 923 | console.error('不支持webrtc',e) |
| 924 | this.$message({ | 924 | this.$message({ |
| 925 | showClose: true, | 925 | showClose: true, |
| 926 | - message: '不支持webrtc, 无法进行语音对讲', | 926 | + message: '不支持webrtc, 无法进行语音喊话', |
| 927 | type: 'error' | 927 | type: 'error' |
| 928 | }); | 928 | }); |
| 929 | this.broadcastStatus = -1; | 929 | this.broadcastStatus = -1; |