Commit 4b827f3897600e97023ded3df83a2f2551131d53

Authored by 648540858
1 parent 38a85d43

级联语音对讲部分

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