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,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;